From 3405265ac81951e1455a35783b7c70938d480c19 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 22 Mar 2026 00:39:53 -0300 Subject: [PATCH 01/78] Updated alarm tests --- src/MIDTesters.Core/Alarm/TestMid0071.cs | 37 ++++++------- src/MIDTesters.Core/Alarm/TestMid0074.cs | 12 ++--- src/MIDTesters.Core/Alarm/TestMid0076.cs | 66 ++++++++++++------------ src/MIDTesters.Core/Alarm/TestMid1000.cs | 13 ++--- 4 files changed, 64 insertions(+), 64 deletions(-) diff --git a/src/MIDTesters.Core/Alarm/TestMid0071.cs b/src/MIDTesters.Core/Alarm/TestMid0071.cs index 103ab7a8..7112edcf 100644 --- a/src/MIDTesters.Core/Alarm/TestMid0071.cs +++ b/src/MIDTesters.Core/Alarm/TestMid0071.cs @@ -1,5 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter.Alarm; +using System; namespace MIDTesters.Alarm { @@ -14,10 +15,10 @@ public void Mid0071Revision1() string pack = @"00530071001 01E851021031042017-12-01:20:12:45"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.ErrorCode); - Assert.IsNotNull(mid.ControllerReadyStatus); - Assert.IsNotNull(mid.ToolReadyStatus); - Assert.IsNotNull(mid.Time); + Assert.AreEqual("E851", mid.ErrorCode); + Assert.IsTrue(mid.ControllerReadyStatus); + Assert.IsTrue(mid.ToolReadyStatus); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.Time); AssertEqualPackages(pack, mid); } @@ -29,10 +30,10 @@ public void Mid0071ByteRevision1() byte[] bytes = GetAsciiBytes(pack); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ErrorCode); - Assert.IsNotNull(mid.ControllerReadyStatus); - Assert.IsNotNull(mid.ToolReadyStatus); - Assert.IsNotNull(mid.Time); + Assert.AreEqual("E851", mid.ErrorCode); + Assert.IsTrue(mid.ControllerReadyStatus); + Assert.IsTrue(mid.ToolReadyStatus); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.Time); AssertEqualPackages(bytes, mid); } @@ -43,11 +44,11 @@ public void Mid0071Revision2() string pack = @"01060071002 01E1021021031042017-12-01:20:12:4505Alarm Text "; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.ErrorCode); - Assert.IsNotNull(mid.ControllerReadyStatus); - Assert.IsNotNull(mid.ToolReadyStatus); - Assert.IsNotNull(mid.Time); - Assert.IsNotNull(mid.AlarmText); + Assert.AreEqual("E1021", mid.ErrorCode); + Assert.IsTrue(mid.ControllerReadyStatus); + Assert.IsTrue(mid.ToolReadyStatus); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.Time); + Assert.AreEqual("Alarm Text ", mid.AlarmText); AssertEqualPackages(pack, mid); } @@ -59,11 +60,11 @@ public void Mid0071ByteRevision2() byte[] bytes = GetAsciiBytes(pack); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ErrorCode); - Assert.IsNotNull(mid.ControllerReadyStatus); - Assert.IsNotNull(mid.ToolReadyStatus); - Assert.IsNotNull(mid.Time); - Assert.IsNotNull(mid.AlarmText); + Assert.AreEqual("E1021", mid.ErrorCode); + Assert.IsTrue(mid.ControllerReadyStatus); + Assert.IsTrue(mid.ToolReadyStatus); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.Time); + Assert.AreEqual("Alarm Text ", mid.AlarmText); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/Alarm/TestMid0074.cs b/src/MIDTesters.Core/Alarm/TestMid0074.cs index b41f608c..ca4243df 100644 --- a/src/MIDTesters.Core/Alarm/TestMid0074.cs +++ b/src/MIDTesters.Core/Alarm/TestMid0074.cs @@ -14,8 +14,7 @@ public void Mid0074Revision1() string pack = @"00240074001 E851"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.ErrorCode); - Assert.AreEqual(4, mid.ErrorCode.Length); + Assert.AreEqual("E851", mid.ErrorCode); AssertEqualPackages(pack, mid); } @@ -27,8 +26,7 @@ public void Mid0074ByteRevision1() byte[] bytes = GetAsciiBytes(pack); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ErrorCode); - Assert.AreEqual(4, mid.ErrorCode.Length); + Assert.AreEqual("E851", mid.ErrorCode); AssertEqualPackages(bytes, mid); } @@ -39,8 +37,7 @@ public void Mid0074Revision2() string pack = @"00250074002 E8514"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.ErrorCode); - Assert.AreEqual(5, mid.ErrorCode.Length); + Assert.AreEqual("E8514", mid.ErrorCode); AssertEqualPackages(pack, mid); } @@ -52,8 +49,7 @@ public void Mid0074ByteRevision2() byte[] bytes = GetAsciiBytes(pack); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ErrorCode); - Assert.AreEqual(5, mid.ErrorCode.Length); + Assert.AreEqual("E8514", mid.ErrorCode); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/Alarm/TestMid0076.cs b/src/MIDTesters.Core/Alarm/TestMid0076.cs index 7baa6704..adf4c4df 100644 --- a/src/MIDTesters.Core/Alarm/TestMid0076.cs +++ b/src/MIDTesters.Core/Alarm/TestMid0076.cs @@ -1,5 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.Alarm; +using System; namespace MIDTesters.Alarm { @@ -14,11 +16,11 @@ public void Mid0076Revision1() string pack = @"00560076001 01102E851031041052017-01-25:10:20:20"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.AlarmStatus); - Assert.IsNotNull(mid.ErrorCode); - Assert.IsNotNull(mid.ControllerReadyStatus); - Assert.IsNotNull(mid.ToolReadyStatus); - Assert.IsNotNull(mid.Time); + Assert.IsTrue(mid.AlarmStatus); + Assert.AreEqual("E851", mid.ErrorCode); + Assert.IsTrue(mid.ControllerReadyStatus); + Assert.IsTrue(mid.ToolReadyStatus); + Assert.AreEqual(new DateTime(2017, 1, 25, 10, 20, 20), mid.Time); AssertEqualPackages(pack, mid); } @@ -30,11 +32,11 @@ public void Mid0076ByteRevision1() byte[] bytes = GetAsciiBytes(pack); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.AlarmStatus); - Assert.IsNotNull(mid.ErrorCode); - Assert.IsNotNull(mid.ControllerReadyStatus); - Assert.IsNotNull(mid.ToolReadyStatus); - Assert.IsNotNull(mid.Time); + Assert.IsTrue(mid.AlarmStatus); + Assert.AreEqual("E851", mid.ErrorCode); + Assert.IsTrue(mid.ControllerReadyStatus); + Assert.IsTrue(mid.ToolReadyStatus); + Assert.AreEqual(new DateTime(2017, 1, 25, 10, 20, 20), mid.Time); AssertEqualPackages(bytes, mid); } @@ -45,11 +47,11 @@ public void Mid0076Revision2() string pack = @"00570076002 01102 E851031041052017-01-25:10:20:20"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.AlarmStatus); - Assert.IsNotNull(mid.ErrorCode); - Assert.IsNotNull(mid.ControllerReadyStatus); - Assert.IsNotNull(mid.ToolReadyStatus); - Assert.IsNotNull(mid.Time); + Assert.IsTrue(mid.AlarmStatus); + Assert.AreEqual(" E851", mid.ErrorCode); + Assert.IsTrue(mid.ControllerReadyStatus); + Assert.IsTrue(mid.ToolReadyStatus); + Assert.AreEqual(new DateTime(2017, 1, 25, 10, 20, 20), mid.Time); AssertEqualPackages(pack, mid); } @@ -61,11 +63,11 @@ public void Mid0076ByteRevision2() byte[] bytes = GetAsciiBytes(pack); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.AlarmStatus); - Assert.IsNotNull(mid.ErrorCode); - Assert.IsNotNull(mid.ControllerReadyStatus); - Assert.IsNotNull(mid.ToolReadyStatus); - Assert.IsNotNull(mid.Time); + Assert.IsTrue(mid.AlarmStatus); + Assert.AreEqual(" E851", mid.ErrorCode); + Assert.IsTrue(mid.ControllerReadyStatus); + Assert.IsTrue(mid.ToolReadyStatus); + Assert.AreEqual(new DateTime(2017, 1, 25, 10, 20, 20), mid.Time); AssertEqualPackages(bytes, mid); } @@ -76,12 +78,12 @@ public void Mid0076Revision3() string pack = @"00600076003 01102 E851031041052017-01-25:10:20:20061"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.AlarmStatus); - Assert.IsNotNull(mid.ErrorCode); - Assert.IsNotNull(mid.ControllerReadyStatus); - Assert.IsNotNull(mid.ToolReadyStatus); - Assert.IsNotNull(mid.Time); - Assert.IsNotNull(mid.ToolHealth); + Assert.IsTrue(mid.AlarmStatus); + Assert.AreEqual(" E851", mid.ErrorCode); + Assert.IsTrue(mid.ControllerReadyStatus); + Assert.IsTrue(mid.ToolReadyStatus); + Assert.AreEqual(new DateTime(2017, 1, 25, 10, 20, 20), mid.Time); + Assert.AreEqual(ToolHealth.Ok, mid.ToolHealth); AssertEqualPackages(pack, mid); } @@ -93,12 +95,12 @@ public void Mid0076ByteRevision3() byte[] bytes = GetAsciiBytes(pack); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.AlarmStatus); - Assert.IsNotNull(mid.ErrorCode); - Assert.IsNotNull(mid.ControllerReadyStatus); - Assert.IsNotNull(mid.ToolReadyStatus); - Assert.IsNotNull(mid.Time); - Assert.IsNotNull(mid.ToolHealth); + Assert.IsTrue(mid.AlarmStatus); + Assert.AreEqual(" E851", mid.ErrorCode); + Assert.IsTrue(mid.ControllerReadyStatus); + Assert.IsTrue(mid.ToolReadyStatus); + Assert.AreEqual(new DateTime(2017, 1, 25, 10, 20, 20), mid.Time); + Assert.AreEqual(ToolHealth.Ok, mid.ToolHealth); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/Alarm/TestMid1000.cs b/src/MIDTesters.Core/Alarm/TestMid1000.cs index 7bafbe3d..85a1459a 100644 --- a/src/MIDTesters.Core/Alarm/TestMid1000.cs +++ b/src/MIDTesters.Core/Alarm/TestMid1000.cs @@ -1,5 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter.Alarm; +using System; namespace MIDTesters.Alarm { @@ -14,10 +15,10 @@ public void Mid1000Revision1() string pack = "00911000001 ABCDE2017-12-01:20:12:4500201700009040000000ALARMTEXT017010010100000001"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.AlarmCode); - Assert.IsNotNull(mid.Time); - Assert.IsNotNull(mid.AlarmDataFields); + Assert.AreEqual("ABCDE", mid.AlarmCode); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.Time); Assert.AreEqual(2, mid.NumberOfDataFields); + Assert.AreEqual(2, mid.AlarmDataFields.Count); AssertEqualPackages(pack, mid); } @@ -29,10 +30,10 @@ public void Mid1000ByteRevision1() byte[] bytes = GetAsciiBytes(pack); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.AlarmCode); - Assert.IsNotNull(mid.Time); - Assert.IsNotNull(mid.AlarmDataFields); + Assert.AreEqual("ABCDE", mid.AlarmCode); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.Time); Assert.AreEqual(2, mid.NumberOfDataFields); + Assert.AreEqual(2, mid.AlarmDataFields.Count); AssertEqualPackages(bytes, mid); } } From 746b8aebeb74c735dc4bf9075344b7963654a873 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 22 Mar 2026 00:44:13 -0300 Subject: [PATCH 02/78] Updated application selector tests --- .../ApplicationSelector/TestMid0251.cs | 37 ++++++++++++++++--- .../ApplicationSelector/TestMid0254.cs | 22 +++++++++-- .../ApplicationSelector/TestMid0255.cs | 30 +++++++++++++-- 3 files changed, 75 insertions(+), 14 deletions(-) diff --git a/src/MIDTesters.Core/ApplicationSelector/TestMid0251.cs b/src/MIDTesters.Core/ApplicationSelector/TestMid0251.cs index 1523ce2e..ecd81c92 100644 --- a/src/MIDTesters.Core/ApplicationSelector/TestMid0251.cs +++ b/src/MIDTesters.Core/ApplicationSelector/TestMid0251.cs @@ -1,5 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter.ApplicationSelector; +using System.Collections.Generic; namespace MIDTesters.ApplicationSelector { @@ -15,9 +16,21 @@ public void Mid0251Revision1() var mid = _midInterpreter.Parse(package); Assert.IsTrue(mid.Header.NoAckFlag); - Assert.IsNotNull(mid.DeviceId); - Assert.IsNotNull(mid.NumberOfSockets); - Assert.IsNotNull(mid.SocketStatus); + Assert.AreEqual(50, mid.DeviceId); + Assert.AreEqual(10, mid.NumberOfSockets); + CollectionAssert.AreEqual(new List + { + false, + true, + false, + true, + true, + false, + true, + true, + true, + false + }, mid.SocketStatus); AssertEqualPackages(package, mid, true); } @@ -29,9 +42,21 @@ public void Mid0251ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.DeviceId); - Assert.IsNotNull(mid.NumberOfSockets); - Assert.IsNotNull(mid.SocketStatus); + Assert.AreEqual(50, mid.DeviceId); + Assert.AreEqual(10, mid.NumberOfSockets); + CollectionAssert.AreEqual(new List + { + false, + true, + false, + true, + true, + false, + true, + true, + true, + false + }, mid.SocketStatus); AssertEqualPackages(bytes, mid, true); } } diff --git a/src/MIDTesters.Core/ApplicationSelector/TestMid0254.cs b/src/MIDTesters.Core/ApplicationSelector/TestMid0254.cs index 5380f173..298b2e56 100644 --- a/src/MIDTesters.Core/ApplicationSelector/TestMid0254.cs +++ b/src/MIDTesters.Core/ApplicationSelector/TestMid0254.cs @@ -1,5 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.ApplicationSelector; +using System.Collections.Generic; namespace MIDTesters.ApplicationSelector { @@ -14,8 +16,10 @@ public void Mid0254Revision1() string package = "00340254 01110201221022"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.DeviceId); - Assert.IsNotNull(mid.GreenLights); + Assert.AreEqual(11, mid.DeviceId); + CollectionAssert.AreEqual( + new List { LightCommand.Off, LightCommand.Steady, LightCommand.Flashing, LightCommand.Flashing, LightCommand.Steady, LightCommand.Off, LightCommand.Flashing, LightCommand.Flashing }, + mid.GreenLights); AssertEqualPackages(package, mid, true); } @@ -27,8 +31,18 @@ public void Mid0254ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.DeviceId); - Assert.IsNotNull(mid.GreenLights); + Assert.AreEqual(11, mid.DeviceId); + CollectionAssert.AreEqual(new List + { + LightCommand.Off, + LightCommand.Steady, + LightCommand.Flashing, + LightCommand.Flashing, + LightCommand.Steady, + LightCommand.Off, + LightCommand.Flashing, + LightCommand.Flashing + }, mid.GreenLights); AssertEqualPackages(bytes, mid, true); } } diff --git a/src/MIDTesters.Core/ApplicationSelector/TestMid0255.cs b/src/MIDTesters.Core/ApplicationSelector/TestMid0255.cs index 3d0a277d..af3c831d 100644 --- a/src/MIDTesters.Core/ApplicationSelector/TestMid0255.cs +++ b/src/MIDTesters.Core/ApplicationSelector/TestMid0255.cs @@ -1,5 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.ApplicationSelector; +using System.Collections.Generic; namespace MIDTesters.ApplicationSelector { @@ -14,8 +16,18 @@ public void Mid0255Revision1() string package = "00340255 01510221112022"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.DeviceId); - Assert.IsNotNull(mid.RedLights); + Assert.AreEqual(51, mid.DeviceId); + CollectionAssert.AreEqual(new List + { + LightCommand.Flashing, + LightCommand.Steady, + LightCommand.Steady, + LightCommand.Steady, + LightCommand.Flashing, + LightCommand.Off, + LightCommand.Flashing, + LightCommand.Flashing + }, mid.RedLights); AssertEqualPackages(package, mid, true); } @@ -27,8 +39,18 @@ public void Mid0255ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.DeviceId); - Assert.IsNotNull(mid.RedLights); + Assert.AreEqual(51, mid.DeviceId); + CollectionAssert.AreEqual(new List + { + LightCommand.Flashing, + LightCommand.Steady, + LightCommand.Steady, + LightCommand.Steady, + LightCommand.Flashing, + LightCommand.Off, + LightCommand.Flashing, + LightCommand.Flashing + }, mid.RedLights); AssertEqualPackages(bytes, mid, true); } } From c2dded3a2fd51a7a5827f0ede9357854632ae9b1 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 29 Mar 2026 23:27:35 -0300 Subject: [PATCH 03/78] Updated application tool location system tests --- .../ApplicationToolLocationSystem/TestMid0262.cs | 4 ++-- .../ApplicationToolLocationSystem/TestMid0265.cs | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0262.cs b/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0262.cs index c3ef80ef..4d8339d0 100644 --- a/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0262.cs +++ b/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0262.cs @@ -14,7 +14,7 @@ public void Mid0262Revision1() string package = "00300262001 013200078D"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.ToolTagId); + Assert.AreEqual("3200078D", mid.ToolTagId); AssertEqualPackages(package, mid); } @@ -26,7 +26,7 @@ public void Mid0262ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ToolTagId); + Assert.AreEqual("3200078D", mid.ToolTagId); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0265.cs b/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0265.cs index 46d9669d..854b2817 100644 --- a/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0265.cs +++ b/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0265.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.ApplicationToolLocationSystem; namespace MIDTesters.ApplicationToolLocationSystem @@ -14,8 +15,8 @@ public void Mid0265Revision1() string package = "00340265001 013200078D0202"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.ToolTagId); - Assert.IsNotNull(mid.ToolStatus); + Assert.AreEqual("3200078D", mid.ToolTagId); + Assert.AreEqual(ToolStatus.Inoperable, mid.ToolStatus); AssertEqualPackages(package, mid); } @@ -27,8 +28,8 @@ public void Mid0265ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ToolTagId); - Assert.IsNotNull(mid.ToolStatus); + Assert.AreEqual("3200078D", mid.ToolTagId); + Assert.AreEqual(ToolStatus.Inoperable, mid.ToolStatus); AssertEqualPackages(bytes, mid); } } From 2a83ce86a5d1263d880133d522a4cda2a5e178a7 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 29 Mar 2026 23:29:07 -0300 Subject: [PATCH 04/78] Updated automatic manual mode tests --- src/MIDTesters.Core/AutomaticManualMode/TestMid0401.cs | 4 ++-- src/MIDTesters.Core/AutomaticManualMode/TestMid0411.cs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/MIDTesters.Core/AutomaticManualMode/TestMid0401.cs b/src/MIDTesters.Core/AutomaticManualMode/TestMid0401.cs index 40c61900..307965ac 100644 --- a/src/MIDTesters.Core/AutomaticManualMode/TestMid0401.cs +++ b/src/MIDTesters.Core/AutomaticManualMode/TestMid0401.cs @@ -14,7 +14,7 @@ public void Mid0401Revision1() string package = "00210401 1 1"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.ManualAutomaticMode); + Assert.IsTrue(mid.ManualAutomaticMode); AssertEqualPackages(package, mid, true); } @@ -26,7 +26,7 @@ public void Mid0401ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ManualAutomaticMode); + Assert.IsTrue(mid.ManualAutomaticMode); AssertEqualPackages(bytes, mid, true); } } diff --git a/src/MIDTesters.Core/AutomaticManualMode/TestMid0411.cs b/src/MIDTesters.Core/AutomaticManualMode/TestMid0411.cs index 92e92db6..48abc4ac 100644 --- a/src/MIDTesters.Core/AutomaticManualMode/TestMid0411.cs +++ b/src/MIDTesters.Core/AutomaticManualMode/TestMid0411.cs @@ -14,8 +14,8 @@ public void Mid0411Revision1() string package = "00240411 0105"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.AutoDisableSetting); - Assert.IsNotNull(mid.CurrentBatch); + Assert.AreEqual(1, mid.AutoDisableSetting); + Assert.AreEqual(5, mid.CurrentBatch); AssertEqualPackages(package, mid, true); } @@ -27,8 +27,8 @@ public void Mid0411ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.AutoDisableSetting); - Assert.IsNotNull(mid.CurrentBatch); + Assert.AreEqual(1, mid.AutoDisableSetting); + Assert.AreEqual(5, mid.CurrentBatch); AssertEqualPackages(bytes, mid, true); } } From 41fbb235ee845b70d0748f9a6bb065ca93c4ab12 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 29 Mar 2026 23:34:21 -0300 Subject: [PATCH 05/78] Updated communication mid tests --- .../Communication/TestMid0001.cs | 4 +- .../Communication/TestMid0002.cs | 269 +++++++++--------- .../Communication/TestMid0004.cs | 17 +- .../Communication/TestMid0005.cs | 4 +- .../Communication/TestMid0006.cs | 16 +- .../Communication/TestMid0008.cs | 16 +- .../Communication/TestMid0009.cs | 16 +- 7 files changed, 172 insertions(+), 170 deletions(-) diff --git a/src/MIDTesters.Core/Communication/TestMid0001.cs b/src/MIDTesters.Core/Communication/TestMid0001.cs index 4d3d1f0f..fce6c921 100644 --- a/src/MIDTesters.Core/Communication/TestMid0001.cs +++ b/src/MIDTesters.Core/Communication/TestMid0001.cs @@ -37,7 +37,7 @@ public void Mid0001Revision7() var package = "00230001007 011"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.OptionalKeepAlive); + Assert.IsTrue(mid.OptionalKeepAlive); AssertEqualPackages(package, mid); } @@ -49,7 +49,7 @@ public void Mid0001ByteRevision7() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.OptionalKeepAlive); + Assert.IsTrue(mid.OptionalKeepAlive); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/Communication/TestMid0002.cs b/src/MIDTesters.Core/Communication/TestMid0002.cs index d3db436b..f4423b05 100644 --- a/src/MIDTesters.Core/Communication/TestMid0002.cs +++ b/src/MIDTesters.Core/Communication/TestMid0002.cs @@ -1,5 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter.Communication; +using OpenProtocolInterpreter; namespace MIDTesters.Communication { @@ -14,9 +15,9 @@ public void Mid0002Revision1() string pack = @"00570002001 010001020103Airbag1 "; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.ControllerName); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("Airbag1", mid.ControllerName.TrimEnd()); AssertEqualPackages(pack, mid); } @@ -28,9 +29,9 @@ public void Mid0002ByteRevision1() byte[] bytes = GetAsciiBytes(pack); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.ControllerName); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("Airbag1", mid.ControllerName.TrimEnd()); AssertEqualPackages(bytes, mid); } @@ -41,10 +42,10 @@ public void Mid0002Revision2() string pack = @"00620002002 010001020103Airbag1 04ACT"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.ControllerName); - Assert.IsNotNull(mid.SupplierCode); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("Airbag1", mid.ControllerName.TrimEnd()); + Assert.AreEqual("ACT", mid.SupplierCode); AssertEqualPackages(pack, mid); } @@ -56,10 +57,10 @@ public void Mid0002ByteRevision2() byte[] bytes = GetAsciiBytes(pack); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.ControllerName); - Assert.IsNotNull(mid.SupplierCode); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("Airbag1", mid.ControllerName.TrimEnd()); + Assert.AreEqual("ACT", mid.SupplierCode); AssertEqualPackages(bytes, mid); } @@ -70,13 +71,13 @@ public void Mid0002Revision3() string pack = @"01250002003 010001020103Airbag1 04ACT05OpenProtocolVersion06Version 19.0.0.0 07Version 01.0.0.0 "; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.ControllerName); - Assert.IsNotNull(mid.SupplierCode); - Assert.IsNotNull(mid.OpenProtocolVersion); - Assert.IsNotNull(mid.ControllerSoftwareVersion); - Assert.IsNotNull(mid.ToolSoftwareVersion); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("Airbag1", mid.ControllerName.TrimEnd()); + Assert.AreEqual("ACT", mid.SupplierCode); + Assert.AreEqual("OpenProtocolVersion", mid.OpenProtocolVersion); + Assert.AreEqual("Version 19.0.0.0", mid.ControllerSoftwareVersion.TrimEnd()); + Assert.AreEqual("Version 01.0.0.0", mid.ToolSoftwareVersion.TrimEnd()); AssertEqualPackages(pack, mid); } @@ -88,13 +89,13 @@ public void Mid0002ByteRevision3() byte[] bytes = GetAsciiBytes(pack); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.ControllerName); - Assert.IsNotNull(mid.SupplierCode); - Assert.IsNotNull(mid.OpenProtocolVersion); - Assert.IsNotNull(mid.ControllerSoftwareVersion); - Assert.IsNotNull(mid.ToolSoftwareVersion); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("Airbag1", mid.ControllerName.TrimEnd()); + Assert.AreEqual("ACT", mid.SupplierCode); + Assert.AreEqual("OpenProtocolVersion", mid.OpenProtocolVersion); + Assert.AreEqual("Version 19.0.0.0", mid.ControllerSoftwareVersion.TrimEnd()); + Assert.AreEqual("Version 01.0.0.0", mid.ToolSoftwareVersion.TrimEnd()); AssertEqualPackages(bytes, mid); } @@ -105,15 +106,15 @@ public void Mid0002Revision4() string pack = @"01630002004 010001020103Airbag1 04ACT05OpenProtocolVersion06Version 19.0.0.0 07Version 01.0.0.0 08RBUType 09Serial "; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.ControllerName); - Assert.IsNotNull(mid.SupplierCode); - Assert.IsNotNull(mid.OpenProtocolVersion); - Assert.IsNotNull(mid.ControllerSoftwareVersion); - Assert.IsNotNull(mid.ToolSoftwareVersion); - Assert.IsNotNull(mid.RBUType); - Assert.IsNotNull(mid.ControllerSerialNumber); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("Airbag1", mid.ControllerName.TrimEnd()); + Assert.AreEqual("ACT", mid.SupplierCode); + Assert.AreEqual("OpenProtocolVersion", mid.OpenProtocolVersion); + Assert.AreEqual("Version 19.0.0.0", mid.ControllerSoftwareVersion.TrimEnd()); + Assert.AreEqual("Version 01.0.0.0", mid.ToolSoftwareVersion.TrimEnd()); + Assert.AreEqual("RBUType", mid.RBUType.TrimEnd()); + Assert.AreEqual("Serial", mid.ControllerSerialNumber.TrimEnd()); AssertEqualPackages(pack, mid); } @@ -125,15 +126,15 @@ public void Mid0002ByteRevision4() byte[] bytes = GetAsciiBytes(pack); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.ControllerName); - Assert.IsNotNull(mid.SupplierCode); - Assert.IsNotNull(mid.OpenProtocolVersion); - Assert.IsNotNull(mid.ControllerSoftwareVersion); - Assert.IsNotNull(mid.ToolSoftwareVersion); - Assert.IsNotNull(mid.RBUType); - Assert.IsNotNull(mid.ControllerSerialNumber); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("Airbag1", mid.ControllerName.TrimEnd()); + Assert.AreEqual("ACT", mid.SupplierCode); + Assert.AreEqual("OpenProtocolVersion", mid.OpenProtocolVersion); + Assert.AreEqual("Version 19.0.0.0", mid.ControllerSoftwareVersion.TrimEnd()); + Assert.AreEqual("Version 01.0.0.0", mid.ToolSoftwareVersion.TrimEnd()); + Assert.AreEqual("RBUType", mid.RBUType.TrimEnd()); + Assert.AreEqual("Serial", mid.ControllerSerialNumber.TrimEnd()); AssertEqualPackages(bytes, mid); } @@ -144,17 +145,17 @@ public void Mid0002Revision5() string pack = @"01730002005 010001020103Airbag1 04ACT05OpenProtocolVersion06Version 19.0.0.0 07Version 01.0.0.0 08RBUType 09Serial 1000211002"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.ControllerName); - Assert.IsNotNull(mid.SupplierCode); - Assert.IsNotNull(mid.OpenProtocolVersion); - Assert.IsNotNull(mid.ControllerSoftwareVersion); - Assert.IsNotNull(mid.ToolSoftwareVersion); - Assert.IsNotNull(mid.RBUType); - Assert.IsNotNull(mid.ControllerSerialNumber); - Assert.IsNotNull(mid.SystemType); - Assert.IsNotNull(mid.SystemSubType); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("Airbag1", mid.ControllerName.TrimEnd()); + Assert.AreEqual("ACT", mid.SupplierCode); + Assert.AreEqual("OpenProtocolVersion", mid.OpenProtocolVersion); + Assert.AreEqual("Version 19.0.0.0", mid.ControllerSoftwareVersion.TrimEnd()); + Assert.AreEqual("Version 01.0.0.0", mid.ToolSoftwareVersion.TrimEnd()); + Assert.AreEqual("RBUType", mid.RBUType.TrimEnd()); + Assert.AreEqual("Serial", mid.ControllerSerialNumber.TrimEnd()); + Assert.AreEqual(SystemType.PowerMacs4000, mid.SystemType); + Assert.AreEqual(SystemSubType.SystemRunningPresses, mid.SystemSubType); AssertEqualPackages(pack, mid); } @@ -166,17 +167,17 @@ public void Mid0002ByteRevision5() byte[] bytes = GetAsciiBytes(pack); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.ControllerName); - Assert.IsNotNull(mid.SupplierCode); - Assert.IsNotNull(mid.OpenProtocolVersion); - Assert.IsNotNull(mid.ControllerSoftwareVersion); - Assert.IsNotNull(mid.ToolSoftwareVersion); - Assert.IsNotNull(mid.RBUType); - Assert.IsNotNull(mid.ControllerSerialNumber); - Assert.IsNotNull(mid.SystemType); - Assert.IsNotNull(mid.SystemSubType); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("Airbag1", mid.ControllerName.TrimEnd()); + Assert.AreEqual("ACT", mid.SupplierCode); + Assert.AreEqual("OpenProtocolVersion", mid.OpenProtocolVersion); + Assert.AreEqual("Version 19.0.0.0", mid.ControllerSoftwareVersion.TrimEnd()); + Assert.AreEqual("Version 01.0.0.0", mid.ToolSoftwareVersion.TrimEnd()); + Assert.AreEqual("RBUType", mid.RBUType.TrimEnd()); + Assert.AreEqual("Serial", mid.ControllerSerialNumber.TrimEnd()); + Assert.AreEqual(SystemType.PowerMacs4000, mid.SystemType); + Assert.AreEqual(SystemSubType.SystemRunningPresses, mid.SystemSubType); AssertEqualPackages(bytes, mid); } @@ -187,22 +188,22 @@ public void Mid0002Revision6() string pack = @"02210002006 010001020103Airbag1 04ACT05OpenProtocolVersion06Version 19.0.0.0 07Version 01.0.0.0 08RBUType 09Serial 100021100212013114429496729515Station Or Cell Name 16A"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.ControllerName); - Assert.IsNotNull(mid.SupplierCode); - Assert.IsNotNull(mid.OpenProtocolVersion); - Assert.IsNotNull(mid.ControllerSoftwareVersion); - Assert.IsNotNull(mid.ToolSoftwareVersion); - Assert.IsNotNull(mid.RBUType); - Assert.IsNotNull(mid.ControllerSerialNumber); - Assert.IsNotNull(mid.SystemType); - Assert.IsNotNull(mid.SystemSubType); - Assert.IsNotNull(mid.SequenceNumberSupport); - Assert.IsNotNull(mid.LinkingHandlingSupport); - Assert.AreNotEqual(0, mid.StationCellId); - Assert.IsNotNull(mid.StationCellName); - Assert.IsNotNull(mid.ClientId); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("Airbag1", mid.ControllerName.TrimEnd()); + Assert.AreEqual("ACT", mid.SupplierCode); + Assert.AreEqual("OpenProtocolVersion", mid.OpenProtocolVersion); + Assert.AreEqual("Version 19.0.0.0", mid.ControllerSoftwareVersion.TrimEnd()); + Assert.AreEqual("Version 01.0.0.0", mid.ToolSoftwareVersion.TrimEnd()); + Assert.AreEqual("RBUType", mid.RBUType.TrimEnd()); + Assert.AreEqual("Serial", mid.ControllerSerialNumber.TrimEnd()); + Assert.AreEqual(SystemType.PowerMacs4000, mid.SystemType); + Assert.AreEqual(SystemSubType.SystemRunningPresses, mid.SystemSubType); + Assert.IsFalse(mid.SequenceNumberSupport); + Assert.IsTrue(mid.LinkingHandlingSupport); + Assert.AreEqual(4294967295L, mid.StationCellId); + Assert.AreEqual("Station Or Cell Name", mid.StationCellName.TrimEnd()); + Assert.AreEqual("A", mid.ClientId); AssertEqualPackages(pack, mid); } @@ -214,22 +215,22 @@ public void Mid0002ByteRevision6() byte[] bytes = GetAsciiBytes(pack); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.ControllerName); - Assert.IsNotNull(mid.SupplierCode); - Assert.IsNotNull(mid.OpenProtocolVersion); - Assert.IsNotNull(mid.ControllerSoftwareVersion); - Assert.IsNotNull(mid.ToolSoftwareVersion); - Assert.IsNotNull(mid.RBUType); - Assert.IsNotNull(mid.ControllerSerialNumber); - Assert.IsNotNull(mid.SystemType); - Assert.IsNotNull(mid.SystemSubType); - Assert.IsNotNull(mid.SequenceNumberSupport); - Assert.IsNotNull(mid.LinkingHandlingSupport); - Assert.AreNotEqual(0, mid.StationCellId); - Assert.IsNotNull(mid.StationCellName); - Assert.IsNotNull(mid.ClientId); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("Airbag1", mid.ControllerName.TrimEnd()); + Assert.AreEqual("ACT", mid.SupplierCode); + Assert.AreEqual("OpenProtocolVersion", mid.OpenProtocolVersion); + Assert.AreEqual("Version 19.0.0.0", mid.ControllerSoftwareVersion.TrimEnd()); + Assert.AreEqual("Version 01.0.0.0", mid.ToolSoftwareVersion.TrimEnd()); + Assert.AreEqual("RBUType", mid.RBUType.TrimEnd()); + Assert.AreEqual("Serial", mid.ControllerSerialNumber.TrimEnd()); + Assert.AreEqual(SystemType.PowerMacs4000, mid.SystemType); + Assert.AreEqual(SystemSubType.SystemRunningPresses, mid.SystemSubType); + Assert.IsFalse(mid.SequenceNumberSupport); + Assert.IsTrue(mid.LinkingHandlingSupport); + Assert.AreEqual(4294967295L, mid.StationCellId); + Assert.AreEqual("Station Or Cell Name", mid.StationCellName.TrimEnd()); + Assert.AreEqual("A", mid.ClientId); AssertEqualPackages(bytes, mid); } @@ -240,23 +241,23 @@ public void Mid0002Revision7() string pack = @"02240002007 010001020103Airbag1 04ACT05OpenProtocolVersion06Version 19.0.0.0 07Version 01.0.0.0 08RBUType 09Serial 100021100212013114429496729515Station Or Cell Name 16A171"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.ControllerName); - Assert.IsNotNull(mid.SupplierCode); - Assert.IsNotNull(mid.OpenProtocolVersion); - Assert.IsNotNull(mid.ControllerSoftwareVersion); - Assert.IsNotNull(mid.ToolSoftwareVersion); - Assert.IsNotNull(mid.RBUType); - Assert.IsNotNull(mid.ControllerSerialNumber); - Assert.IsNotNull(mid.SystemType); - Assert.IsNotNull(mid.SystemSubType); - Assert.IsNotNull(mid.SequenceNumberSupport); - Assert.IsNotNull(mid.LinkingHandlingSupport); - Assert.AreNotEqual(0, mid.StationCellId); - Assert.IsNotNull(mid.StationCellName); - Assert.IsNotNull(mid.ClientId); - Assert.IsNotNull(mid.OptionalKeepAlive); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("Airbag1", mid.ControllerName.TrimEnd()); + Assert.AreEqual("ACT", mid.SupplierCode); + Assert.AreEqual("OpenProtocolVersion", mid.OpenProtocolVersion); + Assert.AreEqual("Version 19.0.0.0", mid.ControllerSoftwareVersion.TrimEnd()); + Assert.AreEqual("Version 01.0.0.0", mid.ToolSoftwareVersion.TrimEnd()); + Assert.AreEqual("RBUType", mid.RBUType.TrimEnd()); + Assert.AreEqual("Serial", mid.ControllerSerialNumber.TrimEnd()); + Assert.AreEqual(SystemType.PowerMacs4000, mid.SystemType); + Assert.AreEqual(SystemSubType.SystemRunningPresses, mid.SystemSubType); + Assert.IsFalse(mid.SequenceNumberSupport); + Assert.IsTrue(mid.LinkingHandlingSupport); + Assert.AreEqual(4294967295L, mid.StationCellId); + Assert.AreEqual("Station Or Cell Name", mid.StationCellName.TrimEnd()); + Assert.AreEqual("A", mid.ClientId); + Assert.IsTrue(mid.OptionalKeepAlive); AssertEqualPackages(pack, mid); } @@ -268,23 +269,23 @@ public void Mid0002ByteRevision7() byte[] bytes = GetAsciiBytes(pack); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.ControllerName); - Assert.IsNotNull(mid.SupplierCode); - Assert.IsNotNull(mid.OpenProtocolVersion); - Assert.IsNotNull(mid.ControllerSoftwareVersion); - Assert.IsNotNull(mid.ToolSoftwareVersion); - Assert.IsNotNull(mid.RBUType); - Assert.IsNotNull(mid.ControllerSerialNumber); - Assert.IsNotNull(mid.SystemType); - Assert.IsNotNull(mid.SystemSubType); - Assert.IsNotNull(mid.SequenceNumberSupport); - Assert.IsNotNull(mid.LinkingHandlingSupport); - Assert.AreNotEqual(0, mid.StationCellId); - Assert.IsNotNull(mid.StationCellName); - Assert.IsNotNull(mid.ClientId); - Assert.IsNotNull(mid.OptionalKeepAlive); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("Airbag1", mid.ControllerName.TrimEnd()); + Assert.AreEqual("ACT", mid.SupplierCode); + Assert.AreEqual("OpenProtocolVersion", mid.OpenProtocolVersion); + Assert.AreEqual("Version 19.0.0.0", mid.ControllerSoftwareVersion.TrimEnd()); + Assert.AreEqual("Version 01.0.0.0", mid.ToolSoftwareVersion.TrimEnd()); + Assert.AreEqual("RBUType", mid.RBUType.TrimEnd()); + Assert.AreEqual("Serial", mid.ControllerSerialNumber.TrimEnd()); + Assert.AreEqual(SystemType.PowerMacs4000, mid.SystemType); + Assert.AreEqual(SystemSubType.SystemRunningPresses, mid.SystemSubType); + Assert.IsFalse(mid.SequenceNumberSupport); + Assert.IsTrue(mid.LinkingHandlingSupport); + Assert.AreEqual(4294967295L, mid.StationCellId); + Assert.AreEqual("Station Or Cell Name", mid.StationCellName.TrimEnd()); + Assert.AreEqual("A", mid.ClientId); + Assert.IsTrue(mid.OptionalKeepAlive); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/Communication/TestMid0004.cs b/src/MIDTesters.Core/Communication/TestMid0004.cs index 800a81b0..9cf6d9f7 100644 --- a/src/MIDTesters.Core/Communication/TestMid0004.cs +++ b/src/MIDTesters.Core/Communication/TestMid0004.cs @@ -1,5 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter.Communication; +using OpenProtocolInterpreter; namespace MIDTesters.Communication { @@ -14,8 +15,8 @@ public void Mid0004Revision1() string pack = @"00260004 001802"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.FailedMid); - Assert.IsNotNull(mid.ErrorCode); + Assert.AreEqual(18, mid.FailedMid); + Assert.AreEqual(Error.ParameterSetIdNotPresent, mid.ErrorCode); AssertEqualPackages(pack, mid, true); } @@ -27,8 +28,8 @@ public void Mid0004ByteRevision1() byte[] bytes = GetAsciiBytes(pack); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.FailedMid); - Assert.IsNotNull(mid.ErrorCode); + Assert.AreEqual(18, mid.FailedMid); + Assert.AreEqual(Error.ParameterSetIdNotPresent, mid.ErrorCode); AssertEqualPackages(bytes, mid, true); } @@ -39,8 +40,8 @@ public void Mid0004Revision2() string pack = @"00270004002 0018021"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.FailedMid); - Assert.IsNotNull(mid.ErrorCode); + Assert.AreEqual(18, mid.FailedMid); + Assert.AreEqual(Error.JobNotRunning, mid.ErrorCode); AssertEqualPackages(pack, mid); } @@ -52,8 +53,8 @@ public void Mid0004ByteRevision2() byte[] bytes = GetAsciiBytes(pack); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.FailedMid); - Assert.IsNotNull(mid.ErrorCode); + Assert.AreEqual(18, mid.FailedMid); + Assert.AreEqual(Error.JobNotRunning, mid.ErrorCode); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/Communication/TestMid0005.cs b/src/MIDTesters.Core/Communication/TestMid0005.cs index b294268d..a011ec80 100644 --- a/src/MIDTesters.Core/Communication/TestMid0005.cs +++ b/src/MIDTesters.Core/Communication/TestMid0005.cs @@ -14,7 +14,7 @@ public void Mid0005Revision1() string pack = @"00240005 0018"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.MidAccepted); + Assert.AreEqual(18, mid.MidAccepted); AssertEqualPackages(pack, mid, true); } @@ -26,7 +26,7 @@ public void Mid0005ByteRevision1() byte[] bytes = GetAsciiBytes(pack); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.MidAccepted); + Assert.AreEqual(18, mid.MidAccepted); AssertEqualPackages(bytes, mid, true); } } diff --git a/src/MIDTesters.Core/Communication/TestMid0006.cs b/src/MIDTesters.Core/Communication/TestMid0006.cs index c100bee8..0afe23d5 100644 --- a/src/MIDTesters.Core/Communication/TestMid0006.cs +++ b/src/MIDTesters.Core/Communication/TestMid0006.cs @@ -15,10 +15,10 @@ public void Mid0006Revision1() var mid = _midInterpreter.Parse(pack); Assert.AreEqual(typeof(Mid0006), mid.GetType()); - Assert.IsNotNull(mid.RequestedMid); - Assert.IsNotNull(mid.WantedRevision); - Assert.IsNotNull(mid.ExtraDataLength); - Assert.IsNotNull(mid.ExtraData); + Assert.AreEqual(18, mid.RequestedMid); + Assert.AreEqual(2, mid.WantedRevision); + Assert.AreEqual(14, mid.ExtraDataLength); + Assert.AreEqual("lengthequals14", mid.ExtraData); AssertEqualPackages(pack, mid, true); } @@ -31,10 +31,10 @@ public void Mid0006ByteRevision1() var mid = _midInterpreter.Parse(bytes); Assert.AreEqual(typeof(Mid0006), mid.GetType()); - Assert.IsNotNull(mid.RequestedMid); - Assert.IsNotNull(mid.WantedRevision); - Assert.IsNotNull(mid.ExtraDataLength); - Assert.IsNotNull(mid.ExtraData); + Assert.AreEqual(18, mid.RequestedMid); + Assert.AreEqual(2, mid.WantedRevision); + Assert.AreEqual(14, mid.ExtraDataLength); + Assert.AreEqual("lengthequals14", mid.ExtraData); AssertEqualPackages(bytes, mid, true); } } diff --git a/src/MIDTesters.Core/Communication/TestMid0008.cs b/src/MIDTesters.Core/Communication/TestMid0008.cs index 89439739..a9e7dd79 100644 --- a/src/MIDTesters.Core/Communication/TestMid0008.cs +++ b/src/MIDTesters.Core/Communication/TestMid0008.cs @@ -14,10 +14,10 @@ public void Mid0008Revision1() string pack = @"00430008 002200214lengthequals14"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.SubscriptionMid); - Assert.IsNotNull(mid.WantedRevision); - Assert.IsNotNull(mid.ExtraDataLength); - Assert.IsNotNull(mid.ExtraData); + Assert.AreEqual("0022", mid.SubscriptionMid); + Assert.AreEqual(2, mid.WantedRevision); + Assert.AreEqual(14, mid.ExtraDataLength); + Assert.AreEqual("lengthequals14", mid.ExtraData); AssertEqualPackages(pack, mid, true); } @@ -29,10 +29,10 @@ public void Mid0008ByteRevision1() byte[] bytes = GetAsciiBytes(pack); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.SubscriptionMid); - Assert.IsNotNull(mid.WantedRevision); - Assert.IsNotNull(mid.ExtraDataLength); - Assert.IsNotNull(mid.ExtraData); + Assert.AreEqual("0022", mid.SubscriptionMid); + Assert.AreEqual(2, mid.WantedRevision); + Assert.AreEqual(14, mid.ExtraDataLength); + Assert.AreEqual("lengthequals14", mid.ExtraData); AssertEqualPackages(bytes, mid, true); } } diff --git a/src/MIDTesters.Core/Communication/TestMid0009.cs b/src/MIDTesters.Core/Communication/TestMid0009.cs index f346e6a6..cbfd542c 100644 --- a/src/MIDTesters.Core/Communication/TestMid0009.cs +++ b/src/MIDTesters.Core/Communication/TestMid0009.cs @@ -14,10 +14,10 @@ public void Mid0009Revision1() string pack = @"00430009 002200214lengthequals14"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.UnsubscriptionMid); - Assert.IsNotNull(mid.ExtraDataRevision); - Assert.IsNotNull(mid.ExtraDataLength); - Assert.IsNotNull(mid.ExtraData); + Assert.AreEqual("0022", mid.UnsubscriptionMid); + Assert.AreEqual(2, mid.ExtraDataRevision); + Assert.AreEqual(14, mid.ExtraDataLength); + Assert.AreEqual("lengthequals14", mid.ExtraData); AssertEqualPackages(pack, mid, true); } @@ -29,10 +29,10 @@ public void Mid0009ByteRevision1() byte[] bytes = GetAsciiBytes(pack); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.UnsubscriptionMid); - Assert.IsNotNull(mid.ExtraDataRevision); - Assert.IsNotNull(mid.ExtraDataLength); - Assert.IsNotNull(mid.ExtraData); + Assert.AreEqual("0022", mid.UnsubscriptionMid); + Assert.AreEqual(2, mid.ExtraDataRevision); + Assert.AreEqual(14, mid.ExtraDataLength); + Assert.AreEqual("lengthequals14", mid.ExtraData); AssertEqualPackages(bytes, mid, true); } } From a82178bfe05af558f06a8b29003e5df2ec7ad1c7 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 29 Mar 2026 23:34:34 -0300 Subject: [PATCH 06/78] Updated IOInterface tests --- .../IOInterface/TestMid0200.cs | 41 ++++++++++--------- .../IOInterface/TestMid0211.cs | 32 +++++++-------- .../IOInterface/TestMid0214.cs | 4 +- .../IOInterface/TestMid0215.cs | 26 ++++-------- .../IOInterface/TestMid0216.cs | 5 ++- .../IOInterface/TestMid0217.cs | 9 ++-- .../IOInterface/TestMid0219.cs | 5 ++- .../IOInterface/TestMid0220.cs | 5 ++- .../IOInterface/TestMid0221.cs | 9 ++-- .../IOInterface/TestMid0223.cs | 5 ++- .../IOInterface/TestMid0224.cs | 5 ++- .../IOInterface/TestMid0225.cs | 5 ++- 12 files changed, 75 insertions(+), 76 deletions(-) diff --git a/src/MIDTesters.Core/IOInterface/TestMid0200.cs b/src/MIDTesters.Core/IOInterface/TestMid0200.cs index 465c14ec..c99f3af7 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0200.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0200.cs @@ -1,5 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter.IOInterface; +using OpenProtocolInterpreter; namespace MIDTesters.IOInterface { @@ -14,16 +15,16 @@ public void Mid0200Revision1() string package = "00300200 1231231230"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.StatusRelayOne); - Assert.IsNotNull(mid.StatusRelayTwo); - Assert.IsNotNull(mid.StatusRelayThree); - Assert.IsNotNull(mid.StatusRelayFour); - Assert.IsNotNull(mid.StatusRelayFive); - Assert.IsNotNull(mid.StatusRelaySix); - Assert.IsNotNull(mid.StatusRelaySeven); - Assert.IsNotNull(mid.StatusRelayEight); - Assert.IsNotNull(mid.StatusRelayNine); - Assert.IsNotNull(mid.StatusRelayTen); + Assert.AreEqual(RelayStatus.On, mid.StatusRelayOne); + Assert.AreEqual(RelayStatus.Flashing, mid.StatusRelayTwo); + Assert.AreEqual(RelayStatus.KeepCurrentStatus, mid.StatusRelayThree); + Assert.AreEqual(RelayStatus.On, mid.StatusRelayFour); + Assert.AreEqual(RelayStatus.Flashing, mid.StatusRelayFive); + Assert.AreEqual(RelayStatus.KeepCurrentStatus, mid.StatusRelaySix); + Assert.AreEqual(RelayStatus.On, mid.StatusRelaySeven); + Assert.AreEqual(RelayStatus.Flashing, mid.StatusRelayEight); + Assert.AreEqual(RelayStatus.KeepCurrentStatus, mid.StatusRelayNine); + Assert.AreEqual(RelayStatus.Off, mid.StatusRelayTen); AssertEqualPackages(package, mid, true); } @@ -35,16 +36,16 @@ public void Mid0200ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.StatusRelayOne); - Assert.IsNotNull(mid.StatusRelayTwo); - Assert.IsNotNull(mid.StatusRelayThree); - Assert.IsNotNull(mid.StatusRelayFour); - Assert.IsNotNull(mid.StatusRelayFive); - Assert.IsNotNull(mid.StatusRelaySix); - Assert.IsNotNull(mid.StatusRelaySeven); - Assert.IsNotNull(mid.StatusRelayEight); - Assert.IsNotNull(mid.StatusRelayNine); - Assert.IsNotNull(mid.StatusRelayTen); + Assert.AreEqual(RelayStatus.On, mid.StatusRelayOne); + Assert.AreEqual(RelayStatus.Flashing, mid.StatusRelayTwo); + Assert.AreEqual(RelayStatus.KeepCurrentStatus, mid.StatusRelayThree); + Assert.AreEqual(RelayStatus.On, mid.StatusRelayFour); + Assert.AreEqual(RelayStatus.Flashing, mid.StatusRelayFive); + Assert.AreEqual(RelayStatus.KeepCurrentStatus, mid.StatusRelaySix); + Assert.AreEqual(RelayStatus.On, mid.StatusRelaySeven); + Assert.AreEqual(RelayStatus.Flashing, mid.StatusRelayEight); + Assert.AreEqual(RelayStatus.KeepCurrentStatus, mid.StatusRelayNine); + Assert.AreEqual(RelayStatus.Off, mid.StatusRelayTen); AssertEqualPackages(bytes, mid, true); } } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0211.cs b/src/MIDTesters.Core/IOInterface/TestMid0211.cs index 8acea432..e9b705de 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0211.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0211.cs @@ -15,14 +15,14 @@ public void Mid0211Revision1() var mid = _midInterpreter.Parse(package); Assert.IsTrue(mid.Header.NoAckFlag); - Assert.IsNotNull(mid.StatusDigInOne); - Assert.IsNotNull(mid.StatusDigInTwo); - Assert.IsNotNull(mid.StatusDigInThree); - Assert.IsNotNull(mid.StatusDigInFour); - Assert.IsNotNull(mid.StatusDigInFive); - Assert.IsNotNull(mid.StatusDigInSix); - Assert.IsNotNull(mid.StatusDigInSeven); - Assert.IsNotNull(mid.StatusDigInEight); + Assert.IsTrue(mid.StatusDigInOne); + Assert.IsFalse(mid.StatusDigInTwo); + Assert.IsTrue(mid.StatusDigInThree); + Assert.IsFalse(mid.StatusDigInFour); + Assert.IsTrue(mid.StatusDigInFive); + Assert.IsFalse(mid.StatusDigInSix); + Assert.IsTrue(mid.StatusDigInSeven); + Assert.IsTrue(mid.StatusDigInEight); AssertEqualPackages(package, mid, true); } @@ -35,14 +35,14 @@ public void Mid0211ByteRevision1() var mid = _midInterpreter.Parse(bytes); Assert.IsTrue(mid.Header.NoAckFlag); - Assert.IsNotNull(mid.StatusDigInOne); - Assert.IsNotNull(mid.StatusDigInTwo); - Assert.IsNotNull(mid.StatusDigInThree); - Assert.IsNotNull(mid.StatusDigInFour); - Assert.IsNotNull(mid.StatusDigInFive); - Assert.IsNotNull(mid.StatusDigInSix); - Assert.IsNotNull(mid.StatusDigInSeven); - Assert.IsNotNull(mid.StatusDigInEight); + Assert.IsTrue(mid.StatusDigInOne); + Assert.IsFalse(mid.StatusDigInTwo); + Assert.IsTrue(mid.StatusDigInThree); + Assert.IsFalse(mid.StatusDigInFour); + Assert.IsTrue(mid.StatusDigInFive); + Assert.IsFalse(mid.StatusDigInSix); + Assert.IsTrue(mid.StatusDigInSeven); + Assert.IsTrue(mid.StatusDigInEight); AssertEqualPackages(bytes, mid, true); } } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0214.cs b/src/MIDTesters.Core/IOInterface/TestMid0214.cs index 4cd06126..6e2632c5 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0214.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0214.cs @@ -14,7 +14,7 @@ public void Mid0214AllRevisions() string package = "00220214002 10"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.DeviceNumber); + Assert.AreEqual(10, mid.DeviceNumber); AssertEqualPackages(package, mid); } @@ -26,7 +26,7 @@ public void Mid0214ByteAllRevisions() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.DeviceNumber); + Assert.AreEqual(10, mid.DeviceNumber); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0215.cs b/src/MIDTesters.Core/IOInterface/TestMid0215.cs index 0707194d..f34ba063 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0215.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0215.cs @@ -14,9 +14,7 @@ public void Mid0215Revision1() string package = "00920215001 010302001000210031004010012000300140010300110020003100410051006100700080"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.IODeviceId); - Assert.IsNotNull(mid.Relays); - Assert.IsNotNull(mid.DigitalInputs); + Assert.AreEqual(3, mid.IODeviceId); Assert.AreEqual(8, mid.Relays.Count); Assert.AreEqual(8, mid.DigitalInputs.Count); @@ -31,9 +29,7 @@ public void Mid0215ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.IODeviceId); - Assert.IsNotNull(mid.Relays); - Assert.IsNotNull(mid.DigitalInputs); + Assert.AreEqual(3, mid.IODeviceId); Assert.AreEqual(8, mid.Relays.Count); Assert.AreEqual(8, mid.DigitalInputs.Count); @@ -47,14 +43,11 @@ public void Mid0215Revision2() string package = "00920215002 010302070300100021003100400051006000710407050011002000310041005100610070"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.IODeviceId); - Assert.IsNotNull(mid.Relays); - Assert.IsNotNull(mid.DigitalInputs); + Assert.AreEqual(3, mid.IODeviceId); Assert.AreEqual(7, mid.Relays.Count); Assert.AreEqual(7, mid.DigitalInputs.Count); - - Assert.IsNotNull(mid.NumberOfDigitalInputs); - Assert.IsNotNull(mid.NumberOfRelays); + Assert.AreEqual(7, mid.NumberOfRelays); + Assert.AreEqual(7, mid.NumberOfDigitalInputs); AssertEqualPackages(package, mid); } @@ -67,14 +60,11 @@ public void Mid0215ByteRevision2() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.IODeviceId); - Assert.IsNotNull(mid.Relays); - Assert.IsNotNull(mid.DigitalInputs); + Assert.AreEqual(3, mid.IODeviceId); Assert.AreEqual(7, mid.Relays.Count); Assert.AreEqual(7, mid.DigitalInputs.Count); - - Assert.IsNotNull(mid.NumberOfDigitalInputs); - Assert.IsNotNull(mid.NumberOfRelays); + Assert.AreEqual(7, mid.NumberOfRelays); + Assert.AreEqual(7, mid.NumberOfDigitalInputs); AssertEqualPackages(bytes, mid); } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0216.cs b/src/MIDTesters.Core/IOInterface/TestMid0216.cs index 083ac9fa..6dfc475b 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0216.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0216.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.IOInterface; namespace MIDTesters.IOInterface @@ -14,7 +15,7 @@ public void Mid0216Revision1() string package = "00230216 1 026"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.RelayNumber); + Assert.AreEqual(RelayNumber.ToolRunning, mid.RelayNumber); AssertEqualPackages(package, mid, true); } @@ -26,7 +27,7 @@ public void Mid0216ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.RelayNumber); + Assert.AreEqual(RelayNumber.ToolRunning, mid.RelayNumber); AssertEqualPackages(bytes, mid, true); } } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0217.cs b/src/MIDTesters.Core/IOInterface/TestMid0217.cs index 7859b1ce..9deb8db6 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0217.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0217.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.IOInterface; namespace MIDTesters.IOInterface @@ -14,8 +15,8 @@ public void Mid0217Revision1() string package = "00280217 1 01026021"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.RelayNumber); - Assert.IsNotNull(mid.RelayStatus); + Assert.AreEqual(RelayNumber.ToolRunning, mid.RelayNumber); + Assert.IsTrue(mid.RelayStatus); AssertEqualPackages(package, mid, true); } @@ -27,8 +28,8 @@ public void Mid0217ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.RelayNumber); - Assert.IsNotNull(mid.RelayStatus); + Assert.AreEqual(RelayNumber.ToolRunning, mid.RelayNumber); + Assert.IsTrue(mid.RelayStatus); AssertEqualPackages(bytes, mid, true); } } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0219.cs b/src/MIDTesters.Core/IOInterface/TestMid0219.cs index cc0af51d..8b2d7dae 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0219.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0219.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.IOInterface; namespace MIDTesters.IOInterface @@ -14,7 +15,7 @@ public void Mid0219Revision1() string package = "00230219 102"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.RelayNumber); + Assert.AreEqual(RelayNumber.ExternalControlled10, mid.RelayNumber); AssertEqualPackages(package, mid, true); } @@ -26,7 +27,7 @@ public void Mid0219ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.RelayNumber); + Assert.AreEqual(RelayNumber.ExternalControlled10, mid.RelayNumber); AssertEqualPackages(bytes, mid, true); } } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0220.cs b/src/MIDTesters.Core/IOInterface/TestMid0220.cs index 964bc829..2844bcdd 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0220.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0220.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.IOInterface; namespace MIDTesters.IOInterface @@ -14,7 +15,7 @@ public void Mid0220Revision1() string package = "00230220 120"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.DigitalInputNumber); + Assert.AreEqual(DigitalInputNumber.ForcedCcwOnce, mid.DigitalInputNumber); AssertEqualPackages(package, mid, true); } @@ -26,7 +27,7 @@ public void Mid0220ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.DigitalInputNumber); + Assert.AreEqual(DigitalInputNumber.ForcedCcwOnce, mid.DigitalInputNumber); AssertEqualPackages(bytes, mid, true); } } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0221.cs b/src/MIDTesters.Core/IOInterface/TestMid0221.cs index 23d0ffab..610c5bbb 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0221.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0221.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.IOInterface; namespace MIDTesters.IOInterface @@ -14,8 +15,8 @@ public void Mid0221Revision1() string package = "00280221 01120021"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.DigitalInputNumber); - Assert.IsNotNull(mid.DigitalInputStatus); + Assert.AreEqual(DigitalInputNumber.ForcedCcwOnce, mid.DigitalInputNumber); + Assert.IsTrue(mid.DigitalInputStatus); AssertEqualPackages(package, mid, true); } @@ -27,8 +28,8 @@ public void Mid0221ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.DigitalInputNumber); - Assert.IsNotNull(mid.DigitalInputStatus); + Assert.AreEqual(DigitalInputNumber.ForcedCcwOnce, mid.DigitalInputNumber); + Assert.IsTrue(mid.DigitalInputStatus); AssertEqualPackages(bytes, mid, true); } } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0223.cs b/src/MIDTesters.Core/IOInterface/TestMid0223.cs index fb5fa02a..b8c3e6fd 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0223.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0223.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.IOInterface; namespace MIDTesters.IOInterface @@ -14,7 +15,7 @@ public void Mid0223Revision1() string package = "00230223 066"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.DigitalInputNumber); + Assert.AreEqual(DigitalInputNumber.IdCard, mid.DigitalInputNumber); AssertEqualPackages(package, mid, true); } @@ -26,7 +27,7 @@ public void Mid0223ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.DigitalInputNumber); + Assert.AreEqual(DigitalInputNumber.IdCard, mid.DigitalInputNumber); AssertEqualPackages(bytes, mid, true); } } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0224.cs b/src/MIDTesters.Core/IOInterface/TestMid0224.cs index 2e8434f6..cc50ee7e 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0224.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0224.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.IOInterface; namespace MIDTesters.IOInterface @@ -14,7 +15,7 @@ public void Mid0224Revision1() string package = "00230224 065"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.DigitalInputNumber); + Assert.AreEqual(DigitalInputNumber.ClickWrench4, mid.DigitalInputNumber); AssertEqualPackages(package, mid, true); } @@ -26,7 +27,7 @@ public void Mid0224ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.DigitalInputNumber); + Assert.AreEqual(DigitalInputNumber.ClickWrench4, mid.DigitalInputNumber); AssertEqualPackages(bytes, mid, true); } } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0225.cs b/src/MIDTesters.Core/IOInterface/TestMid0225.cs index 7bb705aa..bb4e4fee 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0225.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0225.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.IOInterface; namespace MIDTesters.IOInterface @@ -14,7 +15,7 @@ public void Mid0225Revision1() string package = "00230225 055"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.DigitalInputNumber); + Assert.AreEqual(DigitalInputNumber.EndOfCycle, mid.DigitalInputNumber); AssertEqualPackages(package, mid, true); } @@ -26,7 +27,7 @@ public void Mid0225ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.DigitalInputNumber); + Assert.AreEqual(DigitalInputNumber.EndOfCycle, mid.DigitalInputNumber); AssertEqualPackages(bytes, mid, true); } } From 1d551923a02d46f9ceed8a1b98c90c60bb008668 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 29 Mar 2026 23:34:45 -0300 Subject: [PATCH 07/78] Updated Job tests --- .../Job/Advanced/TestMid0129.cs | 8 +- .../Job/Advanced/TestMid0130.cs | 4 +- .../Job/Advanced/TestMid0140.cs | 121 ++++---- src/MIDTesters.Core/Job/TestMid0031.cs | 16 +- src/MIDTesters.Core/Job/TestMid0032.cs | 16 +- src/MIDTesters.Core/Job/TestMid0033.cs | 261 +++++++++--------- src/MIDTesters.Core/Job/TestMid0035.cs | 186 +++++++------ src/MIDTesters.Core/Job/TestMid0039.cs | 8 +- 8 files changed, 308 insertions(+), 312 deletions(-) diff --git a/src/MIDTesters.Core/Job/Advanced/TestMid0129.cs b/src/MIDTesters.Core/Job/Advanced/TestMid0129.cs index 6a9e84ea..a18a466b 100644 --- a/src/MIDTesters.Core/Job/Advanced/TestMid0129.cs +++ b/src/MIDTesters.Core/Job/Advanced/TestMid0129.cs @@ -37,8 +37,8 @@ public void Mid0129Revision2() string package = "00290129002 010302123"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.ParameterSetId); + Assert.AreEqual(3, mid.ChannelId); + Assert.AreEqual(123, mid.ParameterSetId); AssertEqualPackages(package, mid); } @@ -50,8 +50,8 @@ public void Mid0129ByteRevision2() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.ParameterSetId); + Assert.AreEqual(3, mid.ChannelId); + Assert.AreEqual(123, mid.ParameterSetId); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/Job/Advanced/TestMid0130.cs b/src/MIDTesters.Core/Job/Advanced/TestMid0130.cs index c6788966..13c0cd81 100644 --- a/src/MIDTesters.Core/Job/Advanced/TestMid0130.cs +++ b/src/MIDTesters.Core/Job/Advanced/TestMid0130.cs @@ -14,7 +14,7 @@ public void Mid0130Revision1() string package = "00210130 1"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.JobOffStatus); + Assert.IsTrue(mid.JobOffStatus); AssertEqualPackages(package, mid, true); } @@ -26,7 +26,7 @@ public void Mid0130ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.JobOffStatus); + Assert.IsTrue(mid.JobOffStatus); AssertEqualPackages(bytes, mid, true); } } diff --git a/src/MIDTesters.Core/Job/Advanced/TestMid0140.cs b/src/MIDTesters.Core/Job/Advanced/TestMid0140.cs index 42ba4dec..4383307b 100644 --- a/src/MIDTesters.Core/Job/Advanced/TestMid0140.cs +++ b/src/MIDTesters.Core/Job/Advanced/TestMid0140.cs @@ -17,20 +17,19 @@ public void Mid0140Revision1() var mid = _midInterpreter.Parse(package); Assert.AreEqual(typeof(Mid0140), mid.GetType()); - Assert.AreNotEqual(0, mid.JobId); - Assert.AreNotEqual(string.Empty, mid.JobName); - Assert.AreNotEqual(0, mid.NumberOfParameterSets); - Assert.IsNotNull(mid.JobList); - Assert.AreNotEqual(0, mid.JobList.Count); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual("Job 1", mid.JobName.TrimEnd()); + Assert.AreEqual(2, mid.NumberOfParameterSets); + Assert.AreEqual(2, mid.JobList.Count); Assert.AreEqual(ForcedOrder.ForcedOrder, mid.ForcedOrder); Assert.IsTrue(mid.LockAtJobDone); Assert.AreEqual(ToolLoosening.EnableOnlyOnNokTightenings, mid.ToolLoosening); Assert.IsTrue(mid.RepeatJob); Assert.AreEqual(BatchMode.BothOkAndNok, mid.BatchMode); Assert.IsTrue(mid.DecrementBatchAtOkLoosening); - Assert.AreNotEqual(0, mid.MaxTimeForFirstTightening); - Assert.AreNotEqual(0, mid.MaxTimeToCompleteJob); - Assert.AreNotEqual(0, mid.DisplayResultAtAutoSelect); + Assert.AreEqual(100, mid.MaxTimeForFirstTightening); + Assert.AreEqual(10000, mid.MaxTimeToCompleteJob); + Assert.AreEqual(90, mid.DisplayResultAtAutoSelect); Assert.IsTrue(mid.UsingLineControl); Assert.AreEqual(IdentifierPart.Other, mid.IdentifierResultPart); Assert.IsTrue(mid.ResultOfNonTightenings); @@ -48,20 +47,19 @@ public void Mid0140ByteRevision1() var mid = _midInterpreter.Parse(bytes); Assert.AreEqual(typeof(Mid0140), mid.GetType()); - Assert.AreNotEqual(0, mid.JobId); - Assert.AreNotEqual(string.Empty, mid.JobName); - Assert.AreNotEqual(0, mid.NumberOfParameterSets); - Assert.IsNotNull(mid.JobList); - Assert.AreNotEqual(0, mid.JobList.Count); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual("Job 1", mid.JobName.TrimEnd()); + Assert.AreEqual(2, mid.NumberOfParameterSets); + Assert.AreEqual(2, mid.JobList.Count); Assert.AreEqual(ForcedOrder.ForcedOrder, mid.ForcedOrder); Assert.IsTrue(mid.LockAtJobDone); Assert.AreEqual(ToolLoosening.EnableOnlyOnNokTightenings, mid.ToolLoosening); Assert.IsTrue(mid.RepeatJob); Assert.AreEqual(BatchMode.BothOkAndNok, mid.BatchMode); Assert.IsTrue(mid.DecrementBatchAtOkLoosening); - Assert.AreNotEqual(0, mid.MaxTimeForFirstTightening); - Assert.AreNotEqual(0, mid.MaxTimeToCompleteJob); - Assert.AreNotEqual(0, mid.DisplayResultAtAutoSelect); + Assert.AreEqual(100, mid.MaxTimeForFirstTightening); + Assert.AreEqual(10000, mid.MaxTimeToCompleteJob); + Assert.AreEqual(90, mid.DisplayResultAtAutoSelect); Assert.IsTrue(mid.UsingLineControl); Assert.AreEqual(IdentifierPart.Other, mid.IdentifierResultPart); Assert.IsTrue(mid.ResultOfNonTightenings); @@ -78,26 +76,25 @@ public void Mid0140Revision2() var mid = _midInterpreter.Parse(package); Assert.AreEqual(typeof(Mid0140), mid.GetType()); - Assert.AreNotEqual(0, mid.JobId); - Assert.AreNotEqual(string.Empty, mid.JobName); - Assert.AreNotEqual(0, mid.NumberOfParameterSets); - Assert.IsNotNull(mid.JobList); - Assert.AreNotEqual(0, mid.JobList.Count); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual("Job 1", mid.JobName.TrimEnd()); + Assert.AreEqual(2, mid.NumberOfParameterSets); + Assert.AreEqual(2, mid.JobList.Count); Assert.AreEqual(ForcedOrder.ForcedOrder, mid.ForcedOrder); Assert.IsTrue(mid.LockAtJobDone); Assert.AreEqual(ToolLoosening.EnableOnlyOnNokTightenings, mid.ToolLoosening); Assert.IsTrue(mid.RepeatJob); Assert.AreEqual(BatchMode.BothOkAndNok, mid.BatchMode); Assert.IsTrue(mid.DecrementBatchAtOkLoosening); - Assert.AreNotEqual(0, mid.MaxTimeForFirstTightening); - Assert.AreNotEqual(0, mid.MaxTimeToCompleteJob); - Assert.AreNotEqual(0, mid.DisplayResultAtAutoSelect); + Assert.AreEqual(100, mid.MaxTimeForFirstTightening); + Assert.AreEqual(10000, mid.MaxTimeToCompleteJob); + Assert.AreEqual(90, mid.DisplayResultAtAutoSelect); Assert.IsTrue(mid.UsingLineControl); Assert.AreEqual(IdentifierPart.Other, mid.IdentifierResultPart); Assert.IsTrue(mid.ResultOfNonTightenings); Assert.IsTrue(mid.ResetAllIdentifiersAtJobDone); Assert.AreEqual(Reserved.G, mid.Reserved); - Assert.AreNotEqual(0, mid.JobSequenceNumber); + Assert.AreEqual(120, mid.JobSequenceNumber); AssertEqualPackages(package, mid); } @@ -110,9 +107,9 @@ public void Mid0140ByteRevision2() var mid = _midInterpreter.Parse(bytes); Assert.AreEqual(typeof(Mid0140), mid.GetType()); - Assert.AreNotEqual(0, mid.JobId); - Assert.AreNotEqual(string.Empty, mid.JobName); - Assert.AreNotEqual(0, mid.NumberOfParameterSets); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual("Job 1", mid.JobName.TrimEnd()); + Assert.AreEqual(2, mid.NumberOfParameterSets); AssertEqualPackages(bytes, mid); } @@ -124,23 +121,22 @@ public void Mid0140Revision3() var mid = _midInterpreter.Parse(package); Assert.AreEqual(typeof(Mid0140), mid.GetType()); - Assert.AreNotEqual(0, mid.JobId); - Assert.AreNotEqual(string.Empty, mid.JobName); - Assert.AreNotEqual(0, mid.NumberOfParameterSets); - Assert.IsNotNull(mid.JobList); - Assert.AreNotEqual(0, mid.JobList.Count); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual("Job 1", mid.JobName.TrimEnd()); + Assert.AreEqual(2, mid.NumberOfParameterSets); + Assert.AreEqual(2, mid.JobList.Count); Assert.AreEqual(ForcedOrder.ForcedOrder, mid.ForcedOrder); Assert.IsTrue(mid.LockAtJobDone); Assert.IsTrue(mid.RepeatJob); - Assert.AreNotEqual(0, mid.MaxTimeForFirstTightening); - Assert.AreNotEqual(0, mid.MaxTimeToCompleteJob); - Assert.AreNotEqual(0, mid.DisplayResultAtAutoSelect); + Assert.AreEqual(100, mid.MaxTimeForFirstTightening); + Assert.AreEqual(900, mid.MaxTimeToCompleteJob); + Assert.AreEqual(90, mid.DisplayResultAtAutoSelect); Assert.IsTrue(mid.UsingLineControl); Assert.AreEqual(IdentifierPart.Other, mid.IdentifierResultPart); Assert.IsTrue(mid.ResultOfNonTightenings); Assert.IsTrue(mid.ResetAllIdentifiersAtJobDone); Assert.AreEqual(Reserved.G, mid.Reserved); - Assert.AreNotEqual(0, mid.JobSequenceNumber); + Assert.AreEqual(5000, mid.JobSequenceNumber); AssertEqualPackages(package, mid); } @@ -153,23 +149,22 @@ public void Mid0140ByteRevision3() var mid = _midInterpreter.Parse(bytes); Assert.AreEqual(typeof(Mid0140), mid.GetType()); - Assert.AreNotEqual(0, mid.JobId); - Assert.AreNotEqual(string.Empty, mid.JobName); - Assert.AreNotEqual(0, mid.NumberOfParameterSets); - Assert.IsNotNull(mid.JobList); - Assert.AreNotEqual(0, mid.JobList.Count); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual("Job 1", mid.JobName.TrimEnd()); + Assert.AreEqual(2, mid.NumberOfParameterSets); + Assert.AreEqual(2, mid.JobList.Count); Assert.AreEqual(ForcedOrder.ForcedOrder, mid.ForcedOrder); Assert.IsTrue(mid.LockAtJobDone); Assert.IsTrue(mid.RepeatJob); - Assert.AreNotEqual(0, mid.MaxTimeForFirstTightening); - Assert.AreNotEqual(0, mid.MaxTimeToCompleteJob); - Assert.AreNotEqual(0, mid.DisplayResultAtAutoSelect); + Assert.AreEqual(100, mid.MaxTimeForFirstTightening); + Assert.AreEqual(900, mid.MaxTimeToCompleteJob); + Assert.AreEqual(90, mid.DisplayResultAtAutoSelect); Assert.IsTrue(mid.UsingLineControl); Assert.AreEqual(IdentifierPart.Other, mid.IdentifierResultPart); Assert.IsTrue(mid.ResultOfNonTightenings); Assert.IsTrue(mid.ResetAllIdentifiersAtJobDone); Assert.AreEqual(Reserved.G, mid.Reserved); - Assert.AreNotEqual(0, mid.JobSequenceNumber); + Assert.AreEqual(5000, mid.JobSequenceNumber); AssertEqualPackages(bytes, mid); } @@ -181,23 +176,22 @@ public void Mid0140Revision4() var mid = _midInterpreter.Parse(package); Assert.AreEqual(typeof(Mid0140), mid.GetType()); - Assert.AreNotEqual(0, mid.JobId); - Assert.AreNotEqual(string.Empty, mid.JobName); - Assert.AreNotEqual(0, mid.NumberOfParameterSets); - Assert.IsNotNull(mid.JobList); - Assert.AreNotEqual(0, mid.JobList.Count); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual("Job 1", mid.JobName.TrimEnd()); + Assert.AreEqual(2, mid.NumberOfParameterSets); + Assert.AreEqual(2, mid.JobList.Count); Assert.AreEqual(ForcedOrder.ForcedOrder, mid.ForcedOrder); Assert.IsTrue(mid.LockAtJobDone); Assert.IsTrue(mid.RepeatJob); - Assert.AreNotEqual(0, mid.MaxTimeForFirstTightening); - Assert.AreNotEqual(0, mid.MaxTimeToCompleteJob); - Assert.AreNotEqual(0, mid.DisplayResultAtAutoSelect); + Assert.AreEqual(100, mid.MaxTimeForFirstTightening); + Assert.AreEqual(900, mid.MaxTimeToCompleteJob); + Assert.AreEqual(90, mid.DisplayResultAtAutoSelect); Assert.IsTrue(mid.UsingLineControl); Assert.AreEqual(IdentifierPart.Other, mid.IdentifierResultPart); Assert.IsTrue(mid.ResultOfNonTightenings); Assert.IsTrue(mid.ResetAllIdentifiersAtJobDone); Assert.AreEqual(Reserved.G, mid.Reserved); - Assert.AreNotEqual(0, mid.JobSequenceNumber); + Assert.AreEqual(5000, mid.JobSequenceNumber); AssertEqualPackages(package, mid); } @@ -210,23 +204,22 @@ public void Mid0140ByteRevision4() var mid = _midInterpreter.Parse(bytes); Assert.AreEqual(typeof(Mid0140), mid.GetType()); - Assert.AreNotEqual(0, mid.JobId); - Assert.AreNotEqual(string.Empty, mid.JobName); - Assert.AreNotEqual(0, mid.NumberOfParameterSets); - Assert.IsNotNull(mid.JobList); - Assert.AreNotEqual(0, mid.JobList.Count); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual("Job 1", mid.JobName.TrimEnd()); + Assert.AreEqual(2, mid.NumberOfParameterSets); + Assert.AreEqual(2, mid.JobList.Count); Assert.AreEqual(ForcedOrder.ForcedOrder, mid.ForcedOrder); Assert.IsTrue(mid.LockAtJobDone); Assert.IsTrue(mid.RepeatJob); - Assert.AreNotEqual(0, mid.MaxTimeForFirstTightening); - Assert.AreNotEqual(0, mid.MaxTimeToCompleteJob); - Assert.AreNotEqual(0, mid.DisplayResultAtAutoSelect); + Assert.AreEqual(100, mid.MaxTimeForFirstTightening); + Assert.AreEqual(900, mid.MaxTimeToCompleteJob); + Assert.AreEqual(90, mid.DisplayResultAtAutoSelect); Assert.IsTrue(mid.UsingLineControl); Assert.AreEqual(IdentifierPart.Other, mid.IdentifierResultPart); Assert.IsTrue(mid.ResultOfNonTightenings); Assert.IsTrue(mid.ResetAllIdentifiersAtJobDone); Assert.AreEqual(Reserved.G, mid.Reserved); - Assert.AreNotEqual(0, mid.JobSequenceNumber); + Assert.AreEqual(5000, mid.JobSequenceNumber); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/Job/TestMid0031.cs b/src/MIDTesters.Core/Job/TestMid0031.cs index 2dc90c5f..509c6356 100644 --- a/src/MIDTesters.Core/Job/TestMid0031.cs +++ b/src/MIDTesters.Core/Job/TestMid0031.cs @@ -14,8 +14,8 @@ public void Mid0031Revision1() string package = "00300031001 0401020304"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.TotalJobs); - Assert.IsNotNull(mid.JobIds); + Assert.AreEqual(4, mid.TotalJobs); + Assert.AreEqual(4, mid.JobIds.Count); AssertEqualPackages(package, mid); } @@ -27,8 +27,8 @@ public void Mid0031ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.TotalJobs); - Assert.IsNotNull(mid.JobIds); + Assert.AreEqual(4, mid.TotalJobs); + Assert.AreEqual(4, mid.JobIds.Count); AssertEqualPackages(bytes, mid); } @@ -39,8 +39,8 @@ public void Mid0031Revision2() string package = "00640031002 00100001000200030004000500100015001100120019"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.TotalJobs); - Assert.IsNotNull(mid.JobIds); + Assert.AreEqual(10, mid.TotalJobs); + Assert.AreEqual(10, mid.JobIds.Count); AssertEqualPackages(package, mid); } @@ -52,8 +52,8 @@ public void Mid0031ByteRevision2() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.TotalJobs); - Assert.IsNotNull(mid.JobIds); + Assert.AreEqual(10, mid.TotalJobs); + Assert.AreEqual(10, mid.JobIds.Count); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/Job/TestMid0032.cs b/src/MIDTesters.Core/Job/TestMid0032.cs index 9238f527..84783363 100644 --- a/src/MIDTesters.Core/Job/TestMid0032.cs +++ b/src/MIDTesters.Core/Job/TestMid0032.cs @@ -14,7 +14,7 @@ public void Mid0032Revision1() string package = "00220032001 04"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.JobId); + Assert.AreEqual(4, mid.JobId); AssertEqualPackages(package, mid); } @@ -26,7 +26,7 @@ public void Mid0032ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.JobId); + Assert.AreEqual(4, mid.JobId); AssertEqualPackages(bytes, mid); } @@ -37,7 +37,7 @@ public void Mid0032Revision2() string package = "00240032002 0002"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.JobId); + Assert.AreEqual(2, mid.JobId); AssertEqualPackages(package, mid); } @@ -49,7 +49,7 @@ public void Mid0032ByteRevision2() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.JobId); + Assert.AreEqual(2, mid.JobId); AssertEqualPackages(bytes, mid); } @@ -60,7 +60,7 @@ public void Mid0032Revision3() string package = "00240032003 0003"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.JobId); + Assert.AreEqual(3, mid.JobId); AssertEqualPackages(package, mid); } @@ -72,7 +72,7 @@ public void Mid0032ByteRevision3() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.JobId); + Assert.AreEqual(3, mid.JobId); AssertEqualPackages(bytes, mid); } @@ -83,7 +83,7 @@ public void Mid0032Revision4() string package = "00240032004 0003"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.JobId); + Assert.AreEqual(3, mid.JobId); AssertEqualPackages(package, mid); } @@ -95,7 +95,7 @@ public void Mid0032ByteRevision4() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.JobId); + Assert.AreEqual(3, mid.JobId); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/Job/TestMid0033.cs b/src/MIDTesters.Core/Job/TestMid0033.cs index 30f02b58..3e005b5e 100644 --- a/src/MIDTesters.Core/Job/TestMid0033.cs +++ b/src/MIDTesters.Core/Job/TestMid0033.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.Job; namespace MIDTesters.Job @@ -14,19 +15,19 @@ public void Mid0033Revision1() string package = "01150033001 010402My Job 4 031045000057000406107108109110211112021315:011:1:02;11:015:1:02;"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.JobName); - Assert.IsNotNull(mid.ForcedOrder); - Assert.IsNotNull(mid.MaxTimeForFirstTightening); - Assert.IsNotNull(mid.MaxTimeToCompleteJob); - Assert.IsNotNull(mid.JobBatchMode); - Assert.IsNotNull(mid.LockAtJobDone); - Assert.IsNotNull(mid.UseLineControl); - Assert.IsNotNull(mid.RepeatJob); - Assert.IsNotNull(mid.ToolLoosening); - Assert.IsNotNull(mid.Reserved); - Assert.IsNotNull(mid.NumberOfParameterSets); - Assert.IsNotNull(mid.ParameterSetList); + Assert.AreEqual(4, mid.JobId); + Assert.AreEqual("My Job 4", mid.JobName.TrimEnd()); + Assert.AreEqual(ForcedOrder.ForcedOrder, mid.ForcedOrder); + Assert.AreEqual(5000, mid.MaxTimeForFirstTightening); + Assert.AreEqual(70004, mid.MaxTimeToCompleteJob); + Assert.AreEqual(JobBatchMode.OkAndNokTightenings, mid.JobBatchMode); + Assert.IsTrue(mid.LockAtJobDone); + Assert.IsTrue(mid.UseLineControl); + Assert.IsTrue(mid.RepeatJob); + Assert.AreEqual(ToolLoosening.EnableOnlyOnNokTightenings, mid.ToolLoosening); + Assert.AreEqual(Reserved.G, mid.Reserved); + Assert.AreEqual(2, mid.NumberOfParameterSets); + Assert.AreEqual(2, mid.ParameterSetList.Count); AssertEqualPackages(package, mid); } @@ -38,19 +39,19 @@ public void Mid0033ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.JobName); - Assert.IsNotNull(mid.ForcedOrder); - Assert.IsNotNull(mid.MaxTimeForFirstTightening); - Assert.IsNotNull(mid.MaxTimeToCompleteJob); - Assert.IsNotNull(mid.JobBatchMode); - Assert.IsNotNull(mid.LockAtJobDone); - Assert.IsNotNull(mid.UseLineControl); - Assert.IsNotNull(mid.RepeatJob); - Assert.IsNotNull(mid.ToolLoosening); - Assert.IsNotNull(mid.Reserved); - Assert.IsNotNull(mid.NumberOfParameterSets); - Assert.IsNotNull(mid.ParameterSetList); + Assert.AreEqual(4, mid.JobId); + Assert.AreEqual("My Job 4", mid.JobName.TrimEnd()); + Assert.AreEqual(ForcedOrder.ForcedOrder, mid.ForcedOrder); + Assert.AreEqual(5000, mid.MaxTimeForFirstTightening); + Assert.AreEqual(70004, mid.MaxTimeToCompleteJob); + Assert.AreEqual(JobBatchMode.OkAndNokTightenings, mid.JobBatchMode); + Assert.IsTrue(mid.LockAtJobDone); + Assert.IsTrue(mid.UseLineControl); + Assert.IsTrue(mid.RepeatJob); + Assert.AreEqual(ToolLoosening.EnableOnlyOnNokTightenings, mid.ToolLoosening); + Assert.AreEqual(Reserved.G, mid.Reserved); + Assert.AreEqual(2, mid.NumberOfParameterSets); + Assert.AreEqual(2, mid.ParameterSetList.Count); AssertEqualPackages(bytes, mid); } @@ -61,19 +62,19 @@ public void Mid0033Revision2() string package = "01170033002 01000402My Job 4 031045000057000406107108109110211112021315:011:1:02;11:015:1:02;"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.JobName); - Assert.IsNotNull(mid.ForcedOrder); - Assert.IsNotNull(mid.MaxTimeForFirstTightening); - Assert.IsNotNull(mid.MaxTimeToCompleteJob); - Assert.IsNotNull(mid.JobBatchMode); - Assert.IsNotNull(mid.LockAtJobDone); - Assert.IsNotNull(mid.UseLineControl); - Assert.IsNotNull(mid.RepeatJob); - Assert.IsNotNull(mid.ToolLoosening); - Assert.IsNotNull(mid.Reserved); - Assert.IsNotNull(mid.NumberOfParameterSets); - Assert.IsNotNull(mid.ParameterSetList); + Assert.AreEqual(4, mid.JobId); + Assert.AreEqual("My Job 4", mid.JobName.TrimEnd()); + Assert.AreEqual(ForcedOrder.ForcedOrder, mid.ForcedOrder); + Assert.AreEqual(5000, mid.MaxTimeForFirstTightening); + Assert.AreEqual(70004, mid.MaxTimeToCompleteJob); + Assert.AreEqual(JobBatchMode.OkAndNokTightenings, mid.JobBatchMode); + Assert.IsTrue(mid.LockAtJobDone); + Assert.IsTrue(mid.UseLineControl); + Assert.IsTrue(mid.RepeatJob); + Assert.AreEqual(ToolLoosening.EnableOnlyOnNokTightenings, mid.ToolLoosening); + Assert.AreEqual(Reserved.G, mid.Reserved); + Assert.AreEqual(2, mid.NumberOfParameterSets); + Assert.AreEqual(2, mid.ParameterSetList.Count); AssertEqualPackages(package, mid); } @@ -85,19 +86,19 @@ public void Mid0033ByteRevision2() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.JobName); - Assert.IsNotNull(mid.ForcedOrder); - Assert.IsNotNull(mid.MaxTimeForFirstTightening); - Assert.IsNotNull(mid.MaxTimeToCompleteJob); - Assert.IsNotNull(mid.JobBatchMode); - Assert.IsNotNull(mid.LockAtJobDone); - Assert.IsNotNull(mid.UseLineControl); - Assert.IsNotNull(mid.RepeatJob); - Assert.IsNotNull(mid.ToolLoosening); - Assert.IsNotNull(mid.Reserved); - Assert.IsNotNull(mid.NumberOfParameterSets); - Assert.IsNotNull(mid.ParameterSetList); + Assert.AreEqual(4, mid.JobId); + Assert.AreEqual("My Job 4", mid.JobName.TrimEnd()); + Assert.AreEqual(ForcedOrder.ForcedOrder, mid.ForcedOrder); + Assert.AreEqual(5000, mid.MaxTimeForFirstTightening); + Assert.AreEqual(70004, mid.MaxTimeToCompleteJob); + Assert.AreEqual(JobBatchMode.OkAndNokTightenings, mid.JobBatchMode); + Assert.IsTrue(mid.LockAtJobDone); + Assert.IsTrue(mid.UseLineControl); + Assert.IsTrue(mid.RepeatJob); + Assert.AreEqual(ToolLoosening.EnableOnlyOnNokTightenings, mid.ToolLoosening); + Assert.AreEqual(Reserved.G, mid.Reserved); + Assert.AreEqual(2, mid.NumberOfParameterSets); + Assert.AreEqual(2, mid.ParameterSetList.Count); AssertEqualPackages(bytes, mid); } @@ -108,19 +109,19 @@ public void Mid0033Revision3() string package = "01810033003 01000402My Job 4 031045000057000406107108109110211112021315:011:1:02:02:Job Step 1 Name :03;11:015:1:02:01:Job Step 2 Name :05;"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.JobName); - Assert.IsNotNull(mid.ForcedOrder); - Assert.IsNotNull(mid.MaxTimeForFirstTightening); - Assert.IsNotNull(mid.MaxTimeToCompleteJob); - Assert.IsNotNull(mid.JobBatchMode); - Assert.IsNotNull(mid.LockAtJobDone); - Assert.IsNotNull(mid.UseLineControl); - Assert.IsNotNull(mid.RepeatJob); - Assert.IsNotNull(mid.ToolLoosening); - Assert.IsNotNull(mid.Reserved); - Assert.IsNotNull(mid.NumberOfParameterSets); - Assert.IsNotNull(mid.ParameterSetList); + Assert.AreEqual(4, mid.JobId); + Assert.AreEqual("My Job 4", mid.JobName.TrimEnd()); + Assert.AreEqual(ForcedOrder.ForcedOrder, mid.ForcedOrder); + Assert.AreEqual(5000, mid.MaxTimeForFirstTightening); + Assert.AreEqual(70004, mid.MaxTimeToCompleteJob); + Assert.AreEqual(JobBatchMode.OkAndNokTightenings, mid.JobBatchMode); + Assert.IsTrue(mid.LockAtJobDone); + Assert.IsTrue(mid.UseLineControl); + Assert.IsTrue(mid.RepeatJob); + Assert.AreEqual(ToolLoosening.EnableOnlyOnNokTightenings, mid.ToolLoosening); + Assert.AreEqual(Reserved.G, mid.Reserved); + Assert.AreEqual(2, mid.NumberOfParameterSets); + Assert.AreEqual(2, mid.ParameterSetList.Count); AssertEqualPackages(package, mid); } @@ -132,19 +133,19 @@ public void Mid0033ByteRevision3() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.JobName); - Assert.IsNotNull(mid.ForcedOrder); - Assert.IsNotNull(mid.MaxTimeForFirstTightening); - Assert.IsNotNull(mid.MaxTimeToCompleteJob); - Assert.IsNotNull(mid.JobBatchMode); - Assert.IsNotNull(mid.LockAtJobDone); - Assert.IsNotNull(mid.UseLineControl); - Assert.IsNotNull(mid.RepeatJob); - Assert.IsNotNull(mid.ToolLoosening); - Assert.IsNotNull(mid.Reserved); - Assert.IsNotNull(mid.NumberOfParameterSets); - Assert.IsNotNull(mid.ParameterSetList); + Assert.AreEqual(4, mid.JobId); + Assert.AreEqual("My Job 4", mid.JobName.TrimEnd()); + Assert.AreEqual(ForcedOrder.ForcedOrder, mid.ForcedOrder); + Assert.AreEqual(5000, mid.MaxTimeForFirstTightening); + Assert.AreEqual(70004, mid.MaxTimeToCompleteJob); + Assert.AreEqual(JobBatchMode.OkAndNokTightenings, mid.JobBatchMode); + Assert.IsTrue(mid.LockAtJobDone); + Assert.IsTrue(mid.UseLineControl); + Assert.IsTrue(mid.RepeatJob); + Assert.AreEqual(ToolLoosening.EnableOnlyOnNokTightenings, mid.ToolLoosening); + Assert.AreEqual(Reserved.G, mid.Reserved); + Assert.AreEqual(2, mid.NumberOfParameterSets); + Assert.AreEqual(2, mid.ParameterSetList.Count); AssertEqualPackages(bytes, mid); } @@ -155,19 +156,19 @@ public void Mid0033Revision4() string package = "01910033004 01000402My Job 4 031045000057000406107108109110211112021315:011:1:02:0002:Job Step 1 Name :03:05;11:015:1:02:0001:Job Step 2 Name :05:02;"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.JobName); - Assert.IsNotNull(mid.ForcedOrder); - Assert.IsNotNull(mid.MaxTimeForFirstTightening); - Assert.IsNotNull(mid.MaxTimeToCompleteJob); - Assert.IsNotNull(mid.JobBatchMode); - Assert.IsNotNull(mid.LockAtJobDone); - Assert.IsNotNull(mid.UseLineControl); - Assert.IsNotNull(mid.RepeatJob); - Assert.IsNotNull(mid.ToolLoosening); - Assert.IsNotNull(mid.Reserved); - Assert.IsNotNull(mid.NumberOfParameterSets); - Assert.IsNotNull(mid.ParameterSetList); + Assert.AreEqual(4, mid.JobId); + Assert.AreEqual("My Job 4", mid.JobName.TrimEnd()); + Assert.AreEqual(ForcedOrder.ForcedOrder, mid.ForcedOrder); + Assert.AreEqual(5000, mid.MaxTimeForFirstTightening); + Assert.AreEqual(70004, mid.MaxTimeToCompleteJob); + Assert.AreEqual(JobBatchMode.OkAndNokTightenings, mid.JobBatchMode); + Assert.IsTrue(mid.LockAtJobDone); + Assert.IsTrue(mid.UseLineControl); + Assert.IsTrue(mid.RepeatJob); + Assert.AreEqual(ToolLoosening.EnableOnlyOnNokTightenings, mid.ToolLoosening); + Assert.AreEqual(Reserved.G, mid.Reserved); + Assert.AreEqual(2, mid.NumberOfParameterSets); + Assert.AreEqual(2, mid.ParameterSetList.Count); AssertEqualPackages(package, mid); } @@ -179,19 +180,19 @@ public void Mid0033ByteRevision4() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.JobName); - Assert.IsNotNull(mid.ForcedOrder); - Assert.IsNotNull(mid.MaxTimeForFirstTightening); - Assert.IsNotNull(mid.MaxTimeToCompleteJob); - Assert.IsNotNull(mid.JobBatchMode); - Assert.IsNotNull(mid.LockAtJobDone); - Assert.IsNotNull(mid.UseLineControl); - Assert.IsNotNull(mid.RepeatJob); - Assert.IsNotNull(mid.ToolLoosening); - Assert.IsNotNull(mid.Reserved); - Assert.IsNotNull(mid.NumberOfParameterSets); - Assert.IsNotNull(mid.ParameterSetList); + Assert.AreEqual(4, mid.JobId); + Assert.AreEqual("My Job 4", mid.JobName.TrimEnd()); + Assert.AreEqual(ForcedOrder.ForcedOrder, mid.ForcedOrder); + Assert.AreEqual(5000, mid.MaxTimeForFirstTightening); + Assert.AreEqual(70004, mid.MaxTimeToCompleteJob); + Assert.AreEqual(JobBatchMode.OkAndNokTightenings, mid.JobBatchMode); + Assert.IsTrue(mid.LockAtJobDone); + Assert.IsTrue(mid.UseLineControl); + Assert.IsTrue(mid.RepeatJob); + Assert.AreEqual(ToolLoosening.EnableOnlyOnNokTightenings, mid.ToolLoosening); + Assert.AreEqual(Reserved.G, mid.Reserved); + Assert.AreEqual(2, mid.NumberOfParameterSets); + Assert.AreEqual(2, mid.ParameterSetList.Count); AssertEqualPackages(bytes, mid); } @@ -202,19 +203,19 @@ public void Mid0033Revision5() string package = "01950033005 01000402My Job 4 031045000057000406107108109110211112021315:011:1:0002:0002:Job Step 1 Name :03:05;11:015:1:0002:0001:Job Step 2 Name :05:02;"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.JobName); - Assert.IsNotNull(mid.ForcedOrder); - Assert.IsNotNull(mid.MaxTimeForFirstTightening); - Assert.IsNotNull(mid.MaxTimeToCompleteJob); - Assert.IsNotNull(mid.JobBatchMode); - Assert.IsNotNull(mid.LockAtJobDone); - Assert.IsNotNull(mid.UseLineControl); - Assert.IsNotNull(mid.RepeatJob); - Assert.IsNotNull(mid.ToolLoosening); - Assert.IsNotNull(mid.Reserved); - Assert.IsNotNull(mid.NumberOfParameterSets); - Assert.IsNotNull(mid.ParameterSetList); + Assert.AreEqual(4, mid.JobId); + Assert.AreEqual("My Job 4", mid.JobName.TrimEnd()); + Assert.AreEqual(ForcedOrder.ForcedOrder, mid.ForcedOrder); + Assert.AreEqual(5000, mid.MaxTimeForFirstTightening); + Assert.AreEqual(70004, mid.MaxTimeToCompleteJob); + Assert.AreEqual(JobBatchMode.OkAndNokTightenings, mid.JobBatchMode); + Assert.IsTrue(mid.LockAtJobDone); + Assert.IsTrue(mid.UseLineControl); + Assert.IsTrue(mid.RepeatJob); + Assert.AreEqual(ToolLoosening.EnableOnlyOnNokTightenings, mid.ToolLoosening); + Assert.AreEqual(Reserved.G, mid.Reserved); + Assert.AreEqual(2, mid.NumberOfParameterSets); + Assert.AreEqual(2, mid.ParameterSetList.Count); AssertEqualPackages(package, mid); } @@ -226,19 +227,19 @@ public void Mid0033ByteRevision5() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.JobName); - Assert.IsNotNull(mid.ForcedOrder); - Assert.IsNotNull(mid.MaxTimeForFirstTightening); - Assert.IsNotNull(mid.MaxTimeToCompleteJob); - Assert.IsNotNull(mid.JobBatchMode); - Assert.IsNotNull(mid.LockAtJobDone); - Assert.IsNotNull(mid.UseLineControl); - Assert.IsNotNull(mid.RepeatJob); - Assert.IsNotNull(mid.ToolLoosening); - Assert.IsNotNull(mid.Reserved); - Assert.IsNotNull(mid.NumberOfParameterSets); - Assert.IsNotNull(mid.ParameterSetList); + Assert.AreEqual(4, mid.JobId); + Assert.AreEqual("My Job 4", mid.JobName.TrimEnd()); + Assert.AreEqual(ForcedOrder.ForcedOrder, mid.ForcedOrder); + Assert.AreEqual(5000, mid.MaxTimeForFirstTightening); + Assert.AreEqual(70004, mid.MaxTimeToCompleteJob); + Assert.AreEqual(JobBatchMode.OkAndNokTightenings, mid.JobBatchMode); + Assert.IsTrue(mid.LockAtJobDone); + Assert.IsTrue(mid.UseLineControl); + Assert.IsTrue(mid.RepeatJob); + Assert.AreEqual(ToolLoosening.EnableOnlyOnNokTightenings, mid.ToolLoosening); + Assert.AreEqual(Reserved.G, mid.Reserved); + Assert.AreEqual(2, mid.NumberOfParameterSets); + Assert.AreEqual(2, mid.ParameterSetList.Count); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/Job/TestMid0035.cs b/src/MIDTesters.Core/Job/TestMid0035.cs index 985c85aa..7cec372a 100644 --- a/src/MIDTesters.Core/Job/TestMid0035.cs +++ b/src/MIDTesters.Core/Job/TestMid0035.cs @@ -1,5 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.Job; +using System; namespace MIDTesters.Job { @@ -14,12 +16,12 @@ public void Mid0035Revision1() string package = "00630035001 0101020030040008050003062001-12-01:20:12:45"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.JobStatus); - Assert.IsNotNull(mid.JobBatchMode); - Assert.IsNotNull(mid.JobBatchSize); - Assert.IsNotNull(mid.JobBatchCounter); - Assert.IsNotNull(mid.TimeStamp); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(JobStatus.NotCompleted, mid.JobStatus); + Assert.AreEqual(JobBatchMode.OnlyOkTightenings, mid.JobBatchMode); + Assert.AreEqual(8, mid.JobBatchSize); + Assert.AreEqual(3, mid.JobBatchCounter); + Assert.AreEqual(new DateTime(2001, 12, 1, 20, 12, 45), mid.TimeStamp); AssertEqualPackages(package, mid); } @@ -31,12 +33,12 @@ public void Mid0035ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.JobStatus); - Assert.IsNotNull(mid.JobBatchMode); - Assert.IsNotNull(mid.JobBatchSize); - Assert.IsNotNull(mid.JobBatchCounter); - Assert.IsNotNull(mid.TimeStamp); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(JobStatus.NotCompleted, mid.JobStatus); + Assert.AreEqual(JobBatchMode.OnlyOkTightenings, mid.JobBatchMode); + Assert.AreEqual(8, mid.JobBatchSize); + Assert.AreEqual(3, mid.JobBatchCounter); + Assert.AreEqual(new DateTime(2001, 12, 1, 20, 12, 45), mid.TimeStamp); AssertEqualPackages(bytes, mid); } @@ -47,12 +49,12 @@ public void Mid0035Revision2() string package = "00650035002 010001020030040008050003062001-12-01:20:12:45"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.JobStatus); - Assert.IsNotNull(mid.JobBatchMode); - Assert.IsNotNull(mid.JobBatchSize); - Assert.IsNotNull(mid.JobBatchCounter); - Assert.IsNotNull(mid.TimeStamp); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(JobStatus.NotCompleted, mid.JobStatus); + Assert.AreEqual(JobBatchMode.OnlyOkTightenings, mid.JobBatchMode); + Assert.AreEqual(8, mid.JobBatchSize); + Assert.AreEqual(3, mid.JobBatchCounter); + Assert.AreEqual(new DateTime(2001, 12, 1, 20, 12, 45), mid.TimeStamp); AssertEqualPackages(package, mid); } @@ -64,12 +66,12 @@ public void Mid0035ByteRevision2() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.JobStatus); - Assert.IsNotNull(mid.JobBatchMode); - Assert.IsNotNull(mid.JobBatchSize); - Assert.IsNotNull(mid.JobBatchCounter); - Assert.IsNotNull(mid.TimeStamp); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(JobStatus.NotCompleted, mid.JobStatus); + Assert.AreEqual(JobBatchMode.OnlyOkTightenings, mid.JobBatchMode); + Assert.AreEqual(8, mid.JobBatchSize); + Assert.AreEqual(3, mid.JobBatchCounter); + Assert.AreEqual(new DateTime(2001, 12, 1, 20, 12, 45), mid.TimeStamp); AssertEqualPackages(bytes, mid); } @@ -80,15 +82,15 @@ public void Mid0035Revision3() string package = "00790035003 010001020030040008050003062001-12-01:20:12:4507120080100912"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.JobStatus); - Assert.IsNotNull(mid.JobBatchMode); - Assert.IsNotNull(mid.JobBatchSize); - Assert.IsNotNull(mid.JobBatchCounter); - Assert.IsNotNull(mid.TimeStamp); - Assert.IsNotNull(mid.JobCurrentStep); - Assert.IsNotNull(mid.JobTotalNumberOfSteps); - Assert.IsNotNull(mid.JobStepType); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(JobStatus.NotCompleted, mid.JobStatus); + Assert.AreEqual(JobBatchMode.OnlyOkTightenings, mid.JobBatchMode); + Assert.AreEqual(8, mid.JobBatchSize); + Assert.AreEqual(3, mid.JobBatchCounter); + Assert.AreEqual(new DateTime(2001, 12, 1, 20, 12, 45), mid.TimeStamp); + Assert.AreEqual(120, mid.JobCurrentStep); + Assert.AreEqual(10, mid.JobTotalNumberOfSteps); + Assert.AreEqual(12, mid.JobStepType); AssertEqualPackages(package, mid); } @@ -100,15 +102,15 @@ public void Mid0035ByteRevision3() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.JobStatus); - Assert.IsNotNull(mid.JobBatchMode); - Assert.IsNotNull(mid.JobBatchSize); - Assert.IsNotNull(mid.JobBatchCounter); - Assert.IsNotNull(mid.TimeStamp); - Assert.IsNotNull(mid.JobCurrentStep); - Assert.IsNotNull(mid.JobTotalNumberOfSteps); - Assert.IsNotNull(mid.JobStepType); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(JobStatus.NotCompleted, mid.JobStatus); + Assert.AreEqual(JobBatchMode.OnlyOkTightenings, mid.JobBatchMode); + Assert.AreEqual(8, mid.JobBatchSize); + Assert.AreEqual(3, mid.JobBatchCounter); + Assert.AreEqual(new DateTime(2001, 12, 1, 20, 12, 45), mid.TimeStamp); + Assert.AreEqual(120, mid.JobCurrentStep); + Assert.AreEqual(10, mid.JobTotalNumberOfSteps); + Assert.AreEqual(12, mid.JobStepType); AssertEqualPackages(bytes, mid); } @@ -119,16 +121,16 @@ public void Mid0035Revision4() string package = "00830035004 010001020030040008050003062001-12-01:20:12:45071200801009121001"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.JobStatus); - Assert.IsNotNull(mid.JobBatchMode); - Assert.IsNotNull(mid.JobBatchSize); - Assert.IsNotNull(mid.JobBatchCounter); - Assert.IsNotNull(mid.TimeStamp); - Assert.IsNotNull(mid.JobCurrentStep); - Assert.IsNotNull(mid.JobTotalNumberOfSteps); - Assert.IsNotNull(mid.JobStepType); - Assert.IsNotNull(mid.JobTighteningStatus); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(JobStatus.NotCompleted, mid.JobStatus); + Assert.AreEqual(JobBatchMode.OnlyOkTightenings, mid.JobBatchMode); + Assert.AreEqual(8, mid.JobBatchSize); + Assert.AreEqual(3, mid.JobBatchCounter); + Assert.AreEqual(new DateTime(2001, 12, 1, 20, 12, 45), mid.TimeStamp); + Assert.AreEqual(120, mid.JobCurrentStep); + Assert.AreEqual(10, mid.JobTotalNumberOfSteps); + Assert.AreEqual(12, mid.JobStepType); + Assert.AreEqual(JobTighteningStatus.Nok, mid.JobTighteningStatus); AssertEqualPackages(package, mid); } @@ -140,16 +142,16 @@ public void Mid0035ByteRevision4() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.JobStatus); - Assert.IsNotNull(mid.JobBatchMode); - Assert.IsNotNull(mid.JobBatchSize); - Assert.IsNotNull(mid.JobBatchCounter); - Assert.IsNotNull(mid.TimeStamp); - Assert.IsNotNull(mid.JobCurrentStep); - Assert.IsNotNull(mid.JobTotalNumberOfSteps); - Assert.IsNotNull(mid.JobStepType); - Assert.IsNotNull(mid.JobTighteningStatus); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(JobStatus.NotCompleted, mid.JobStatus); + Assert.AreEqual(JobBatchMode.OnlyOkTightenings, mid.JobBatchMode); + Assert.AreEqual(8, mid.JobBatchSize); + Assert.AreEqual(3, mid.JobBatchCounter); + Assert.AreEqual(new DateTime(2001, 12, 1, 20, 12, 45), mid.TimeStamp); + Assert.AreEqual(120, mid.JobCurrentStep); + Assert.AreEqual(10, mid.JobTotalNumberOfSteps); + Assert.AreEqual(12, mid.JobStepType); + Assert.AreEqual(JobTighteningStatus.Nok, mid.JobTighteningStatus); AssertEqualPackages(bytes, mid); } @@ -160,21 +162,21 @@ public void Mid0035Revision5() string package = "01980035005 010001020030040008050003062001-12-01:20:12:45071200801009121001111234512VINVINN12345678912345678913IdentifierResultPart2xxxx14IdentifierResultPart3xxxx15IdentifierResultPart4xxxx"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.JobStatus); - Assert.IsNotNull(mid.JobBatchMode); - Assert.IsNotNull(mid.JobBatchSize); - Assert.IsNotNull(mid.JobBatchCounter); - Assert.IsNotNull(mid.TimeStamp); - Assert.IsNotNull(mid.JobCurrentStep); - Assert.IsNotNull(mid.JobTotalNumberOfSteps); - Assert.IsNotNull(mid.JobStepType); - Assert.IsNotNull(mid.JobTighteningStatus); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(JobStatus.NotCompleted, mid.JobStatus); + Assert.AreEqual(JobBatchMode.OnlyOkTightenings, mid.JobBatchMode); + Assert.AreEqual(8, mid.JobBatchSize); + Assert.AreEqual(3, mid.JobBatchCounter); + Assert.AreEqual(new DateTime(2001, 12, 1, 20, 12, 45), mid.TimeStamp); + Assert.AreEqual(120, mid.JobCurrentStep); + Assert.AreEqual(10, mid.JobTotalNumberOfSteps); + Assert.AreEqual(12, mid.JobStepType); + Assert.AreEqual(JobTighteningStatus.Nok, mid.JobTighteningStatus); + Assert.AreEqual(12345, mid.JobSequenceNumber); + Assert.AreEqual("VINVINN123456789123456789", mid.VinNumber); + Assert.AreEqual("IdentifierResultPart2xxxx", mid.IdentifierResultPart2); + Assert.AreEqual("IdentifierResultPart3xxxx", mid.IdentifierResultPart3); + Assert.AreEqual("IdentifierResultPart4xxxx", mid.IdentifierResultPart4); AssertEqualPackages(package, mid); } @@ -186,21 +188,21 @@ public void Mid0035ByteRevision5() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.JobStatus); - Assert.IsNotNull(mid.JobBatchMode); - Assert.IsNotNull(mid.JobBatchSize); - Assert.IsNotNull(mid.JobBatchCounter); - Assert.IsNotNull(mid.TimeStamp); - Assert.IsNotNull(mid.JobCurrentStep); - Assert.IsNotNull(mid.JobTotalNumberOfSteps); - Assert.IsNotNull(mid.JobStepType); - Assert.IsNotNull(mid.JobTighteningStatus); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(JobStatus.NotCompleted, mid.JobStatus); + Assert.AreEqual(JobBatchMode.OnlyOkTightenings, mid.JobBatchMode); + Assert.AreEqual(8, mid.JobBatchSize); + Assert.AreEqual(3, mid.JobBatchCounter); + Assert.AreEqual(new DateTime(2001, 12, 1, 20, 12, 45), mid.TimeStamp); + Assert.AreEqual(120, mid.JobCurrentStep); + Assert.AreEqual(10, mid.JobTotalNumberOfSteps); + Assert.AreEqual(12, mid.JobStepType); + Assert.AreEqual(JobTighteningStatus.Nok, mid.JobTighteningStatus); + Assert.AreEqual(12345, mid.JobSequenceNumber); + Assert.AreEqual("VINVINN123456789123456789", mid.VinNumber); + Assert.AreEqual("IdentifierResultPart2xxxx", mid.IdentifierResultPart2); + Assert.AreEqual("IdentifierResultPart3xxxx", mid.IdentifierResultPart3); + Assert.AreEqual("IdentifierResultPart4xxxx", mid.IdentifierResultPart4); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/Job/TestMid0039.cs b/src/MIDTesters.Core/Job/TestMid0039.cs index de70ac17..8b3b3af8 100644 --- a/src/MIDTesters.Core/Job/TestMid0039.cs +++ b/src/MIDTesters.Core/Job/TestMid0039.cs @@ -14,7 +14,7 @@ public void Mid0039Revision1() string package = "00220039001 01"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.JobId); + Assert.AreEqual(1, mid.JobId); AssertEqualPackages(package, mid); } @@ -26,7 +26,7 @@ public void Mid0039ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.JobId); + Assert.AreEqual(1, mid.JobId); AssertEqualPackages(bytes, mid); } @@ -37,7 +37,7 @@ public void Mid0039Revision2() string package = "00240039002 0003"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.JobId); + Assert.AreEqual(3, mid.JobId); AssertEqualPackages(package, mid); } @@ -49,7 +49,7 @@ public void Mid0039ByteRevision2() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.JobId); + Assert.AreEqual(3, mid.JobId); AssertEqualPackages(bytes, mid); } } From 6026797e07022ba38c288fcc37162989f6ff9bed Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 29 Mar 2026 23:34:53 -0300 Subject: [PATCH 08/78] Updated linkcommunication tests --- src/MIDTesters.Core/LinkCommunication/TestMid9997.cs | 4 ++-- src/MIDTesters.Core/LinkCommunication/TestMid9998.cs | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/MIDTesters.Core/LinkCommunication/TestMid9997.cs b/src/MIDTesters.Core/LinkCommunication/TestMid9997.cs index a04ec3fd..5d81f772 100644 --- a/src/MIDTesters.Core/LinkCommunication/TestMid9997.cs +++ b/src/MIDTesters.Core/LinkCommunication/TestMid9997.cs @@ -14,7 +14,7 @@ public void Mid9997Revision1() string package = "00249997001 0061"; var mid = _midInterpreter.Parse(package); - Assert.AreNotEqual(0, mid.MidNumber); + Assert.AreEqual(61, mid.MidNumber); AssertEqualPackages(package, mid); } @@ -26,7 +26,7 @@ public void Mid9997ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.AreNotEqual(0, mid.MidNumber); + Assert.AreEqual(65, mid.MidNumber); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/LinkCommunication/TestMid9998.cs b/src/MIDTesters.Core/LinkCommunication/TestMid9998.cs index 309798b6..3861e38c 100644 --- a/src/MIDTesters.Core/LinkCommunication/TestMid9998.cs +++ b/src/MIDTesters.Core/LinkCommunication/TestMid9998.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.LinkCommunication; namespace MIDTesters.LinkCommunication @@ -14,8 +15,8 @@ public void Mid9998Revision1() string package = "00289998 00610003"; var mid = _midInterpreter.Parse(package); - Assert.AreNotEqual(0, mid.MidNumber); - Assert.IsNotNull(mid.ErrorCode); + Assert.AreEqual(61, mid.MidNumber); + Assert.AreEqual(LinkCommunicationError.InvalidSequenceNumber, mid.ErrorCode); AssertEqualPackages(package, mid, true); } @@ -27,8 +28,8 @@ public void Mid9998ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.AreNotEqual(0, mid.MidNumber); - Assert.IsNotNull(mid.ErrorCode); + Assert.AreEqual(61, mid.MidNumber); + Assert.AreEqual(LinkCommunicationError.InvalidSequenceNumber, mid.ErrorCode); AssertEqualPackages(bytes, mid, true); } } From 4a704f20a7a2ef1d49a7488642650e01cedf2e07 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 29 Mar 2026 23:35:09 -0300 Subject: [PATCH 09/78] Updated motor tuning tests --- src/MIDTesters.Core/MotorTuning/TestMid0501.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MIDTesters.Core/MotorTuning/TestMid0501.cs b/src/MIDTesters.Core/MotorTuning/TestMid0501.cs index f047e870..94f165bd 100644 --- a/src/MIDTesters.Core/MotorTuning/TestMid0501.cs +++ b/src/MIDTesters.Core/MotorTuning/TestMid0501.cs @@ -14,7 +14,7 @@ public void Mid0501Revision1() string package = "00230501 011"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.MotorTuneResult); + Assert.IsTrue(mid.MotorTuneResult); AssertEqualPackages(package, mid, true); } @@ -26,7 +26,7 @@ public void Mid0501ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.MotorTuneResult); + Assert.IsTrue(mid.MotorTuneResult); AssertEqualPackages(bytes, mid, true); } } From 5c137dcce3c06f75d869504e7e004a47f4b33b99 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 29 Mar 2026 23:35:38 -0300 Subject: [PATCH 10/78] Updated multiple identifiers tests --- .../MultipleIdentifiers/TestMid0150.cs | 5 +-- .../MultipleIdentifiers/TestMid0152.cs | 41 +++++++++++++++---- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/MIDTesters.Core/MultipleIdentifiers/TestMid0150.cs b/src/MIDTesters.Core/MultipleIdentifiers/TestMid0150.cs index 45774ed2..d28954a9 100644 --- a/src/MIDTesters.Core/MultipleIdentifiers/TestMid0150.cs +++ b/src/MIDTesters.Core/MultipleIdentifiers/TestMid0150.cs @@ -16,7 +16,7 @@ public void Mid0150Revision1() var mid = _midInterpreter.Parse(package); var mid0150 = new Mid0150() { IdentifierData = identifier }; - Assert.IsNotNull(mid.IdentifierData); + Assert.AreEqual("My identifier less than 100", mid.IdentifierData); AssertEqualPackages(package, mid0150); } @@ -30,7 +30,7 @@ public void Mid0150ByteRevision1() var mid = _midInterpreter.Parse(bytes); var mid0150 = new Mid0150() { IdentifierData = identifier }; - Assert.IsNotNull(mid.IdentifierData); + Assert.AreEqual("My identifier less than 100", mid.IdentifierData); AssertEqualPackages(bytes, mid0150); } @@ -40,7 +40,6 @@ public void Mid0150ShouldTruncateIdentifier() string identifier = "the phrase the quick brown fox jumps over the lazy dog should test all the letter keys in your keyboard"; //103 characters var mid0150 = new Mid0150() { IdentifierData = identifier }; - Assert.IsNotNull(mid0150.IdentifierData); Assert.IsTrue(mid0150.Pack().Length == 120); } } diff --git a/src/MIDTesters.Core/MultipleIdentifiers/TestMid0152.cs b/src/MIDTesters.Core/MultipleIdentifiers/TestMid0152.cs index d1f684dc..f402f103 100644 --- a/src/MIDTesters.Core/MultipleIdentifiers/TestMid0152.cs +++ b/src/MIDTesters.Core/MultipleIdentifiers/TestMid0152.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.MultipleIdentifiers; namespace MIDTesters.MultipleIdentifiers @@ -14,10 +15,22 @@ public void Mid0152Revision1() string package = "01480152001 0110101Result part 1 0220003Result part 2 0330104Result part 3 0440105Result part 4 "; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.FirstIdentifierStatus); - Assert.IsNotNull(mid.SecondIdentifierStatus); - Assert.IsNotNull(mid.ThirdIdentifierStatus); - Assert.IsNotNull(mid.FourthIdentifierStatus); + Assert.AreEqual(1, mid.FirstIdentifierStatus.IdentifierTypeNumber); + Assert.IsTrue(mid.FirstIdentifierStatus.IncludedInWorkOrder); + Assert.AreEqual(StatusInWorkOrder.Accepted, mid.FirstIdentifierStatus.StatusInWorkOrder); + Assert.AreEqual("Result part 1", mid.FirstIdentifierStatus.ResultPart.TrimEnd()); + Assert.AreEqual(2, mid.SecondIdentifierStatus.IdentifierTypeNumber); + Assert.IsFalse(mid.SecondIdentifierStatus.IncludedInWorkOrder); + Assert.AreEqual(StatusInWorkOrder.Reset, mid.SecondIdentifierStatus.StatusInWorkOrder); + Assert.AreEqual("Result part 2", mid.SecondIdentifierStatus.ResultPart.TrimEnd()); + Assert.AreEqual(3, mid.ThirdIdentifierStatus.IdentifierTypeNumber); + Assert.IsTrue(mid.ThirdIdentifierStatus.IncludedInWorkOrder); + Assert.AreEqual(StatusInWorkOrder.Next, mid.ThirdIdentifierStatus.StatusInWorkOrder); + Assert.AreEqual("Result part 3", mid.ThirdIdentifierStatus.ResultPart.TrimEnd()); + Assert.AreEqual(4, mid.FourthIdentifierStatus.IdentifierTypeNumber); + Assert.IsTrue(mid.FourthIdentifierStatus.IncludedInWorkOrder); + Assert.AreEqual(StatusInWorkOrder.Initial, mid.FourthIdentifierStatus.StatusInWorkOrder); + Assert.AreEqual("Result part 4", mid.FourthIdentifierStatus.ResultPart.TrimEnd()); AssertEqualPackages(package, mid); } @@ -29,10 +42,22 @@ public void Mid0152ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.FirstIdentifierStatus); - Assert.IsNotNull(mid.SecondIdentifierStatus); - Assert.IsNotNull(mid.ThirdIdentifierStatus); - Assert.IsNotNull(mid.FourthIdentifierStatus); + Assert.AreEqual(1, mid.FirstIdentifierStatus.IdentifierTypeNumber); + Assert.IsTrue(mid.FirstIdentifierStatus.IncludedInWorkOrder); + Assert.AreEqual(StatusInWorkOrder.Accepted, mid.FirstIdentifierStatus.StatusInWorkOrder); + Assert.AreEqual("Result part 1", mid.FirstIdentifierStatus.ResultPart.TrimEnd()); + Assert.AreEqual(2, mid.SecondIdentifierStatus.IdentifierTypeNumber); + Assert.IsFalse(mid.SecondIdentifierStatus.IncludedInWorkOrder); + Assert.AreEqual(StatusInWorkOrder.Reset, mid.SecondIdentifierStatus.StatusInWorkOrder); + Assert.AreEqual("Result part 2", mid.SecondIdentifierStatus.ResultPart.TrimEnd()); + Assert.AreEqual(3, mid.ThirdIdentifierStatus.IdentifierTypeNumber); + Assert.IsTrue(mid.ThirdIdentifierStatus.IncludedInWorkOrder); + Assert.AreEqual(StatusInWorkOrder.Next, mid.ThirdIdentifierStatus.StatusInWorkOrder); + Assert.AreEqual("Result part 3", mid.ThirdIdentifierStatus.ResultPart.TrimEnd()); + Assert.AreEqual(4, mid.FourthIdentifierStatus.IdentifierTypeNumber); + Assert.IsTrue(mid.FourthIdentifierStatus.IncludedInWorkOrder); + Assert.AreEqual(StatusInWorkOrder.Initial, mid.FourthIdentifierStatus.StatusInWorkOrder); + Assert.AreEqual("Result part 4", mid.FourthIdentifierStatus.ResultPart.TrimEnd()); AssertEqualPackages(bytes, mid); } } From c554e5645987f2053e1be1f40f92280877ef705a Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 29 Mar 2026 23:35:47 -0300 Subject: [PATCH 11/78] Updated multi spindle tests --- .../MultiSpindle/TestMid0091.cs | 23 +- .../MultiSpindle/TestMid0100.cs | 12 +- .../MultiSpindle/TestMid0101.cs | 314 +++++++++--------- .../MultiSpindle/TestMid0104.cs | 4 +- 4 files changed, 177 insertions(+), 176 deletions(-) diff --git a/src/MIDTesters.Core/MultiSpindle/TestMid0091.cs b/src/MIDTesters.Core/MultiSpindle/TestMid0091.cs index 905738bc..5b01308f 100644 --- a/src/MIDTesters.Core/MultiSpindle/TestMid0091.cs +++ b/src/MIDTesters.Core/MultiSpindle/TestMid0091.cs @@ -1,4 +1,5 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter.MultiSpindle; namespace MIDTesters.MultiSpindle @@ -14,11 +15,11 @@ public void Mid0091AllRevisions() string pack = @"00670091 1 01020265535032017-01-25:10:20:20041050101102031"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.NumberOfSpindles); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.Time); - Assert.IsNotNull(mid.SyncOverallStatus); - Assert.IsNotNull(mid.SpindlesStatus); + Assert.AreEqual(2, mid.NumberOfSpindles); + Assert.AreEqual(65535, mid.SyncTighteningId); + Assert.AreEqual(new DateTime(2017, 1, 25, 10, 20, 20), mid.Time); + Assert.IsTrue(mid.SyncOverallStatus); + Assert.AreEqual(2, mid.SpindlesStatus.Count); AssertEqualPackages(pack, mid, true); } @@ -30,11 +31,11 @@ public void Mid0091ByteAllRevisions() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.NumberOfSpindles); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.Time); - Assert.IsNotNull(mid.SyncOverallStatus); - Assert.IsNotNull(mid.SpindlesStatus); + Assert.AreEqual(2, mid.NumberOfSpindles); + Assert.AreEqual(65535, mid.SyncTighteningId); + Assert.AreEqual(new DateTime(2017, 1, 25, 10, 20, 20), mid.Time); + Assert.IsTrue(mid.SyncOverallStatus); + Assert.AreEqual(2, mid.SpindlesStatus.Count); AssertEqualPackages(bytes, mid, true); } } diff --git a/src/MIDTesters.Core/MultiSpindle/TestMid0100.cs b/src/MIDTesters.Core/MultiSpindle/TestMid0100.cs index 82a53157..33bca12a 100644 --- a/src/MIDTesters.Core/MultiSpindle/TestMid0100.cs +++ b/src/MIDTesters.Core/MultiSpindle/TestMid0100.cs @@ -38,7 +38,7 @@ public void Mid0100Revision2() var mid = _midInterpreter.Parse(pack); Assert.AreEqual(typeof(Mid0100), mid.GetType()); - Assert.IsNotNull(mid.DataNumberSystem); + Assert.AreEqual(123456789L, mid.DataNumberSystem); AssertEqualPackages(pack, mid); } @@ -51,7 +51,7 @@ public void Mid0100ByteRevision2() var mid = _midInterpreter.Parse(bytes); Assert.AreEqual(typeof(Mid0100), mid.GetType()); - Assert.IsNotNull(mid.DataNumberSystem); + Assert.AreEqual(123456789L, mid.DataNumberSystem); AssertEqualPackages(bytes, mid); } @@ -63,8 +63,8 @@ public void Mid0100Revision3() var mid = _midInterpreter.Parse(pack); Assert.AreEqual(typeof(Mid0100), mid.GetType()); - Assert.IsNotNull(mid.DataNumberSystem); - Assert.IsNotNull(mid.SendOnlyNewData); + Assert.AreEqual(123456789L, mid.DataNumberSystem); + Assert.IsTrue(mid.SendOnlyNewData); AssertEqualPackages(pack, mid); } @@ -77,8 +77,8 @@ public void Mid0100ByteRevision3() var mid = _midInterpreter.Parse(bytes); Assert.AreEqual(typeof(Mid0100), mid.GetType()); - Assert.IsNotNull(mid.DataNumberSystem); - Assert.IsNotNull(mid.SendOnlyNewData); + Assert.AreEqual(123456789L, mid.DataNumberSystem); + Assert.IsTrue(mid.SendOnlyNewData); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/MultiSpindle/TestMid0101.cs b/src/MIDTesters.Core/MultiSpindle/TestMid0101.cs index 8e300f37..f0a843f6 100644 --- a/src/MIDTesters.Core/MultiSpindle/TestMid0101.cs +++ b/src/MIDTesters.Core/MultiSpindle/TestMid0101.cs @@ -1,4 +1,6 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.MultiSpindle; namespace MIDTesters.MultiSpindle @@ -15,24 +17,24 @@ public void Mid0101Revisions1To3() var mid = _midInterpreter.Parse(pack); Assert.AreEqual(typeof(Mid0101), mid.GetType()); - Assert.IsNotNull(mid.NumberOfSpindlesOrPresses); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueOrForceMinLimit); - Assert.IsNotNull(mid.TorqueOrForceMaxLimit); - Assert.IsNotNull(mid.TorqueOrForceFinalTarget); - Assert.IsNotNull(mid.AngleOrStrokeMinLimit); - Assert.IsNotNull(mid.AngleOrStrokeMaxLimit); - Assert.IsNotNull(mid.FinalAngleOrStrokeTarget); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.TimeStamp); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.SyncOverallStatus); - Assert.IsNotNull(mid.SpindlesOrPressesStatus); + Assert.AreEqual(1, mid.NumberOfSpindlesOrPresses); + Assert.AreEqual("BM3GA02111900601", mid.VinNumber.TrimEnd()); + Assert.AreEqual(3, mid.JobId); + Assert.AreEqual(3, mid.ParameterSetId); + Assert.AreEqual(1, mid.BatchSize); + Assert.AreEqual(1, mid.BatchCounter); + Assert.AreEqual(BatchStatus.Ok, mid.BatchStatus); + Assert.AreEqual(6.80m, mid.TorqueOrForceMinLimit); + Assert.AreEqual(9.20m, mid.TorqueOrForceMaxLimit); + Assert.AreEqual(8.00m, mid.TorqueOrForceFinalTarget); + Assert.AreEqual(0.00m, mid.AngleOrStrokeMinLimit); + Assert.AreEqual(0.15m, mid.AngleOrStrokeMaxLimit); + Assert.AreEqual(0.00m, mid.FinalAngleOrStrokeTarget); + Assert.AreEqual(new DateTime(2019, 11, 14, 14, 8, 5), mid.LastChangeInParameterSet); + Assert.AreEqual(new DateTime(2019, 11, 25, 11, 22, 41), mid.TimeStamp); + Assert.AreEqual(913, mid.SyncTighteningId); + Assert.IsTrue(mid.SyncOverallStatus); + Assert.AreEqual(1, mid.SpindlesOrPressesStatus.Count); AssertEqualPackages(pack, mid); } @@ -45,24 +47,24 @@ public void Mid0101ByteRevisions1To3() var mid = _midInterpreter.Parse(bytes); Assert.AreEqual(typeof(Mid0101), mid.GetType()); - Assert.IsNotNull(mid.NumberOfSpindlesOrPresses); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueOrForceMinLimit); - Assert.IsNotNull(mid.TorqueOrForceMaxLimit); - Assert.IsNotNull(mid.TorqueOrForceFinalTarget); - Assert.IsNotNull(mid.AngleOrStrokeMinLimit); - Assert.IsNotNull(mid.AngleOrStrokeMaxLimit); - Assert.IsNotNull(mid.FinalAngleOrStrokeTarget); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.TimeStamp); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.SyncOverallStatus); - Assert.IsNotNull(mid.SpindlesOrPressesStatus); + Assert.AreEqual(1, mid.NumberOfSpindlesOrPresses); + Assert.AreEqual("BM3GA02111900601", mid.VinNumber.TrimEnd()); + Assert.AreEqual(3, mid.JobId); + Assert.AreEqual(3, mid.ParameterSetId); + Assert.AreEqual(1, mid.BatchSize); + Assert.AreEqual(1, mid.BatchCounter); + Assert.AreEqual(BatchStatus.Ok, mid.BatchStatus); + Assert.AreEqual(6.80m, mid.TorqueOrForceMinLimit); + Assert.AreEqual(9.20m, mid.TorqueOrForceMaxLimit); + Assert.AreEqual(8.00m, mid.TorqueOrForceFinalTarget); + Assert.AreEqual(0.00m, mid.AngleOrStrokeMinLimit); + Assert.AreEqual(0.15m, mid.AngleOrStrokeMaxLimit); + Assert.AreEqual(0.00m, mid.FinalAngleOrStrokeTarget); + Assert.AreEqual(new DateTime(2019, 11, 14, 14, 8, 5), mid.LastChangeInParameterSet); + Assert.AreEqual(new DateTime(2019, 11, 25, 11, 22, 41), mid.TimeStamp); + Assert.AreEqual(913, mid.SyncTighteningId); + Assert.IsTrue(mid.SyncOverallStatus); + Assert.AreEqual(1, mid.SpindlesOrPressesStatus.Count); AssertEqualPackages(bytes, mid); } @@ -74,25 +76,25 @@ public void Mid0101Revision4() var mid = _midInterpreter.Parse(pack); Assert.AreEqual(typeof(Mid0101), mid.GetType()); - Assert.IsNotNull(mid.NumberOfSpindlesOrPresses); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueOrForceMinLimit); - Assert.IsNotNull(mid.TorqueOrForceMaxLimit); - Assert.IsNotNull(mid.TorqueOrForceFinalTarget); - Assert.IsNotNull(mid.AngleOrStrokeMinLimit); - Assert.IsNotNull(mid.AngleOrStrokeMaxLimit); - Assert.IsNotNull(mid.FinalAngleOrStrokeTarget); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.TimeStamp); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.SyncOverallStatus); - Assert.IsNotNull(mid.SpindlesOrPressesStatus); - Assert.IsNotNull(mid.SystemSubType); + Assert.AreEqual(1, mid.NumberOfSpindlesOrPresses); + Assert.AreEqual("BM3GA02111900601", mid.VinNumber.TrimEnd()); + Assert.AreEqual(3, mid.JobId); + Assert.AreEqual(3, mid.ParameterSetId); + Assert.AreEqual(1, mid.BatchSize); + Assert.AreEqual(1, mid.BatchCounter); + Assert.AreEqual(BatchStatus.Ok, mid.BatchStatus); + Assert.AreEqual(6.80m, mid.TorqueOrForceMinLimit); + Assert.AreEqual(9.20m, mid.TorqueOrForceMaxLimit); + Assert.AreEqual(8.00m, mid.TorqueOrForceFinalTarget); + Assert.AreEqual(0.00m, mid.AngleOrStrokeMinLimit); + Assert.AreEqual(0.15m, mid.AngleOrStrokeMaxLimit); + Assert.AreEqual(0.00m, mid.FinalAngleOrStrokeTarget); + Assert.AreEqual(new DateTime(2019, 11, 14, 14, 8, 5), mid.LastChangeInParameterSet); + Assert.AreEqual(new DateTime(2019, 11, 25, 11, 22, 41), mid.TimeStamp); + Assert.AreEqual(913, mid.SyncTighteningId); + Assert.IsTrue(mid.SyncOverallStatus); + Assert.AreEqual(1, mid.SpindlesOrPressesStatus.Count); + Assert.AreEqual(SystemSubType.SystemRunningPresses, mid.SystemSubType); AssertEqualPackages(pack, mid); } @@ -105,25 +107,25 @@ public void Mid0101ByteRevision4() var mid = _midInterpreter.Parse(bytes); Assert.AreEqual(typeof(Mid0101), mid.GetType()); - Assert.IsNotNull(mid.NumberOfSpindlesOrPresses); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueOrForceMinLimit); - Assert.IsNotNull(mid.TorqueOrForceMaxLimit); - Assert.IsNotNull(mid.TorqueOrForceFinalTarget); - Assert.IsNotNull(mid.AngleOrStrokeMinLimit); - Assert.IsNotNull(mid.AngleOrStrokeMaxLimit); - Assert.IsNotNull(mid.FinalAngleOrStrokeTarget); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.TimeStamp); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.SyncOverallStatus); - Assert.IsNotNull(mid.SpindlesOrPressesStatus); - Assert.IsNotNull(mid.SystemSubType); + Assert.AreEqual(1, mid.NumberOfSpindlesOrPresses); + Assert.AreEqual("BM3GA02111900601", mid.VinNumber.TrimEnd()); + Assert.AreEqual(3, mid.JobId); + Assert.AreEqual(3, mid.ParameterSetId); + Assert.AreEqual(1, mid.BatchSize); + Assert.AreEqual(1, mid.BatchCounter); + Assert.AreEqual(BatchStatus.Ok, mid.BatchStatus); + Assert.AreEqual(6.80m, mid.TorqueOrForceMinLimit); + Assert.AreEqual(9.20m, mid.TorqueOrForceMaxLimit); + Assert.AreEqual(8.00m, mid.TorqueOrForceFinalTarget); + Assert.AreEqual(0.00m, mid.AngleOrStrokeMinLimit); + Assert.AreEqual(0.15m, mid.AngleOrStrokeMaxLimit); + Assert.AreEqual(0.00m, mid.FinalAngleOrStrokeTarget); + Assert.AreEqual(new DateTime(2019, 11, 14, 14, 8, 5), mid.LastChangeInParameterSet); + Assert.AreEqual(new DateTime(2019, 11, 25, 11, 22, 41), mid.TimeStamp); + Assert.AreEqual(913, mid.SyncTighteningId); + Assert.IsTrue(mid.SyncOverallStatus); + Assert.AreEqual(1, mid.SpindlesOrPressesStatus.Count); + Assert.AreEqual(SystemSubType.SystemRunningPresses, mid.SystemSubType); AssertEqualPackages(bytes, mid); } @@ -135,26 +137,26 @@ public void Mid0101Revision5() var mid = _midInterpreter.Parse(pack); Assert.AreEqual(typeof(Mid0101), mid.GetType()); - Assert.IsNotNull(mid.NumberOfSpindlesOrPresses); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueOrForceMinLimit); - Assert.IsNotNull(mid.TorqueOrForceMaxLimit); - Assert.IsNotNull(mid.TorqueOrForceFinalTarget); - Assert.IsNotNull(mid.AngleOrStrokeMinLimit); - Assert.IsNotNull(mid.AngleOrStrokeMaxLimit); - Assert.IsNotNull(mid.FinalAngleOrStrokeTarget); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.TimeStamp); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.SyncOverallStatus); - Assert.IsNotNull(mid.SpindlesOrPressesStatus); - Assert.IsNotNull(mid.SystemSubType); - Assert.IsNotNull(mid.JobSequenceNumber); + Assert.AreEqual(1, mid.NumberOfSpindlesOrPresses); + Assert.AreEqual("BM3GA02111900601", mid.VinNumber.TrimEnd()); + Assert.AreEqual(3, mid.JobId); + Assert.AreEqual(3, mid.ParameterSetId); + Assert.AreEqual(1, mid.BatchSize); + Assert.AreEqual(1, mid.BatchCounter); + Assert.AreEqual(BatchStatus.Ok, mid.BatchStatus); + Assert.AreEqual(6.80m, mid.TorqueOrForceMinLimit); + Assert.AreEqual(9.20m, mid.TorqueOrForceMaxLimit); + Assert.AreEqual(8.00m, mid.TorqueOrForceFinalTarget); + Assert.AreEqual(0.00m, mid.AngleOrStrokeMinLimit); + Assert.AreEqual(0.15m, mid.AngleOrStrokeMaxLimit); + Assert.AreEqual(0.00m, mid.FinalAngleOrStrokeTarget); + Assert.AreEqual(new DateTime(2019, 11, 14, 14, 8, 5), mid.LastChangeInParameterSet); + Assert.AreEqual(new DateTime(2019, 11, 25, 11, 22, 41), mid.TimeStamp); + Assert.AreEqual(913, mid.SyncTighteningId); + Assert.IsTrue(mid.SyncOverallStatus); + Assert.AreEqual(1, mid.SpindlesOrPressesStatus.Count); + Assert.AreEqual(SystemSubType.SystemRunningPresses, mid.SystemSubType); + Assert.AreEqual(21, mid.JobSequenceNumber); AssertEqualPackages(pack, mid); } @@ -167,26 +169,26 @@ public void Mid0101ByteRevision5() var mid = _midInterpreter.Parse(bytes); Assert.AreEqual(typeof(Mid0101), mid.GetType()); - Assert.IsNotNull(mid.NumberOfSpindlesOrPresses); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueOrForceMinLimit); - Assert.IsNotNull(mid.TorqueOrForceMaxLimit); - Assert.IsNotNull(mid.TorqueOrForceFinalTarget); - Assert.IsNotNull(mid.AngleOrStrokeMinLimit); - Assert.IsNotNull(mid.AngleOrStrokeMaxLimit); - Assert.IsNotNull(mid.FinalAngleOrStrokeTarget); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.TimeStamp); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.SyncOverallStatus); - Assert.IsNotNull(mid.SpindlesOrPressesStatus); - Assert.IsNotNull(mid.SystemSubType); - Assert.IsNotNull(mid.JobSequenceNumber); + Assert.AreEqual(1, mid.NumberOfSpindlesOrPresses); + Assert.AreEqual("BM3GA02111900601", mid.VinNumber.TrimEnd()); + Assert.AreEqual(3, mid.JobId); + Assert.AreEqual(3, mid.ParameterSetId); + Assert.AreEqual(1, mid.BatchSize); + Assert.AreEqual(1, mid.BatchCounter); + Assert.AreEqual(BatchStatus.Ok, mid.BatchStatus); + Assert.AreEqual(6.80m, mid.TorqueOrForceMinLimit); + Assert.AreEqual(9.20m, mid.TorqueOrForceMaxLimit); + Assert.AreEqual(8.00m, mid.TorqueOrForceFinalTarget); + Assert.AreEqual(0.00m, mid.AngleOrStrokeMinLimit); + Assert.AreEqual(0.15m, mid.AngleOrStrokeMaxLimit); + Assert.AreEqual(0.00m, mid.FinalAngleOrStrokeTarget); + Assert.AreEqual(new DateTime(2019, 11, 14, 14, 8, 5), mid.LastChangeInParameterSet); + Assert.AreEqual(new DateTime(2019, 11, 25, 11, 22, 41), mid.TimeStamp); + Assert.AreEqual(913, mid.SyncTighteningId); + Assert.IsTrue(mid.SyncOverallStatus); + Assert.AreEqual(1, mid.SpindlesOrPressesStatus.Count); + Assert.AreEqual(SystemSubType.SystemRunningPresses, mid.SystemSubType); + Assert.AreEqual(21, mid.JobSequenceNumber); AssertEqualPackages(bytes, mid); } @@ -198,27 +200,26 @@ public void Mid0101Revision6() var mid = _midInterpreter.Parse(pack); Assert.AreEqual(typeof(Mid0101), mid.GetType()); - Assert.IsNotNull(mid.NumberOfSpindlesOrPresses); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueOrForceMinLimit); - Assert.IsNotNull(mid.TorqueOrForceMaxLimit); - Assert.IsNotNull(mid.TorqueOrForceFinalTarget); - Assert.IsNotNull(mid.AngleOrStrokeMinLimit); - Assert.IsNotNull(mid.AngleOrStrokeMaxLimit); - Assert.IsTrue(mid.AngleOrStrokeMaxLimit < 0); - Assert.IsNotNull(mid.FinalAngleOrStrokeTarget); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.TimeStamp); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.SyncOverallStatus); - Assert.IsNotNull(mid.SpindlesOrPressesStatus); - Assert.IsNotNull(mid.SystemSubType); - Assert.IsNotNull(mid.JobSequenceNumber); + Assert.AreEqual(1, mid.NumberOfSpindlesOrPresses); + Assert.AreEqual("BM3GA02111900601", mid.VinNumber.TrimEnd()); + Assert.AreEqual(3, mid.JobId); + Assert.AreEqual(3, mid.ParameterSetId); + Assert.AreEqual(1, mid.BatchSize); + Assert.AreEqual(1, mid.BatchCounter); + Assert.AreEqual(BatchStatus.Ok, mid.BatchStatus); + Assert.AreEqual(6.80m, mid.TorqueOrForceMinLimit); + Assert.AreEqual(9.20m, mid.TorqueOrForceMaxLimit); + Assert.AreEqual(8.00m, mid.TorqueOrForceFinalTarget); + Assert.AreEqual(0.00m, mid.AngleOrStrokeMinLimit); + Assert.AreEqual(-0.15m, mid.AngleOrStrokeMaxLimit); + Assert.AreEqual(0.00m, mid.FinalAngleOrStrokeTarget); + Assert.AreEqual(new DateTime(2019, 11, 14, 14, 8, 5), mid.LastChangeInParameterSet); + Assert.AreEqual(new DateTime(2019, 11, 25, 11, 22, 41), mid.TimeStamp); + Assert.AreEqual(913, mid.SyncTighteningId); + Assert.IsTrue(mid.SyncOverallStatus); + Assert.AreEqual(1, mid.SpindlesOrPressesStatus.Count); + Assert.AreEqual(SystemSubType.SystemRunningPresses, mid.SystemSubType); + Assert.AreEqual(21, mid.JobSequenceNumber); AssertEqualPackages(pack, mid); } @@ -231,27 +232,26 @@ public void Mid0101ByteRevision6() var mid = _midInterpreter.Parse(bytes); Assert.AreEqual(typeof(Mid0101), mid.GetType()); - Assert.IsNotNull(mid.NumberOfSpindlesOrPresses); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueOrForceMinLimit); - Assert.IsNotNull(mid.TorqueOrForceMaxLimit); - Assert.IsNotNull(mid.TorqueOrForceFinalTarget); - Assert.IsNotNull(mid.AngleOrStrokeMinLimit); - Assert.IsNotNull(mid.AngleOrStrokeMaxLimit); - Assert.IsTrue(mid.AngleOrStrokeMaxLimit < 0); - Assert.IsNotNull(mid.FinalAngleOrStrokeTarget); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.TimeStamp); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.SyncOverallStatus); - Assert.IsNotNull(mid.SpindlesOrPressesStatus); - Assert.IsNotNull(mid.SystemSubType); - Assert.IsNotNull(mid.JobSequenceNumber); + Assert.AreEqual(1, mid.NumberOfSpindlesOrPresses); + Assert.AreEqual("BM3GA02111900601", mid.VinNumber.TrimEnd()); + Assert.AreEqual(3, mid.JobId); + Assert.AreEqual(3, mid.ParameterSetId); + Assert.AreEqual(1, mid.BatchSize); + Assert.AreEqual(1, mid.BatchCounter); + Assert.AreEqual(BatchStatus.Ok, mid.BatchStatus); + Assert.AreEqual(6.80m, mid.TorqueOrForceMinLimit); + Assert.AreEqual(9.20m, mid.TorqueOrForceMaxLimit); + Assert.AreEqual(8.00m, mid.TorqueOrForceFinalTarget); + Assert.AreEqual(0.00m, mid.AngleOrStrokeMinLimit); + Assert.AreEqual(-0.15m, mid.AngleOrStrokeMaxLimit); + Assert.AreEqual(0.00m, mid.FinalAngleOrStrokeTarget); + Assert.AreEqual(new DateTime(2019, 11, 14, 14, 8, 5), mid.LastChangeInParameterSet); + Assert.AreEqual(new DateTime(2019, 11, 25, 11, 22, 41), mid.TimeStamp); + Assert.AreEqual(913, mid.SyncTighteningId); + Assert.IsTrue(mid.SyncOverallStatus); + Assert.AreEqual(1, mid.SpindlesOrPressesStatus.Count); + Assert.AreEqual(SystemSubType.SystemRunningPresses, mid.SystemSubType); + Assert.AreEqual(21, mid.JobSequenceNumber); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/MultiSpindle/TestMid0104.cs b/src/MIDTesters.Core/MultiSpindle/TestMid0104.cs index e95ec455..123c3b2d 100644 --- a/src/MIDTesters.Core/MultiSpindle/TestMid0104.cs +++ b/src/MIDTesters.Core/MultiSpindle/TestMid0104.cs @@ -14,7 +14,7 @@ public void Mid0104Revision1() string pack = @"00320104 013009021015"; var mid = _midInterpreter.Parse(pack); - Assert.AreNotEqual(0, mid.RequestedResultIndex); + Assert.AreEqual(3009021015L, mid.RequestedResultIndex); AssertEqualPackages(pack, mid, true); } @@ -26,7 +26,7 @@ public void Mid0104ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.AreNotEqual(0, mid.RequestedResultIndex); + Assert.AreEqual(3009021015L, mid.RequestedResultIndex); AssertEqualPackages(bytes, mid, true); } } From 3995b85ce38f19a4eb4ba94afc8244adc9c7c492 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 29 Mar 2026 23:36:10 -0300 Subject: [PATCH 12/78] Updated open protocol commands tests --- .../OpenProtocolCommandsDisabled/TestMid0421.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MIDTesters.Core/OpenProtocolCommandsDisabled/TestMid0421.cs b/src/MIDTesters.Core/OpenProtocolCommandsDisabled/TestMid0421.cs index 7081d9cb..ee85e74b 100644 --- a/src/MIDTesters.Core/OpenProtocolCommandsDisabled/TestMid0421.cs +++ b/src/MIDTesters.Core/OpenProtocolCommandsDisabled/TestMid0421.cs @@ -14,7 +14,7 @@ public void Mid0421Revision1() string package = "00210421 1"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.DigitalInputStatus); + Assert.IsTrue(mid.DigitalInputStatus); AssertEqualPackages(package, mid, true); } @@ -26,7 +26,7 @@ public void Mid0421ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.DigitalInputStatus); + Assert.IsTrue(mid.DigitalInputStatus); AssertEqualPackages(bytes, mid, true); } } From 72d15b7de5c74308df55e4d70ccdd41af0545bf4 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 29 Mar 2026 23:36:26 -0300 Subject: [PATCH 13/78] Updated parameter set tests --- .../ParameterSet/TestMid0011.cs | 12 +- .../ParameterSet/TestMid0012.cs | 24 +-- .../ParameterSet/TestMid0013.cs | 144 +++++++++--------- .../ParameterSet/TestMid0015.cs | 64 ++++---- .../ParameterSet/TestMid0018.cs | 4 +- .../ParameterSet/TestMid0019.cs | 16 +- .../ParameterSet/TestMid0020.cs | 4 +- .../ParameterSet/TestMid0022.cs | 4 +- .../ParameterSet/TestMid2504.cs | 4 +- .../ParameterSet/TestMid2505.cs | 3 +- .../ParameterSet/TestMid2506.cs | 9 +- 11 files changed, 146 insertions(+), 142 deletions(-) diff --git a/src/MIDTesters.Core/ParameterSet/TestMid0011.cs b/src/MIDTesters.Core/ParameterSet/TestMid0011.cs index 754d9a4e..4c58d124 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid0011.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid0011.cs @@ -14,8 +14,8 @@ public void Mid0011Revision1() string pack = @"00290011 002001002"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.TotalParameterSets); - Assert.IsNotNull(mid.ParameterSets); + Assert.AreEqual(2, mid.TotalParameterSets); + Assert.AreEqual(2, mid.ParameterSets.Count); AssertEqualPackages(pack, mid, true); } @@ -27,8 +27,8 @@ public void Mid0011ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.TotalParameterSets); - Assert.IsNotNull(mid.ParameterSets); + Assert.AreEqual(2, mid.TotalParameterSets); + Assert.AreEqual(2, mid.ParameterSets.Count); AssertEqualPackages(bytes, mid, true); } @@ -38,8 +38,8 @@ public void Mid0011Revision1MaxParameters() string pack = @"30200011 999001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002002001002001002001002001003005"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.TotalParameterSets); - Assert.IsNotNull(mid.ParameterSets); + Assert.AreEqual(999, mid.TotalParameterSets); + Assert.AreEqual(999, mid.ParameterSets.Count); AssertEqualPackages(pack, mid, true); } } diff --git a/src/MIDTesters.Core/ParameterSet/TestMid0012.cs b/src/MIDTesters.Core/ParameterSet/TestMid0012.cs index 464056a1..e7def0e8 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid0012.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid0012.cs @@ -14,7 +14,7 @@ public void Mid0012Revision1() string pack = @"00230012 002"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.ParameterSetId); + Assert.AreEqual(2, mid.ParameterSetId); AssertEqualPackages(pack, mid, true); } @@ -26,7 +26,7 @@ public void Mid0012ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ParameterSetId); + Assert.AreEqual(2, mid.ParameterSetId); AssertEqualPackages(bytes, mid, true); } @@ -37,7 +37,7 @@ public void Mid0012Revision2() string pack = @"00230012002 002"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.ParameterSetId); + Assert.AreEqual(2, mid.ParameterSetId); AssertEqualPackages(pack, mid); } @@ -49,7 +49,7 @@ public void Mid0012ByteRevision2() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ParameterSetId); + Assert.AreEqual(2, mid.ParameterSetId); AssertEqualPackages(bytes, mid); } @@ -60,8 +60,8 @@ public void Mid0012Revision3() string pack = @"00310012003 00212345678"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.ParameterSetFileVersion); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(12345678, mid.ParameterSetFileVersion); AssertEqualPackages(pack, mid); } @@ -73,8 +73,8 @@ public void Mid0012ByteRevision3() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.ParameterSetFileVersion); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(12345678, mid.ParameterSetFileVersion); AssertEqualPackages(bytes, mid); } @@ -85,8 +85,8 @@ public void Mid0012Revision4() string pack = @"00310012004 00212345678"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.ParameterSetFileVersion); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(12345678, mid.ParameterSetFileVersion); AssertEqualPackages(pack, mid); } @@ -98,8 +98,8 @@ public void Mid0012ByteRevision4() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.ParameterSetFileVersion); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(12345678, mid.ParameterSetFileVersion); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/ParameterSet/TestMid0013.cs b/src/MIDTesters.Core/ParameterSet/TestMid0013.cs index 0ce19d6d..79ad1890 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid0013.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid0013.cs @@ -1,4 +1,6 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.ParameterSet; namespace MIDTesters.ParameterSet @@ -14,16 +16,16 @@ public void Mid0013Revision1() string pack = @"01040013 0100102Airbag1 0310403050012000600150007001400080036009007201000480"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.RotationDirection); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.MinTorque); - Assert.IsNotNull(mid.MaxTorque); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.MinAngle); - Assert.IsNotNull(mid.MaxAngle); - Assert.IsNotNull(mid.AngleFinalTarget); + Assert.AreEqual(1, mid.ParameterSetId); + Assert.AreEqual("Airbag1", mid.ParameterSetName.TrimEnd()); + Assert.AreEqual(RotationDirection.Clockwise, mid.RotationDirection); + Assert.AreEqual(3, mid.BatchSize); + Assert.AreEqual(12.00m, mid.MinTorque); + Assert.AreEqual(15.00m, mid.MaxTorque); + Assert.AreEqual(14.00m, mid.TorqueFinalTarget); + Assert.AreEqual(360, mid.MinAngle); + Assert.AreEqual(720, mid.MaxAngle); + Assert.AreEqual(480, mid.AngleFinalTarget); AssertEqualPackages(pack, mid, true); } @@ -35,16 +37,16 @@ public void Mid0013ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.RotationDirection); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.MinTorque); - Assert.IsNotNull(mid.MaxTorque); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.MinAngle); - Assert.IsNotNull(mid.MaxAngle); - Assert.IsNotNull(mid.AngleFinalTarget); + Assert.AreEqual(1, mid.ParameterSetId); + Assert.AreEqual("Airbag1", mid.ParameterSetName.TrimEnd()); + Assert.AreEqual(RotationDirection.Clockwise, mid.RotationDirection); + Assert.AreEqual(3, mid.BatchSize); + Assert.AreEqual(12.00m, mid.MinTorque); + Assert.AreEqual(15.00m, mid.MaxTorque); + Assert.AreEqual(14.00m, mid.TorqueFinalTarget); + Assert.AreEqual(360, mid.MinAngle); + Assert.AreEqual(720, mid.MaxAngle); + Assert.AreEqual(480, mid.AngleFinalTarget); AssertEqualPackages(bytes, mid, true); } @@ -55,18 +57,18 @@ public void Mid0013Revision2() string pack = @"01200013002 0100102Airbag1 03104030500120006001500070014000800360090072010004801102021112017854"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.RotationDirection); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.MinTorque); - Assert.IsNotNull(mid.MaxTorque); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.MinAngle); - Assert.IsNotNull(mid.MaxAngle); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.FirstTarget); - Assert.IsNotNull(mid.StartFinalAngle); + Assert.AreEqual(1, mid.ParameterSetId); + Assert.AreEqual("Airbag1", mid.ParameterSetName.TrimEnd()); + Assert.AreEqual(RotationDirection.Clockwise, mid.RotationDirection); + Assert.AreEqual(3, mid.BatchSize); + Assert.AreEqual(12.00m, mid.MinTorque); + Assert.AreEqual(15.00m, mid.MaxTorque); + Assert.AreEqual(14.00m, mid.TorqueFinalTarget); + Assert.AreEqual(360, mid.MinAngle); + Assert.AreEqual(720, mid.MaxAngle); + Assert.AreEqual(480, mid.AngleFinalTarget); + Assert.AreEqual(202.11m, mid.FirstTarget); + Assert.AreEqual(178.54m, mid.StartFinalAngle); AssertEqualPackages(pack, mid); } @@ -78,18 +80,18 @@ public void Mid0013ByteRevision2() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.RotationDirection); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.MinTorque); - Assert.IsNotNull(mid.MaxTorque); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.MinAngle); - Assert.IsNotNull(mid.MaxAngle); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.FirstTarget); - Assert.IsNotNull(mid.StartFinalAngle); + Assert.AreEqual(1, mid.ParameterSetId); + Assert.AreEqual("Airbag1", mid.ParameterSetName.TrimEnd()); + Assert.AreEqual(RotationDirection.Clockwise, mid.RotationDirection); + Assert.AreEqual(3, mid.BatchSize); + Assert.AreEqual(12.00m, mid.MinTorque); + Assert.AreEqual(15.00m, mid.MaxTorque); + Assert.AreEqual(14.00m, mid.TorqueFinalTarget); + Assert.AreEqual(360, mid.MinAngle); + Assert.AreEqual(720, mid.MaxAngle); + Assert.AreEqual(480, mid.AngleFinalTarget); + Assert.AreEqual(202.11m, mid.FirstTarget); + Assert.AreEqual(178.54m, mid.StartFinalAngle); AssertEqualPackages(bytes, mid); } @@ -100,19 +102,19 @@ public void Mid0013Revision5() string pack = @"01410013005 0100102Airbag1 03104030500120006001500070014000800360090072010004801102021112017854132001-05-29:12:34:33"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.RotationDirection); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.MinTorque); - Assert.IsNotNull(mid.MaxTorque); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.MinAngle); - Assert.IsNotNull(mid.MaxAngle); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.FirstTarget); - Assert.IsNotNull(mid.StartFinalAngle); - Assert.IsNotNull(mid.LastChangeInParameterSet); + Assert.AreEqual(1, mid.ParameterSetId); + Assert.AreEqual("Airbag1", mid.ParameterSetName.TrimEnd()); + Assert.AreEqual(RotationDirection.Clockwise, mid.RotationDirection); + Assert.AreEqual(3, mid.BatchSize); + Assert.AreEqual(12.00m, mid.MinTorque); + Assert.AreEqual(15.00m, mid.MaxTorque); + Assert.AreEqual(14.00m, mid.TorqueFinalTarget); + Assert.AreEqual(360, mid.MinAngle); + Assert.AreEqual(720, mid.MaxAngle); + Assert.AreEqual(480, mid.AngleFinalTarget); + Assert.AreEqual(202.11m, mid.FirstTarget); + Assert.AreEqual(178.54m, mid.StartFinalAngle); + Assert.AreEqual(new DateTime(2001, 5, 29, 12, 34, 33), mid.LastChangeInParameterSet); AssertEqualPackages(pack, mid); } @@ -124,19 +126,19 @@ public void Mid0013ByteRevision5() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.RotationDirection); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.MinTorque); - Assert.IsNotNull(mid.MaxTorque); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.MinAngle); - Assert.IsNotNull(mid.MaxAngle); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.FirstTarget); - Assert.IsNotNull(mid.StartFinalAngle); - Assert.IsNotNull(mid.LastChangeInParameterSet); + Assert.AreEqual(1, mid.ParameterSetId); + Assert.AreEqual("Airbag1", mid.ParameterSetName.TrimEnd()); + Assert.AreEqual(RotationDirection.Clockwise, mid.RotationDirection); + Assert.AreEqual(3, mid.BatchSize); + Assert.AreEqual(12.00m, mid.MinTorque); + Assert.AreEqual(15.00m, mid.MaxTorque); + Assert.AreEqual(14.00m, mid.TorqueFinalTarget); + Assert.AreEqual(360, mid.MinAngle); + Assert.AreEqual(720, mid.MaxAngle); + Assert.AreEqual(480, mid.AngleFinalTarget); + Assert.AreEqual(202.11m, mid.FirstTarget); + Assert.AreEqual(178.54m, mid.StartFinalAngle); + Assert.AreEqual(new DateTime(2001, 5, 29, 12, 34, 33), mid.LastChangeInParameterSet); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/ParameterSet/TestMid0015.cs b/src/MIDTesters.Core/ParameterSet/TestMid0015.cs index 713dc422..f8a96766 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid0015.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid0015.cs @@ -1,4 +1,6 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.ParameterSet; namespace MIDTesters.ParameterSet @@ -14,8 +16,8 @@ public void Mid0015Revision1() string package = "00420015001 0022017-06-02:09:54:09"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.LastChangeInParameterSet); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(new DateTime(2017, 6, 2, 9, 54, 9), mid.LastChangeInParameterSet); AssertEqualPackages(package, mid); } @@ -27,8 +29,8 @@ public void Mid0015ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.LastChangeInParameterSet); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(new DateTime(2017, 6, 2, 9, 54, 9), mid.LastChangeInParameterSet); AssertEqualPackages(bytes, mid); } @@ -39,19 +41,19 @@ public void Mid0015Revision2() string package = "01410015002 0100202Airbag parameter 032017-06-02:09:54:0904205040600510107010009080050050900001109999911003601200123413001006"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.RotationDirection); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.MinTorque); - Assert.IsNotNull(mid.MaxTorque); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.MinAngle); - Assert.IsNotNull(mid.MaxAngle); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.FirstTarget); - Assert.IsNotNull(mid.StartFinalAngle); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual("Airbag parameter", mid.ParameterSetName.TrimEnd()); + Assert.AreEqual(new DateTime(2017, 6, 2, 9, 54, 9), mid.LastChangeInParameterSet); + Assert.AreEqual(RotationDirection.Counterclockwise, mid.RotationDirection); + Assert.AreEqual(4, mid.BatchSize); + Assert.AreEqual(51.01m, mid.MinTorque); + Assert.AreEqual(100.09m, mid.MaxTorque); + Assert.AreEqual(50.05m, mid.TorqueFinalTarget); + Assert.AreEqual(1, mid.MinAngle); + Assert.AreEqual(99999, mid.MaxAngle); + Assert.AreEqual(360, mid.AngleFinalTarget); + Assert.AreEqual(12.34m, mid.FirstTarget); + Assert.AreEqual(10.06m, mid.StartFinalAngle); AssertEqualPackages(package, mid); } @@ -63,19 +65,19 @@ public void Mid0015ByteRevision2() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.RotationDirection); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.MinTorque); - Assert.IsNotNull(mid.MaxTorque); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.MinAngle); - Assert.IsNotNull(mid.MaxAngle); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.FirstTarget); - Assert.IsNotNull(mid.StartFinalAngle); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual("Airbag parameter", mid.ParameterSetName.TrimEnd()); + Assert.AreEqual(new DateTime(2017, 6, 2, 9, 54, 9), mid.LastChangeInParameterSet); + Assert.AreEqual(RotationDirection.Counterclockwise, mid.RotationDirection); + Assert.AreEqual(4, mid.BatchSize); + Assert.AreEqual(51.01m, mid.MinTorque); + Assert.AreEqual(100.09m, mid.MaxTorque); + Assert.AreEqual(50.05m, mid.TorqueFinalTarget); + Assert.AreEqual(1, mid.MinAngle); + Assert.AreEqual(99999, mid.MaxAngle); + Assert.AreEqual(360, mid.AngleFinalTarget); + Assert.AreEqual(12.34m, mid.FirstTarget); + Assert.AreEqual(10.06m, mid.StartFinalAngle); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/ParameterSet/TestMid0018.cs b/src/MIDTesters.Core/ParameterSet/TestMid0018.cs index e9755ed5..d5c2e3a6 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid0018.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid0018.cs @@ -14,7 +14,7 @@ public void Mid0018Revision1() string package = "00230018001 022"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.ParameterSetId); + Assert.AreEqual(22, mid.ParameterSetId); AssertEqualPackages(package, mid); } @@ -26,7 +26,7 @@ public void Mid0018ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ParameterSetId); + Assert.AreEqual(22, mid.ParameterSetId); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/ParameterSet/TestMid0019.cs b/src/MIDTesters.Core/ParameterSet/TestMid0019.cs index 5750551e..e36aab3a 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid0019.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid0019.cs @@ -14,8 +14,8 @@ public void Mid0019Revision1() string package = "00250019 77750"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.BatchSize); + Assert.AreEqual(777, mid.ParameterSetId); + Assert.AreEqual(50, mid.BatchSize); AssertEqualPackages(package, mid, true); } @@ -27,8 +27,8 @@ public void Mid0019ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.BatchSize); + Assert.AreEqual(777, mid.ParameterSetId); + Assert.AreEqual(50, mid.BatchSize); AssertEqualPackages(bytes, mid, true); } @@ -39,8 +39,8 @@ public void Mid0019Revision2() string package = "00270019002 7770050"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.BatchSize); + Assert.AreEqual(777, mid.ParameterSetId); + Assert.AreEqual(50, mid.BatchSize); AssertEqualPackages(package, mid); } @@ -52,8 +52,8 @@ public void Mid0019ByteRevision2() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.BatchSize); + Assert.AreEqual(777, mid.ParameterSetId); + Assert.AreEqual(50, mid.BatchSize); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/ParameterSet/TestMid0020.cs b/src/MIDTesters.Core/ParameterSet/TestMid0020.cs index 438d6a3f..b1d2c495 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid0020.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid0020.cs @@ -14,7 +14,7 @@ public void Mid0020Revision1() string package = "00230020 054"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.ParameterSetId); + Assert.AreEqual(54, mid.ParameterSetId); AssertEqualPackages(package, mid, true); } @@ -26,7 +26,7 @@ public void Mid0020ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ParameterSetId); + Assert.AreEqual(54, mid.ParameterSetId); AssertEqualPackages(bytes, mid, true); } } diff --git a/src/MIDTesters.Core/ParameterSet/TestMid0022.cs b/src/MIDTesters.Core/ParameterSet/TestMid0022.cs index cce1cf5d..756be6c3 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid0022.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid0022.cs @@ -15,7 +15,7 @@ public void Mid0022Revision1() var mid = _midInterpreter.Parse(package); Assert.IsTrue(mid.Header.NoAckFlag); - Assert.IsNotNull(mid.RelayStatus); + Assert.IsTrue(mid.RelayStatus); AssertEqualPackages(package, mid, true); } @@ -28,7 +28,7 @@ public void Mid0022ByteRevision1() var mid = _midInterpreter.Parse(bytes); Assert.IsTrue(mid.Header.NoAckFlag); - Assert.IsNotNull(mid.RelayStatus); + Assert.IsTrue(mid.RelayStatus); AssertEqualPackages(bytes, mid, true); } } diff --git a/src/MIDTesters.Core/ParameterSet/TestMid2504.cs b/src/MIDTesters.Core/ParameterSet/TestMid2504.cs index 73216cd1..3cfc61cb 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid2504.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid2504.cs @@ -14,7 +14,7 @@ public void Mid2504Revision1() string package = "00232504001 010"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.ParameterSetId); + Assert.AreEqual(10, mid.ParameterSetId); AssertEqualPackages(package, mid); } @@ -26,7 +26,7 @@ public void Mid2504ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ParameterSetId); + Assert.AreEqual(10, mid.ParameterSetId); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/ParameterSet/TestMid2505.cs b/src/MIDTesters.Core/ParameterSet/TestMid2505.cs index 1b8d8047..62abeda0 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid2505.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid2505.cs @@ -16,7 +16,6 @@ public void Mid2505Revision1() Assert.AreEqual(10, mid.ParameterSetId); Assert.AreEqual(2, mid.NumberOfParameterDataFields); - Assert.IsNotNull(mid.VariableDataFields); Assert.AreEqual(2, mid.VariableDataFields.Count); AssertEqualPackages(package, mid); } @@ -29,7 +28,7 @@ public void Mid2505ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ParameterSetId); + Assert.AreEqual(10, mid.ParameterSetId); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/ParameterSet/TestMid2506.cs b/src/MIDTesters.Core/ParameterSet/TestMid2506.cs index 3ee7b155..c6975e58 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid2506.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid2506.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.ParameterSet; namespace MIDTesters.Core.ParameterSet @@ -14,8 +15,8 @@ public void Mid2506Revision1() string package = "00272506001 0030201"; var mid = _midInterpreter.Parse(package); - Assert.AreNotEqual(0, mid.ProgramId); - Assert.AreNotEqual(0, (int)mid.NodeType); + Assert.AreEqual(30, mid.ProgramId); + Assert.AreEqual(NodeType.MultistepTighteningProgram, mid.NodeType); AssertEqualPackages(package, mid); } @@ -27,8 +28,8 @@ public void Mid2506ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.AreNotEqual(0, mid.ProgramId); - Assert.AreNotEqual(0, (int)mid.NodeType); + Assert.AreEqual(30, mid.ProgramId); + Assert.AreEqual(NodeType.MultistepTighteningProgram, mid.NodeType); AssertEqualPackages(bytes, mid); } } From 682b8adf0ed5358ddad67371bb908778ac3897df Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 29 Mar 2026 23:36:38 -0300 Subject: [PATCH 14/78] Updated PLC User data tests --- src/MIDTesters.Core/PLCUserData/TestMid0240.cs | 6 +++--- src/MIDTesters.Core/PLCUserData/TestMid0242.cs | 4 ++-- src/MIDTesters.Core/PLCUserData/TestMid0245.cs | 12 ++++++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/MIDTesters.Core/PLCUserData/TestMid0240.cs b/src/MIDTesters.Core/PLCUserData/TestMid0240.cs index 6ad4b586..16d3e368 100644 --- a/src/MIDTesters.Core/PLCUserData/TestMid0240.cs +++ b/src/MIDTesters.Core/PLCUserData/TestMid0240.cs @@ -14,7 +14,7 @@ public void Mid0240Revision1() string package = "00470240 My identifier less than 200"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.UserData); + Assert.AreEqual("My identifier less than 200", mid.UserData); AssertEqualPackages(package, mid, true); } @@ -26,7 +26,7 @@ public void Mid0240ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.UserData); + Assert.AreEqual("My identifier less than 200", mid.UserData); AssertEqualPackages(bytes, mid, true); } @@ -37,7 +37,7 @@ public void Mid0240ShouldTrucanteUserData() userData += userData; //double it to get 208 characters var mid0240 = new Mid0240() { UserData = userData }; - Assert.IsNotNull(mid0240.UserData); + Assert.AreEqual(208, mid0240.UserData.Length); Assert.IsTrue(mid0240.Pack().Length == 220); } } diff --git a/src/MIDTesters.Core/PLCUserData/TestMid0242.cs b/src/MIDTesters.Core/PLCUserData/TestMid0242.cs index b5541b93..fe142ae3 100644 --- a/src/MIDTesters.Core/PLCUserData/TestMid0242.cs +++ b/src/MIDTesters.Core/PLCUserData/TestMid0242.cs @@ -15,7 +15,7 @@ public void Mid0242Revision1() var mid = _midInterpreter.Parse(package); Assert.IsTrue(mid.Header.NoAckFlag); - Assert.IsNotNull(mid.UserData); + Assert.AreEqual("My identifier less than", mid.UserData); AssertEqualPackages(package, mid, true); } @@ -28,7 +28,7 @@ public void Mid0242ByteRevision1() var mid = _midInterpreter.Parse(bytes); Assert.IsTrue(mid.Header.NoAckFlag); - Assert.IsNotNull(mid.UserData); + Assert.AreEqual("My identifier less than", mid.UserData); AssertEqualPackages(bytes, mid, true); } } diff --git a/src/MIDTesters.Core/PLCUserData/TestMid0245.cs b/src/MIDTesters.Core/PLCUserData/TestMid0245.cs index 72f076af..6beea3d2 100644 --- a/src/MIDTesters.Core/PLCUserData/TestMid0245.cs +++ b/src/MIDTesters.Core/PLCUserData/TestMid0245.cs @@ -14,8 +14,8 @@ public void Mid0245Revision1() string package = "00460245 022My identifier less than"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.Offset); - Assert.IsNotNull(mid.UserData); + Assert.AreEqual(22, mid.Offset); + Assert.AreEqual("My identifier less than", mid.UserData); AssertEqualPackages(package, mid, true); } @@ -27,8 +27,8 @@ public void Mid0245ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.Offset); - Assert.IsNotNull(mid.UserData); + Assert.AreEqual(22, mid.Offset); + Assert.AreEqual("My identifier less than", mid.UserData); AssertEqualPackages(bytes, mid, true); } @@ -39,8 +39,8 @@ public void Mid0245ShouldTruncateUserData() userData += userData; //double it to get 208 characters var mid0245 = new Mid0245(2) { Offset = 0, UserData = userData }; - Assert.IsNotNull(mid0245.Offset); - Assert.IsNotNull(mid0245.UserData); + Assert.AreEqual(0, mid0245.Offset); + Assert.AreEqual(208, mid0245.UserData.Length); Assert.IsTrue(mid0245.Pack().Length == 223); } } From dd012e17ee8f3f888ffe02880ee995b8d870cca5 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 29 Mar 2026 23:37:34 -0300 Subject: [PATCH 15/78] Updated power macs tests --- src/MIDTesters.Core/PowerMACS/TestMid0105.cs | 8 +-- src/MIDTesters.Core/PowerMACS/TestMid0106.cs | 62 ++++++++++---------- src/MIDTesters.Core/PowerMACS/TestMid0107.cs | 62 ++++++++++---------- src/MIDTesters.Core/PowerMACS/TestMid0108.cs | 4 +- 4 files changed, 70 insertions(+), 66 deletions(-) diff --git a/src/MIDTesters.Core/PowerMACS/TestMid0105.cs b/src/MIDTesters.Core/PowerMACS/TestMid0105.cs index b89fdc2b..7f349ca2 100644 --- a/src/MIDTesters.Core/PowerMACS/TestMid0105.cs +++ b/src/MIDTesters.Core/PowerMACS/TestMid0105.cs @@ -61,7 +61,7 @@ public void Mid0105Revision3() var mid = _midInterpreter.Parse(pack); Assert.IsNotNull(mid.DataNumberSystem); - Assert.IsNotNull(mid.SendOnlyNewData); + Assert.IsTrue(mid.SendOnlyNewData); AssertEqualPackages(pack, mid); } @@ -74,7 +74,7 @@ public void Mid0105ByteRevision3() var mid = _midInterpreter.Parse(bytes); Assert.IsNotNull(mid.DataNumberSystem); - Assert.IsNotNull(mid.SendOnlyNewData); + Assert.IsTrue(mid.SendOnlyNewData); AssertEqualPackages(bytes, mid); } @@ -86,7 +86,7 @@ public void Mid0105Revision4() var mid = _midInterpreter.Parse(pack); Assert.IsNotNull(mid.DataNumberSystem); - Assert.IsNotNull(mid.SendOnlyNewData); + Assert.IsTrue(mid.SendOnlyNewData); AssertEqualPackages(pack, mid); } @@ -99,7 +99,7 @@ public void Mid0105ByteRevision4() var mid = _midInterpreter.Parse(bytes); Assert.IsNotNull(mid.DataNumberSystem); - Assert.IsNotNull(mid.SendOnlyNewData); + Assert.IsTrue(mid.SendOnlyNewData); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/PowerMACS/TestMid0106.cs b/src/MIDTesters.Core/PowerMACS/TestMid0106.cs index d6d359cc..21d04698 100644 --- a/src/MIDTesters.Core/PowerMACS/TestMid0106.cs +++ b/src/MIDTesters.Core/PowerMACS/TestMid0106.cs @@ -1,5 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.PowerMACS; +using System; namespace MIDTesters.PowerMACS { @@ -14,21 +16,21 @@ public void Mid0106Revision1() string pack = @"05050106 010502010300000381270401050231 062017-05-25:09:51:38071108Ap.320Nm Diant.P11 091101119BM384069HB066171 1204130114115116117329.9091835.854019360.00020310.000219999.002200.0000130214115116117328.73618-06.10219360.00020310.000219999.002200.0000130314115116117356.04518763.97619370.00020304.000219999.002200.0000130414115116117355.40718380.87219370.00020304.000219999.002200.00002302Data No Station I 100000027897Free No 1 I 100000000002"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.TotalNumberOfMessages); - Assert.IsNotNull(mid.MessageNumber); - Assert.IsNotNull(mid.DataNumberSystem); - Assert.IsNotNull(mid.StationNumber); - Assert.IsNotNull(mid.StationName); - Assert.IsNotNull(mid.Time); - Assert.IsNotNull(mid.ModeNumber); - Assert.IsNotNull(mid.ModeName); - Assert.IsNotNull(mid.SimpleStatus); - Assert.IsNotNull(mid.PMStatus); - Assert.IsNotNull(mid.WpId); - Assert.IsNotNull(mid.NumberOfBolts); - Assert.IsNotNull(mid.BoltsData); - Assert.IsNotNull(mid.TotalSpecialValues); - Assert.IsNotNull(mid.SpecialValues); + Assert.AreEqual(5, mid.TotalNumberOfMessages); + Assert.AreEqual(1, mid.MessageNumber); + Assert.AreEqual(38127, mid.DataNumberSystem); + Assert.AreEqual(1, mid.StationNumber); + Assert.AreEqual("0231", mid.StationName.TrimEnd()); + Assert.AreEqual(new DateTime(2017, 5, 25, 9, 51, 38), mid.Time); + Assert.AreEqual(11, mid.ModeNumber); + Assert.AreEqual("Ap.320Nm Diant.P11", mid.ModeName.TrimEnd()); + Assert.IsTrue(mid.SimpleStatus); + Assert.AreEqual(PowerMacsStatus.Okr, mid.PMStatus); + Assert.AreEqual("9BM384069HB066171", mid.WpId.TrimEnd()); + Assert.AreEqual(4, mid.NumberOfBolts); + Assert.AreEqual(4, mid.BoltsData.Count); + Assert.AreEqual(2, mid.TotalSpecialValues); + Assert.AreEqual(2, mid.SpecialValues.Count); AssertEqualPackages(pack, mid, true); } @@ -40,21 +42,21 @@ public void Mid0106ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.TotalNumberOfMessages); - Assert.IsNotNull(mid.MessageNumber); - Assert.IsNotNull(mid.DataNumberSystem); - Assert.IsNotNull(mid.StationNumber); - Assert.IsNotNull(mid.StationName); - Assert.IsNotNull(mid.Time); - Assert.IsNotNull(mid.ModeNumber); - Assert.IsNotNull(mid.ModeName); - Assert.IsNotNull(mid.SimpleStatus); - Assert.IsNotNull(mid.PMStatus); - Assert.IsNotNull(mid.WpId); - Assert.IsNotNull(mid.NumberOfBolts); - Assert.IsNotNull(mid.BoltsData); - Assert.IsNotNull(mid.TotalSpecialValues); - Assert.IsNotNull(mid.SpecialValues); + Assert.AreEqual(5, mid.TotalNumberOfMessages); + Assert.AreEqual(1, mid.MessageNumber); + Assert.AreEqual(38127, mid.DataNumberSystem); + Assert.AreEqual(1, mid.StationNumber); + Assert.AreEqual("0231", mid.StationName.TrimEnd()); + Assert.AreEqual(new DateTime(2017, 5, 25, 9, 51, 38), mid.Time); + Assert.AreEqual(11, mid.ModeNumber); + Assert.AreEqual("Ap.320Nm Diant.P11", mid.ModeName.TrimEnd()); + Assert.IsTrue(mid.SimpleStatus); + Assert.AreEqual(PowerMacsStatus.Okr, mid.PMStatus); + Assert.AreEqual("9BM384069HB066171", mid.WpId.TrimEnd()); + Assert.AreEqual(4, mid.NumberOfBolts); + Assert.AreEqual(4, mid.BoltsData.Count); + Assert.AreEqual(2, mid.TotalSpecialValues); + Assert.AreEqual(2, mid.SpecialValues.Count); AssertEqualPackages(bytes, mid, true); } } diff --git a/src/MIDTesters.Core/PowerMACS/TestMid0107.cs b/src/MIDTesters.Core/PowerMACS/TestMid0107.cs index cf2045a3..c256b655 100644 --- a/src/MIDTesters.Core/PowerMACS/TestMid0107.cs +++ b/src/MIDTesters.Core/PowerMACS/TestMid0107.cs @@ -1,5 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.PowerMACS; +using System; namespace MIDTesters.PowerMACS { @@ -14,21 +16,21 @@ public void Mid0107Revision1() string pack = @"03510107001 010202020300000381270401052017-05-25:09:51:3806000107My first bolt 08Ap.320Nm Diant.P11 09310 11E3211202Variable 1 I 1234567Variable 2 F 9999.9913002141Step Variable name 1I 765432101Step Variable name 2F 11.1234021501Special Value 1 S 13Got 13 digits01"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.TotalNumberOfMessages); - Assert.IsNotNull(mid.MessageNumber); - Assert.IsNotNull(mid.DataNumberSystem); - Assert.IsNotNull(mid.StationNumber); - Assert.IsNotNull(mid.Time); - Assert.IsNotNull(mid.BoltNumber); - Assert.IsNotNull(mid.BoltName); - Assert.IsNotNull(mid.ProgramName); - Assert.IsNotNull(mid.PowerMacsStatus); - Assert.IsNotNull(mid.Errors); - Assert.IsNotNull(mid.CustomerErrorCode); - Assert.IsNotNull(mid.BoltResults); - Assert.IsNotNull(mid.StepResults); - Assert.IsNotNull(mid.AllStepDataSent); - Assert.IsNotNull(mid.SpecialValues); + Assert.AreEqual(2, mid.TotalNumberOfMessages); + Assert.AreEqual(2, mid.MessageNumber); + Assert.AreEqual(38127, mid.DataNumberSystem); + Assert.AreEqual(1, mid.StationNumber); + Assert.AreEqual(new DateTime(2017, 5, 25, 9, 51, 38), mid.Time); + Assert.AreEqual(1, mid.BoltNumber); + Assert.AreEqual("My first bolt", mid.BoltName.TrimEnd()); + Assert.AreEqual("Ap.320Nm Diant.P11", mid.ProgramName.TrimEnd()); + Assert.AreEqual(PowerMacsStatus.TermNok, mid.PowerMacsStatus); + Assert.AreEqual(" ", mid.Errors); + Assert.AreEqual("E321", mid.CustomerErrorCode); + Assert.AreEqual(2, mid.BoltResults.Count); + Assert.AreEqual(2, mid.StepResults.Count); + Assert.IsTrue(mid.AllStepDataSent); + Assert.AreEqual(1, mid.SpecialValues.Count); AssertEqualPackages(pack, mid); } @@ -40,21 +42,21 @@ public void Mid0107ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.TotalNumberOfMessages); - Assert.IsNotNull(mid.MessageNumber); - Assert.IsNotNull(mid.DataNumberSystem); - Assert.IsNotNull(mid.StationNumber); - Assert.IsNotNull(mid.Time); - Assert.IsNotNull(mid.BoltNumber); - Assert.IsNotNull(mid.BoltName); - Assert.IsNotNull(mid.ProgramName); - Assert.IsNotNull(mid.PowerMacsStatus); - Assert.IsNotNull(mid.Errors); - Assert.IsNotNull(mid.CustomerErrorCode); - Assert.IsNotNull(mid.BoltResults); - Assert.IsNotNull(mid.StepResults); - Assert.IsNotNull(mid.AllStepDataSent); - Assert.IsNotNull(mid.SpecialValues); + Assert.AreEqual(2, mid.TotalNumberOfMessages); + Assert.AreEqual(2, mid.MessageNumber); + Assert.AreEqual(38127, mid.DataNumberSystem); + Assert.AreEqual(1, mid.StationNumber); + Assert.AreEqual(new DateTime(2017, 5, 25, 9, 51, 38), mid.Time); + Assert.AreEqual(1, mid.BoltNumber); + Assert.AreEqual("My first bolt", mid.BoltName.TrimEnd()); + Assert.AreEqual("Ap.320Nm Diant.P11", mid.ProgramName.TrimEnd()); + Assert.AreEqual(PowerMacsStatus.TermNok, mid.PowerMacsStatus); + Assert.AreEqual(" ", mid.Errors); + Assert.AreEqual("E321", mid.CustomerErrorCode); + Assert.AreEqual(2, mid.BoltResults.Count); + Assert.AreEqual(2, mid.StepResults.Count); + Assert.IsTrue(mid.AllStepDataSent); + Assert.AreEqual(1, mid.SpecialValues.Count); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/PowerMACS/TestMid0108.cs b/src/MIDTesters.Core/PowerMACS/TestMid0108.cs index 5cec81c2..edfde372 100644 --- a/src/MIDTesters.Core/PowerMACS/TestMid0108.cs +++ b/src/MIDTesters.Core/PowerMACS/TestMid0108.cs @@ -14,7 +14,7 @@ public void Mid0108AllRevisions() string package = "00210108002 1"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.BoltData); + Assert.IsTrue(mid.BoltData); AssertEqualPackages(package, mid); } @@ -26,7 +26,7 @@ public void Mid0108ByteAllRevisions() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.BoltData); + Assert.IsTrue(mid.BoltData); AssertEqualPackages(bytes, mid); } } From 036cea0553d33985e89c1592f549aaf5dd083cdf Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 29 Mar 2026 23:37:46 -0300 Subject: [PATCH 16/78] Updated statistics tests --- src/MIDTesters.Core/Statistic/TestMid0300.cs | 9 ++-- src/MIDTesters.Core/Statistic/TestMid0301.cs | 57 ++++++++++---------- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/src/MIDTesters.Core/Statistic/TestMid0300.cs b/src/MIDTesters.Core/Statistic/TestMid0300.cs index a4870569..0505474d 100644 --- a/src/MIDTesters.Core/Statistic/TestMid0300.cs +++ b/src/MIDTesters.Core/Statistic/TestMid0300.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.Statistic; namespace MIDTesters.Statistic @@ -14,8 +15,8 @@ public void Mid0300Revision1() string package = "00290300 010020202"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.HistogramType); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(HistogramType.Current, mid.HistogramType); AssertEqualPackages(package, mid, true); } @@ -27,8 +28,8 @@ public void Mid0300ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.HistogramType); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(HistogramType.Current, mid.HistogramType); AssertEqualPackages(bytes, mid, true); } } diff --git a/src/MIDTesters.Core/Statistic/TestMid0301.cs b/src/MIDTesters.Core/Statistic/TestMid0301.cs index 08bb0f9f..3de04596 100644 --- a/src/MIDTesters.Core/Statistic/TestMid0301.cs +++ b/src/MIDTesters.Core/Statistic/TestMid0301.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.Statistic; namespace MIDTesters.Statistic @@ -14,20 +15,20 @@ public void Mid0301Revision1() string package = "01070301 010020205031234560465432105999999061111072222083333094444105555116666127777138888149999"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.HistogramType); - Assert.IsNotNull(mid.SigmaHistogram); - Assert.IsNotNull(mid.MeanValueHistogram); - Assert.IsNotNull(mid.ClassRange); - Assert.IsNotNull(mid.FirstBar); - Assert.IsNotNull(mid.SecondBar); - Assert.IsNotNull(mid.ThirdBar); - Assert.IsNotNull(mid.FourthBar); - Assert.IsNotNull(mid.FifthBar); - Assert.IsNotNull(mid.SixthBar); - Assert.IsNotNull(mid.SeventhBar); - Assert.IsNotNull(mid.EighthBar); - Assert.IsNotNull(mid.NinethBar); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(HistogramType.RundownAngle, mid.HistogramType); + Assert.AreEqual(1234.56m, mid.SigmaHistogram); + Assert.AreEqual(6543.21m, mid.MeanValueHistogram); + Assert.AreEqual(9999.99m, mid.ClassRange); + Assert.AreEqual(1111, mid.FirstBar); + Assert.AreEqual(2222, mid.SecondBar); + Assert.AreEqual(3333, mid.ThirdBar); + Assert.AreEqual(4444, mid.FourthBar); + Assert.AreEqual(5555, mid.FifthBar); + Assert.AreEqual(6666, mid.SixthBar); + Assert.AreEqual(7777, mid.SeventhBar); + Assert.AreEqual(8888, mid.EighthBar); + Assert.AreEqual(9999, mid.NinethBar); AssertEqualPackages(package, mid, true); } @@ -39,20 +40,20 @@ public void Mid0301ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.HistogramType); - Assert.IsNotNull(mid.SigmaHistogram); - Assert.IsNotNull(mid.MeanValueHistogram); - Assert.IsNotNull(mid.ClassRange); - Assert.IsNotNull(mid.FirstBar); - Assert.IsNotNull(mid.SecondBar); - Assert.IsNotNull(mid.ThirdBar); - Assert.IsNotNull(mid.FourthBar); - Assert.IsNotNull(mid.FifthBar); - Assert.IsNotNull(mid.SixthBar); - Assert.IsNotNull(mid.SeventhBar); - Assert.IsNotNull(mid.EighthBar); - Assert.IsNotNull(mid.NinethBar); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(HistogramType.RundownAngle, mid.HistogramType); + Assert.AreEqual(1234.56m, mid.SigmaHistogram); + Assert.AreEqual(6543.21m, mid.MeanValueHistogram); + Assert.AreEqual(9999.99m, mid.ClassRange); + Assert.AreEqual(1111, mid.FirstBar); + Assert.AreEqual(2222, mid.SecondBar); + Assert.AreEqual(3333, mid.ThirdBar); + Assert.AreEqual(4444, mid.FourthBar); + Assert.AreEqual(5555, mid.FifthBar); + Assert.AreEqual(6666, mid.SixthBar); + Assert.AreEqual(7777, mid.SeventhBar); + Assert.AreEqual(8888, mid.EighthBar); + Assert.AreEqual(9999, mid.NinethBar); AssertEqualPackages(bytes, mid, true); } } From f5c76510d86d707ddc22386b7c6ba2d439084884 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 29 Mar 2026 23:37:55 -0300 Subject: [PATCH 17/78] Updated tightening tests --- src/MIDTesters.Core/Tightening/TestMid0061.cs | 2551 +++++++++-------- src/MIDTesters.Core/Tightening/TestMid0064.cs | 8 +- src/MIDTesters.Core/Tightening/TestMid0065.cs | 1518 +++++----- src/MIDTesters.Core/Tightening/TestMid0066.cs | 6 - src/MIDTesters.Core/Tightening/TestMid0902.cs | 29 +- 5 files changed, 2095 insertions(+), 2017 deletions(-) diff --git a/src/MIDTesters.Core/Tightening/TestMid0061.cs b/src/MIDTesters.Core/Tightening/TestMid0061.cs index 700cae83..739dd7f3 100644 --- a/src/MIDTesters.Core/Tightening/TestMid0061.cs +++ b/src/MIDTesters.Core/Tightening/TestMid0061.cs @@ -1,4 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; +using System; using OpenProtocolInterpreter.Tightening; using System.Collections.Generic; @@ -16,29 +18,29 @@ public void Mid0061Revision1() var mid = _midInterpreter.Parse(pack); Assert.AreEqual(typeof(Mid0061), mid.GetType()); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.TorqueControllerName); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.TorqueMinLimit); - Assert.IsNotNull(mid.TorqueMaxLimit); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.AngleMinLimit); - Assert.IsNotNull(mid.AngleMaxLimit); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TighteningId); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("airbag7 ", mid.TorqueControllerName); + Assert.AreEqual("KPOL3456JKLO897 ", mid.VinNumber); + Assert.AreEqual(0, mid.JobId); + Assert.AreEqual(3, mid.ParameterSetId); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.IsFalse(mid.TighteningStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.AngleStatus); + Assert.AreEqual(8.40m, mid.TorqueMinLimit); + Assert.AreEqual(14m, mid.TorqueMaxLimit); + Assert.AreEqual(12m, mid.TorqueFinalTarget); + Assert.AreEqual(7.39m, mid.Torque); + Assert.AreEqual(0, mid.AngleMinLimit); + Assert.AreEqual(9999, mid.AngleMaxLimit); + Assert.AreEqual(0, mid.AngleFinalTarget); + Assert.AreEqual(0, mid.Angle); + Assert.AreEqual(new DateTime(2001, 6, 2, 9, 54, 9), mid.Timestamp); + Assert.AreEqual(new DateTime(2001, 5, 29, 12, 34, 33), mid.LastChangeInParameterSet); + Assert.AreEqual(BatchStatus.Ok, mid.BatchStatus); + Assert.AreEqual(345675, mid.TighteningId); AssertEqualPackages(pack, mid); } @@ -51,29 +53,29 @@ public void Mid0061ByteRevision1() var mid = _midInterpreter.Parse(bytes); Assert.AreEqual(typeof(Mid0061), mid.GetType()); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.TorqueControllerName); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.TorqueMinLimit); - Assert.IsNotNull(mid.TorqueMaxLimit); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.AngleMinLimit); - Assert.IsNotNull(mid.AngleMaxLimit); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TighteningId); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("airbag7 ", mid.TorqueControllerName); + Assert.AreEqual("KPOL3456JKLO897 ", mid.VinNumber); + Assert.AreEqual(0, mid.JobId); + Assert.AreEqual(3, mid.ParameterSetId); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.IsFalse(mid.TighteningStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.AngleStatus); + Assert.AreEqual(8.40m, mid.TorqueMinLimit); + Assert.AreEqual(14m, mid.TorqueMaxLimit); + Assert.AreEqual(12m, mid.TorqueFinalTarget); + Assert.AreEqual(7.39m, mid.Torque); + Assert.AreEqual(0, mid.AngleMinLimit); + Assert.AreEqual(9999, mid.AngleMaxLimit); + Assert.AreEqual(0, mid.AngleFinalTarget); + Assert.AreEqual(0, mid.Angle); + Assert.AreEqual(new DateTime(2001, 6, 2, 9, 54, 9), mid.Timestamp); + Assert.AreEqual(new DateTime(2001, 5, 29, 12, 34, 33), mid.LastChangeInParameterSet); + Assert.AreEqual(BatchStatus.Ok, mid.BatchStatus); + Assert.AreEqual(345675, mid.TighteningId); AssertEqualPackages(bytes, mid); } @@ -85,49 +87,52 @@ public void Mid0061Revision2() var mid = _midInterpreter.Parse(pack); Assert.AreEqual(typeof(Mid0061), mid.GetType()); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.TorqueControllerName); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(0, mid.CellId); + Assert.AreEqual(0, mid.ChannelId); + Assert.AreEqual("RA ST6.2 ETV100 ", mid.TorqueControllerName); + Assert.AreEqual(" ", mid.VinNumber); + Assert.AreEqual(0, mid.JobId); + Assert.AreEqual(1, mid.ParameterSetId); + Assert.AreEqual(Strategy.TorqueControlAndAngleMonitoring, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.IsFalse(mid.TighteningStatus); + Assert.AreEqual(BatchStatus.NotUsed, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.TorqueMinLimit); - Assert.IsNotNull(mid.TorqueMaxLimit); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.AngleMinLimit); - Assert.IsNotNull(mid.AngleMaxLimit); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringMin); - Assert.IsNotNull(mid.CurrentMonitoringMax); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapMin); - Assert.IsNotNull(mid.SelftapMax); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMin); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMax); - Assert.IsNotNull(mid.PrevailTorque); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.LastChangeInParameterSet); + Assert.AreEqual(34m, mid.TorqueMinLimit); + Assert.AreEqual(46m, mid.TorqueMaxLimit); + Assert.AreEqual(40m, mid.TorqueFinalTarget); + Assert.AreEqual(5.05m, mid.Torque); + Assert.AreEqual(20, mid.AngleMinLimit); + Assert.AreEqual(420, mid.AngleMaxLimit); + Assert.AreEqual(0, mid.AngleFinalTarget); + Assert.AreEqual(0, mid.Angle); + Assert.AreEqual(100, mid.RundownAngleMin); + Assert.AreEqual(850, mid.RundownAngleMax); + Assert.AreEqual(4, mid.RundownAngle); + Assert.AreEqual(0, mid.CurrentMonitoringMin); + Assert.AreEqual(150, mid.CurrentMonitoringMax); + Assert.AreEqual(0, mid.CurrentMonitoringValue); + Assert.AreEqual(0m, mid.SelftapMin); + Assert.AreEqual(9999m, mid.SelftapMax); + Assert.AreEqual(0m, mid.SelftapTorque); + Assert.AreEqual(0m, mid.PrevailTorqueMonitoringMin); + Assert.AreEqual(0m, mid.PrevailTorqueMonitoringMax); + Assert.AreEqual(0m, mid.PrevailTorque); + Assert.AreEqual(184887, mid.TighteningId); + Assert.AreEqual(0, mid.JobSequenceNumber); + Assert.AreEqual(0, mid.SyncTighteningId); + Assert.AreEqual(" C0761275", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2020, 6, 25, 1, 4, 39), mid.Timestamp); + Assert.AreEqual(new DateTime(2020, 6, 24, 10, 48, 53), mid.LastChangeInParameterSet); AssertEqualPackages(pack, mid); } @@ -171,49 +176,52 @@ public void Mid0061ByteRevision2() var mid = _midInterpreter.Parse(bytes.ToArray()); Assert.AreEqual(typeof(Mid0061), mid.GetType()); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.TorqueControllerName); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("airbag7 ", mid.TorqueControllerName); + Assert.AreEqual("KPOL3456JKLO897 ", mid.VinNumber); + Assert.AreEqual(6, mid.JobId); + Assert.AreEqual(3, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleReverse, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.IsFalse(mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.TorqueMinLimit); - Assert.IsNotNull(mid.TorqueMaxLimit); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.AngleMinLimit); - Assert.IsNotNull(mid.AngleMaxLimit); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringMin); - Assert.IsNotNull(mid.CurrentMonitoringMax); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapMin); - Assert.IsNotNull(mid.SelftapMax); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMin); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMax); - Assert.IsNotNull(mid.PrevailTorque); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.LastChangeInParameterSet); + Assert.AreEqual(8.40m, mid.TorqueMinLimit); + Assert.AreEqual(14m, mid.TorqueMaxLimit); + Assert.AreEqual(12m, mid.TorqueFinalTarget); + Assert.AreEqual(7.39m, mid.Torque); + Assert.AreEqual(0, mid.AngleMinLimit); + Assert.AreEqual(9999, mid.AngleMaxLimit); + Assert.AreEqual(0, mid.AngleFinalTarget); + Assert.AreEqual(0, mid.Angle); + Assert.AreEqual(0, mid.RundownAngleMin); + Assert.AreEqual(9999, mid.RundownAngleMax); + Assert.AreEqual(5000, mid.RundownAngle); + Assert.AreEqual(0, mid.CurrentMonitoringMin); + Assert.AreEqual(50, mid.CurrentMonitoringMax); + Assert.AreEqual(45, mid.CurrentMonitoringValue); + Assert.AreEqual(0.10m, mid.SelftapMin); + Assert.AreEqual(1.25m, mid.SelftapMax); + Assert.AreEqual(5.48m, mid.SelftapTorque); + Assert.AreEqual(0.10m, mid.PrevailTorqueMonitoringMin); + Assert.AreEqual(9999m, mid.PrevailTorqueMonitoringMax); + Assert.AreEqual(5555m, mid.PrevailTorque); + Assert.AreEqual(4294967295, mid.TighteningId); + Assert.AreEqual(65500, mid.JobSequenceNumber); + Assert.AreEqual(60535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123456", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2001, 6, 2, 9, 54, 9), mid.Timestamp); + Assert.AreEqual(new DateTime(2001, 5, 29, 12, 34, 33), mid.LastChangeInParameterSet); AssertEqualPackages(bytes, mid); } @@ -225,52 +233,55 @@ public void Mid0061Revision3() var mid = _midInterpreter.Parse(pack); Assert.AreEqual(typeof(Mid0061), mid.GetType()); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.TorqueControllerName); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(0, mid.CellId); + Assert.AreEqual(0, mid.ChannelId); + Assert.AreEqual("RA ST6.2 ETV100 ", mid.TorqueControllerName); + Assert.AreEqual(" ", mid.VinNumber); + Assert.AreEqual(0, mid.JobId); + Assert.AreEqual(1, mid.ParameterSetId); + Assert.AreEqual(Strategy.TorqueControlAndAngleMonitoring, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.IsFalse(mid.TighteningStatus); + Assert.AreEqual(BatchStatus.NotUsed, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.TorqueMinLimit); - Assert.IsNotNull(mid.TorqueMaxLimit); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.AngleMinLimit); - Assert.IsNotNull(mid.AngleMaxLimit); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringMin); - Assert.IsNotNull(mid.CurrentMonitoringMax); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapMin); - Assert.IsNotNull(mid.SelftapMax); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMin); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMax); - Assert.IsNotNull(mid.PrevailTorque); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); + Assert.AreEqual(34m, mid.TorqueMinLimit); + Assert.AreEqual(46m, mid.TorqueMaxLimit); + Assert.AreEqual(40m, mid.TorqueFinalTarget); + Assert.AreEqual(5.05m, mid.Torque); + Assert.AreEqual(20, mid.AngleMinLimit); + Assert.AreEqual(420, mid.AngleMaxLimit); + Assert.AreEqual(0, mid.AngleFinalTarget); + Assert.AreEqual(0, mid.Angle); + Assert.AreEqual(100, mid.RundownAngleMin); + Assert.AreEqual(850, mid.RundownAngleMax); + Assert.AreEqual(4, mid.RundownAngle); + Assert.AreEqual(0, mid.CurrentMonitoringMin); + Assert.AreEqual(150, mid.CurrentMonitoringMax); + Assert.AreEqual(0, mid.CurrentMonitoringValue); + Assert.AreEqual(0m, mid.SelftapMin); + Assert.AreEqual(9999m, mid.SelftapMax); + Assert.AreEqual(0m, mid.SelftapTorque); + Assert.AreEqual(0m, mid.PrevailTorqueMonitoringMin); + Assert.AreEqual(0m, mid.PrevailTorqueMonitoringMax); + Assert.AreEqual(0m, mid.PrevailTorque); + Assert.AreEqual(184887, mid.TighteningId); + Assert.AreEqual(0, mid.JobSequenceNumber); + Assert.AreEqual(0, mid.SyncTighteningId); + Assert.AreEqual(" C0761275", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2020, 6, 25, 1, 4, 39), mid.Timestamp); + Assert.AreEqual(new DateTime(2020, 6, 24, 10, 48, 53), mid.LastChangeInParameterSet); + Assert.AreEqual("Test Parameter Set ", mid.ParameterSetName); + Assert.AreEqual(TorqueValuesUnit.LbfFt, mid.TorqueValuesUnit); + Assert.AreEqual(ResultType.BypassParameterSetResult, mid.ResultType); AssertEqualPackages(pack, mid); } @@ -314,52 +325,55 @@ public void Mid0061ByteRevision3() var mid = _midInterpreter.Parse(bytes.ToArray()); Assert.AreEqual(typeof(Mid0061), mid.GetType()); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.TorqueControllerName); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("airbag7 ", mid.TorqueControllerName); + Assert.AreEqual("KPOL3456JKLO897 ", mid.VinNumber); + Assert.AreEqual(6, mid.JobId); + Assert.AreEqual(3, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleReverse, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.IsFalse(mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.TorqueMinLimit); - Assert.IsNotNull(mid.TorqueMaxLimit); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.AngleMinLimit); - Assert.IsNotNull(mid.AngleMaxLimit); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringMin); - Assert.IsNotNull(mid.CurrentMonitoringMax); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapMin); - Assert.IsNotNull(mid.SelftapMax); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMin); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMax); - Assert.IsNotNull(mid.PrevailTorque); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); + Assert.AreEqual(8.40m, mid.TorqueMinLimit); + Assert.AreEqual(14m, mid.TorqueMaxLimit); + Assert.AreEqual(12m, mid.TorqueFinalTarget); + Assert.AreEqual(7.39m, mid.Torque); + Assert.AreEqual(0, mid.AngleMinLimit); + Assert.AreEqual(9999, mid.AngleMaxLimit); + Assert.AreEqual(0, mid.AngleFinalTarget); + Assert.AreEqual(0, mid.Angle); + Assert.AreEqual(0, mid.RundownAngleMin); + Assert.AreEqual(9999, mid.RundownAngleMax); + Assert.AreEqual(5000, mid.RundownAngle); + Assert.AreEqual(0, mid.CurrentMonitoringMin); + Assert.AreEqual(50, mid.CurrentMonitoringMax); + Assert.AreEqual(45, mid.CurrentMonitoringValue); + Assert.AreEqual(0.10m, mid.SelftapMin); + Assert.AreEqual(1.25m, mid.SelftapMax); + Assert.AreEqual(5.48m, mid.SelftapTorque); + Assert.AreEqual(0.10m, mid.PrevailTorqueMonitoringMin); + Assert.AreEqual(9999m, mid.PrevailTorqueMonitoringMax); + Assert.AreEqual(5555m, mid.PrevailTorque); + Assert.AreEqual(4294967295, mid.TighteningId); + Assert.AreEqual(65500, mid.JobSequenceNumber); + Assert.AreEqual(60535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123456", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2001, 6, 2, 9, 54, 9), mid.Timestamp); + Assert.AreEqual(new DateTime(2001, 5, 29, 12, 34, 33), mid.LastChangeInParameterSet); + Assert.AreEqual("Test Parameter Set ", mid.ParameterSetName); + Assert.AreEqual(TorqueValuesUnit.LbfFt, mid.TorqueValuesUnit); + Assert.AreEqual(ResultType.BypassParameterSetResult, mid.ResultType); AssertEqualPackages(bytes, mid); } @@ -371,55 +385,58 @@ public void Mid0061Revision4() var mid = _midInterpreter.Parse(pack); Assert.AreEqual(typeof(Mid0061), mid.GetType()); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.TorqueControllerName); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(0, mid.CellId); + Assert.AreEqual(0, mid.ChannelId); + Assert.AreEqual("RA ST6.2 ETV100 ", mid.TorqueControllerName); + Assert.AreEqual(" ", mid.VinNumber); + Assert.AreEqual(0, mid.JobId); + Assert.AreEqual(1, mid.ParameterSetId); + Assert.AreEqual(Strategy.TorqueControlAndAngleMonitoring, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.IsFalse(mid.TighteningStatus); + Assert.AreEqual(BatchStatus.NotUsed, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.TorqueMinLimit); - Assert.IsNotNull(mid.TorqueMaxLimit); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.AngleMinLimit); - Assert.IsNotNull(mid.AngleMaxLimit); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringMin); - Assert.IsNotNull(mid.CurrentMonitoringMax); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapMin); - Assert.IsNotNull(mid.SelftapMax); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMin); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMax); - Assert.IsNotNull(mid.PrevailTorque); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); + Assert.AreEqual(34m, mid.TorqueMinLimit); + Assert.AreEqual(46m, mid.TorqueMaxLimit); + Assert.AreEqual(40m, mid.TorqueFinalTarget); + Assert.AreEqual(5.05m, mid.Torque); + Assert.AreEqual(20, mid.AngleMinLimit); + Assert.AreEqual(420, mid.AngleMaxLimit); + Assert.AreEqual(0, mid.AngleFinalTarget); + Assert.AreEqual(0, mid.Angle); + Assert.AreEqual(100, mid.RundownAngleMin); + Assert.AreEqual(850, mid.RundownAngleMax); + Assert.AreEqual(4, mid.RundownAngle); + Assert.AreEqual(0, mid.CurrentMonitoringMin); + Assert.AreEqual(150, mid.CurrentMonitoringMax); + Assert.AreEqual(0, mid.CurrentMonitoringValue); + Assert.AreEqual(0m, mid.SelftapMin); + Assert.AreEqual(9999m, mid.SelftapMax); + Assert.AreEqual(0m, mid.SelftapTorque); + Assert.AreEqual(0m, mid.PrevailTorqueMonitoringMin); + Assert.AreEqual(0m, mid.PrevailTorqueMonitoringMax); + Assert.AreEqual(0m, mid.PrevailTorque); + Assert.AreEqual(184887, mid.TighteningId); + Assert.AreEqual(0, mid.JobSequenceNumber); + Assert.AreEqual(0, mid.SyncTighteningId); + Assert.AreEqual(" C0761275", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2020, 6, 25, 1, 4, 39), mid.Timestamp); + Assert.AreEqual(new DateTime(2020, 6, 24, 10, 48, 53), mid.LastChangeInParameterSet); + Assert.AreEqual("Test Parameter Set ", mid.ParameterSetName); + Assert.AreEqual(TorqueValuesUnit.LbfFt, mid.TorqueValuesUnit); + Assert.AreEqual(ResultType.BypassParameterSetResult, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); AssertEqualPackages(pack, mid); } @@ -463,55 +480,58 @@ public void Mid0061ByteRevision4() var mid = _midInterpreter.Parse(bytes.ToArray()); Assert.AreEqual(typeof(Mid0061), mid.GetType()); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.TorqueControllerName); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("airbag7 ", mid.TorqueControllerName); + Assert.AreEqual("KPOL3456JKLO897 ", mid.VinNumber); + Assert.AreEqual(6, mid.JobId); + Assert.AreEqual(3, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleReverse, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.IsFalse(mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.TorqueMinLimit); - Assert.IsNotNull(mid.TorqueMaxLimit); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.AngleMinLimit); - Assert.IsNotNull(mid.AngleMaxLimit); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringMin); - Assert.IsNotNull(mid.CurrentMonitoringMax); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapMin); - Assert.IsNotNull(mid.SelftapMax); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMin); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMax); - Assert.IsNotNull(mid.PrevailTorque); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); + Assert.AreEqual(8.40m, mid.TorqueMinLimit); + Assert.AreEqual(14m, mid.TorqueMaxLimit); + Assert.AreEqual(12m, mid.TorqueFinalTarget); + Assert.AreEqual(7.39m, mid.Torque); + Assert.AreEqual(0, mid.AngleMinLimit); + Assert.AreEqual(9999, mid.AngleMaxLimit); + Assert.AreEqual(0, mid.AngleFinalTarget); + Assert.AreEqual(0, mid.Angle); + Assert.AreEqual(0, mid.RundownAngleMin); + Assert.AreEqual(9999, mid.RundownAngleMax); + Assert.AreEqual(5000, mid.RundownAngle); + Assert.AreEqual(0, mid.CurrentMonitoringMin); + Assert.AreEqual(50, mid.CurrentMonitoringMax); + Assert.AreEqual(45, mid.CurrentMonitoringValue); + Assert.AreEqual(0.10m, mid.SelftapMin); + Assert.AreEqual(1.25m, mid.SelftapMax); + Assert.AreEqual(5.48m, mid.SelftapTorque); + Assert.AreEqual(0.10m, mid.PrevailTorqueMonitoringMin); + Assert.AreEqual(9999m, mid.PrevailTorqueMonitoringMax); + Assert.AreEqual(5555m, mid.PrevailTorque); + Assert.AreEqual(4294967295, mid.TighteningId); + Assert.AreEqual(65500, mid.JobSequenceNumber); + Assert.AreEqual(60535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123456", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2001, 6, 2, 9, 54, 9), mid.Timestamp); + Assert.AreEqual(new DateTime(2001, 5, 29, 12, 34, 33), mid.LastChangeInParameterSet); + Assert.AreEqual("Test Parameter Set ", mid.ParameterSetName); + Assert.AreEqual(TorqueValuesUnit.LbfFt, mid.TorqueValuesUnit); + Assert.AreEqual(ResultType.BypassParameterSetResult, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); AssertEqualPackages(bytes, mid); } @@ -523,56 +543,59 @@ public void Mid0061Revision5() var mid = _midInterpreter.Parse(pack); Assert.AreEqual(typeof(Mid0061), mid.GetType()); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.TorqueControllerName); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(0, mid.CellId); + Assert.AreEqual(0, mid.ChannelId); + Assert.AreEqual("RA ST6.2 ETV100 ", mid.TorqueControllerName); + Assert.AreEqual(" ", mid.VinNumber); + Assert.AreEqual(0, mid.JobId); + Assert.AreEqual(1, mid.ParameterSetId); + Assert.AreEqual(Strategy.TorqueControlAndAngleMonitoring, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.IsFalse(mid.TighteningStatus); + Assert.AreEqual(BatchStatus.NotUsed, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.TorqueMinLimit); - Assert.IsNotNull(mid.TorqueMaxLimit); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.AngleMinLimit); - Assert.IsNotNull(mid.AngleMaxLimit); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringMin); - Assert.IsNotNull(mid.CurrentMonitoringMax); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapMin); - Assert.IsNotNull(mid.SelftapMax); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMin); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMax); - Assert.IsNotNull(mid.PrevailTorque); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); + Assert.AreEqual(34m, mid.TorqueMinLimit); + Assert.AreEqual(46m, mid.TorqueMaxLimit); + Assert.AreEqual(40m, mid.TorqueFinalTarget); + Assert.AreEqual(5.05m, mid.Torque); + Assert.AreEqual(20, mid.AngleMinLimit); + Assert.AreEqual(420, mid.AngleMaxLimit); + Assert.AreEqual(0, mid.AngleFinalTarget); + Assert.AreEqual(0, mid.Angle); + Assert.AreEqual(100, mid.RundownAngleMin); + Assert.AreEqual(850, mid.RundownAngleMax); + Assert.AreEqual(4, mid.RundownAngle); + Assert.AreEqual(0, mid.CurrentMonitoringMin); + Assert.AreEqual(150, mid.CurrentMonitoringMax); + Assert.AreEqual(0, mid.CurrentMonitoringValue); + Assert.AreEqual(0m, mid.SelftapMin); + Assert.AreEqual(9999m, mid.SelftapMax); + Assert.AreEqual(0m, mid.SelftapTorque); + Assert.AreEqual(0m, mid.PrevailTorqueMonitoringMin); + Assert.AreEqual(0m, mid.PrevailTorqueMonitoringMax); + Assert.AreEqual(0m, mid.PrevailTorque); + Assert.AreEqual(184887, mid.TighteningId); + Assert.AreEqual(0, mid.JobSequenceNumber); + Assert.AreEqual(0, mid.SyncTighteningId); + Assert.AreEqual(" C0761275", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2020, 6, 25, 1, 4, 39), mid.Timestamp); + Assert.AreEqual(new DateTime(2020, 6, 24, 10, 48, 53), mid.LastChangeInParameterSet); + Assert.AreEqual("Test Parameter Set ", mid.ParameterSetName); + Assert.AreEqual(TorqueValuesUnit.LbfFt, mid.TorqueValuesUnit); + Assert.AreEqual(ResultType.BypassParameterSetResult, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); AssertEqualPackages(pack, mid); } @@ -616,56 +639,59 @@ public void Mid0061ByteRevision5() var mid = _midInterpreter.Parse(bytes.ToArray()); Assert.AreEqual(typeof(Mid0061), mid.GetType()); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.TorqueControllerName); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("airbag7 ", mid.TorqueControllerName); + Assert.AreEqual("KPOL3456JKLO897 ", mid.VinNumber); + Assert.AreEqual(6, mid.JobId); + Assert.AreEqual(3, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleReverse, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.IsFalse(mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.TorqueMinLimit); - Assert.IsNotNull(mid.TorqueMaxLimit); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.AngleMinLimit); - Assert.IsNotNull(mid.AngleMaxLimit); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringMin); - Assert.IsNotNull(mid.CurrentMonitoringMax); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapMin); - Assert.IsNotNull(mid.SelftapMax); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMin); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMax); - Assert.IsNotNull(mid.PrevailTorque); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); + Assert.AreEqual(8.40m, mid.TorqueMinLimit); + Assert.AreEqual(14m, mid.TorqueMaxLimit); + Assert.AreEqual(12m, mid.TorqueFinalTarget); + Assert.AreEqual(7.39m, mid.Torque); + Assert.AreEqual(0, mid.AngleMinLimit); + Assert.AreEqual(9999, mid.AngleMaxLimit); + Assert.AreEqual(0, mid.AngleFinalTarget); + Assert.AreEqual(0, mid.Angle); + Assert.AreEqual(0, mid.RundownAngleMin); + Assert.AreEqual(9999, mid.RundownAngleMax); + Assert.AreEqual(5000, mid.RundownAngle); + Assert.AreEqual(0, mid.CurrentMonitoringMin); + Assert.AreEqual(50, mid.CurrentMonitoringMax); + Assert.AreEqual(45, mid.CurrentMonitoringValue); + Assert.AreEqual(0.10m, mid.SelftapMin); + Assert.AreEqual(1.25m, mid.SelftapMax); + Assert.AreEqual(5.48m, mid.SelftapTorque); + Assert.AreEqual(0.10m, mid.PrevailTorqueMonitoringMin); + Assert.AreEqual(9999m, mid.PrevailTorqueMonitoringMax); + Assert.AreEqual(5555m, mid.PrevailTorque); + Assert.AreEqual(4294967295, mid.TighteningId); + Assert.AreEqual(65500, mid.JobSequenceNumber); + Assert.AreEqual(60535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123456", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2001, 6, 2, 9, 54, 9), mid.Timestamp); + Assert.AreEqual(new DateTime(2001, 5, 29, 12, 34, 33), mid.LastChangeInParameterSet); + Assert.AreEqual("Test Parameter Set ", mid.ParameterSetName); + Assert.AreEqual(TorqueValuesUnit.LbfFt, mid.TorqueValuesUnit); + Assert.AreEqual(ResultType.BypassParameterSetResult, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); AssertEqualPackages(bytes, mid); } @@ -677,57 +703,60 @@ public void Mid0061Revision6() var mid = _midInterpreter.Parse(pack); Assert.AreEqual(typeof(Mid0061), mid.GetType()); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.TorqueControllerName); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(0, mid.CellId); + Assert.AreEqual(0, mid.ChannelId); + Assert.AreEqual("RA ST6.2 ETV100 ", mid.TorqueControllerName); + Assert.AreEqual(" ", mid.VinNumber); + Assert.AreEqual(0, mid.JobId); + Assert.AreEqual(1, mid.ParameterSetId); + Assert.AreEqual(Strategy.TorqueControlAndAngleMonitoring, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.IsFalse(mid.TighteningStatus); + Assert.AreEqual(BatchStatus.NotUsed, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.TorqueMinLimit); - Assert.IsNotNull(mid.TorqueMaxLimit); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.AngleMinLimit); - Assert.IsNotNull(mid.AngleMaxLimit); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringMin); - Assert.IsNotNull(mid.CurrentMonitoringMax); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapMin); - Assert.IsNotNull(mid.SelftapMax); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMin); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMax); - Assert.IsNotNull(mid.PrevailTorque); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); - Assert.IsNotNull(mid.PrevailTorqueCompensateValue); + Assert.AreEqual(34m, mid.TorqueMinLimit); + Assert.AreEqual(46m, mid.TorqueMaxLimit); + Assert.AreEqual(40m, mid.TorqueFinalTarget); + Assert.AreEqual(5.05m, mid.Torque); + Assert.AreEqual(20, mid.AngleMinLimit); + Assert.AreEqual(420, mid.AngleMaxLimit); + Assert.AreEqual(0, mid.AngleFinalTarget); + Assert.AreEqual(0, mid.Angle); + Assert.AreEqual(100, mid.RundownAngleMin); + Assert.AreEqual(850, mid.RundownAngleMax); + Assert.AreEqual(4, mid.RundownAngle); + Assert.AreEqual(0, mid.CurrentMonitoringMin); + Assert.AreEqual(150, mid.CurrentMonitoringMax); + Assert.AreEqual(0, mid.CurrentMonitoringValue); + Assert.AreEqual(0m, mid.SelftapMin); + Assert.AreEqual(9999m, mid.SelftapMax); + Assert.AreEqual(0m, mid.SelftapTorque); + Assert.AreEqual(0m, mid.PrevailTorqueMonitoringMin); + Assert.AreEqual(0m, mid.PrevailTorqueMonitoringMax); + Assert.AreEqual(0m, mid.PrevailTorque); + Assert.AreEqual(184887, mid.TighteningId); + Assert.AreEqual(0, mid.JobSequenceNumber); + Assert.AreEqual(0, mid.SyncTighteningId); + Assert.AreEqual(" C0761275", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2020, 6, 25, 1, 4, 39), mid.Timestamp); + Assert.AreEqual(new DateTime(2020, 6, 24, 10, 48, 53), mid.LastChangeInParameterSet); + Assert.AreEqual("Test Parameter Set ", mid.ParameterSetName); + Assert.AreEqual(TorqueValuesUnit.LbfFt, mid.TorqueValuesUnit); + Assert.AreEqual(ResultType.BypassParameterSetResult, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); + Assert.AreEqual(15m, mid.PrevailTorqueCompensateValue); Assert.IsNotNull(mid.TighteningErrorStatus2); AssertEqualPackages(pack, mid); } @@ -789,57 +818,60 @@ public void Mid0061ByteRevision6() var mid = _midInterpreter.Parse(bytes.ToArray()); Assert.AreEqual(typeof(Mid0061), mid.GetType()); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.TorqueControllerName); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("airbag7 ", mid.TorqueControllerName); + Assert.AreEqual("KPOL3456JKLO897 ", mid.VinNumber); + Assert.AreEqual(6, mid.JobId); + Assert.AreEqual(3, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleReverse, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.IsFalse(mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.TorqueMinLimit); - Assert.IsNotNull(mid.TorqueMaxLimit); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.AngleMinLimit); - Assert.IsNotNull(mid.AngleMaxLimit); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringMin); - Assert.IsNotNull(mid.CurrentMonitoringMax); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapMin); - Assert.IsNotNull(mid.SelftapMax); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMin); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMax); - Assert.IsNotNull(mid.PrevailTorque); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); - Assert.IsNotNull(mid.PrevailTorqueCompensateValue); + Assert.AreEqual(8.40m, mid.TorqueMinLimit); + Assert.AreEqual(14m, mid.TorqueMaxLimit); + Assert.AreEqual(12m, mid.TorqueFinalTarget); + Assert.AreEqual(7.39m, mid.Torque); + Assert.AreEqual(0, mid.AngleMinLimit); + Assert.AreEqual(9999, mid.AngleMaxLimit); + Assert.AreEqual(0, mid.AngleFinalTarget); + Assert.AreEqual(0, mid.Angle); + Assert.AreEqual(0, mid.RundownAngleMin); + Assert.AreEqual(9999, mid.RundownAngleMax); + Assert.AreEqual(5000, mid.RundownAngle); + Assert.AreEqual(0, mid.CurrentMonitoringMin); + Assert.AreEqual(50, mid.CurrentMonitoringMax); + Assert.AreEqual(45, mid.CurrentMonitoringValue); + Assert.AreEqual(0.10m, mid.SelftapMin); + Assert.AreEqual(1.25m, mid.SelftapMax); + Assert.AreEqual(5.48m, mid.SelftapTorque); + Assert.AreEqual(0.10m, mid.PrevailTorqueMonitoringMin); + Assert.AreEqual(9999m, mid.PrevailTorqueMonitoringMax); + Assert.AreEqual(5555m, mid.PrevailTorque); + Assert.AreEqual(4294967295, mid.TighteningId); + Assert.AreEqual(65500, mid.JobSequenceNumber); + Assert.AreEqual(60535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123456", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2001, 6, 2, 9, 54, 9), mid.Timestamp); + Assert.AreEqual(new DateTime(2001, 5, 29, 12, 34, 33), mid.LastChangeInParameterSet); + Assert.AreEqual("Test Parameter Set ", mid.ParameterSetName); + Assert.AreEqual(TorqueValuesUnit.LbfFt, mid.TorqueValuesUnit); + Assert.AreEqual(ResultType.BypassParameterSetResult, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); + Assert.AreEqual(15m, mid.PrevailTorqueCompensateValue); Assert.IsNotNull(mid.TighteningErrorStatus2); AssertEqualPackages(bytes, mid); } @@ -852,60 +884,63 @@ public void Mid0061Revision7() var mid = _midInterpreter.Parse(pack); Assert.AreEqual(typeof(Mid0061), mid.GetType()); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.TorqueControllerName); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(0, mid.CellId); + Assert.AreEqual(0, mid.ChannelId); + Assert.AreEqual("RA ST6.2 ETV100 ", mid.TorqueControllerName); + Assert.AreEqual(" ", mid.VinNumber); + Assert.AreEqual(0, mid.JobId); + Assert.AreEqual(1, mid.ParameterSetId); + Assert.AreEqual(Strategy.TorqueControlAndAngleMonitoring, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.IsFalse(mid.TighteningStatus); + Assert.AreEqual(BatchStatus.NotUsed, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.TorqueMinLimit); - Assert.IsNotNull(mid.TorqueMaxLimit); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.AngleMinLimit); - Assert.IsNotNull(mid.AngleMaxLimit); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringMin); - Assert.IsNotNull(mid.CurrentMonitoringMax); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapMin); - Assert.IsNotNull(mid.SelftapMax); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMin); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMax); - Assert.IsNotNull(mid.PrevailTorque); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); - Assert.IsNotNull(mid.PrevailTorqueCompensateValue); + Assert.AreEqual(34m, mid.TorqueMinLimit); + Assert.AreEqual(46m, mid.TorqueMaxLimit); + Assert.AreEqual(40m, mid.TorqueFinalTarget); + Assert.AreEqual(5.05m, mid.Torque); + Assert.AreEqual(20, mid.AngleMinLimit); + Assert.AreEqual(420, mid.AngleMaxLimit); + Assert.AreEqual(0, mid.AngleFinalTarget); + Assert.AreEqual(0, mid.Angle); + Assert.AreEqual(100, mid.RundownAngleMin); + Assert.AreEqual(850, mid.RundownAngleMax); + Assert.AreEqual(4, mid.RundownAngle); + Assert.AreEqual(0, mid.CurrentMonitoringMin); + Assert.AreEqual(150, mid.CurrentMonitoringMax); + Assert.AreEqual(0, mid.CurrentMonitoringValue); + Assert.AreEqual(0m, mid.SelftapMin); + Assert.AreEqual(9999m, mid.SelftapMax); + Assert.AreEqual(0m, mid.SelftapTorque); + Assert.AreEqual(0m, mid.PrevailTorqueMonitoringMin); + Assert.AreEqual(0m, mid.PrevailTorqueMonitoringMax); + Assert.AreEqual(0m, mid.PrevailTorque); + Assert.AreEqual(184887, mid.TighteningId); + Assert.AreEqual(0, mid.JobSequenceNumber); + Assert.AreEqual(0, mid.SyncTighteningId); + Assert.AreEqual(" C0761275", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2020, 6, 25, 1, 4, 39), mid.Timestamp); + Assert.AreEqual(new DateTime(2020, 6, 24, 10, 48, 53), mid.LastChangeInParameterSet); + Assert.AreEqual("Test Parameter Set ", mid.ParameterSetName); + Assert.AreEqual(TorqueValuesUnit.LbfFt, mid.TorqueValuesUnit); + Assert.AreEqual(ResultType.BypassParameterSetResult, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); + Assert.AreEqual(15m, mid.PrevailTorqueCompensateValue); Assert.IsNotNull(mid.TighteningErrorStatus2); - Assert.IsNotNull(mid.CompensatedAngle); - Assert.IsNotNull(mid.FinalAngleDecimal); + Assert.AreEqual(100m, mid.CompensatedAngle); + Assert.AreEqual(9999m, mid.FinalAngleDecimal); AssertEqualPackages(pack, mid); } @@ -969,60 +1004,63 @@ public void Mid0061ByteRevision7() var mid = _midInterpreter.Parse(bytes.ToArray()); Assert.AreEqual(typeof(Mid0061), mid.GetType()); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.TorqueControllerName); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("airbag7 ", mid.TorqueControllerName); + Assert.AreEqual("KPOL3456JKLO897 ", mid.VinNumber); + Assert.AreEqual(6, mid.JobId); + Assert.AreEqual(3, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleReverse, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.IsFalse(mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.TorqueMinLimit); - Assert.IsNotNull(mid.TorqueMaxLimit); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.AngleMinLimit); - Assert.IsNotNull(mid.AngleMaxLimit); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringMin); - Assert.IsNotNull(mid.CurrentMonitoringMax); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapMin); - Assert.IsNotNull(mid.SelftapMax); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMin); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMax); - Assert.IsNotNull(mid.PrevailTorque); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); - Assert.IsNotNull(mid.PrevailTorqueCompensateValue); + Assert.AreEqual(8.40m, mid.TorqueMinLimit); + Assert.AreEqual(14m, mid.TorqueMaxLimit); + Assert.AreEqual(12m, mid.TorqueFinalTarget); + Assert.AreEqual(7.39m, mid.Torque); + Assert.AreEqual(0, mid.AngleMinLimit); + Assert.AreEqual(9999, mid.AngleMaxLimit); + Assert.AreEqual(0, mid.AngleFinalTarget); + Assert.AreEqual(0, mid.Angle); + Assert.AreEqual(0, mid.RundownAngleMin); + Assert.AreEqual(9999, mid.RundownAngleMax); + Assert.AreEqual(5000, mid.RundownAngle); + Assert.AreEqual(0, mid.CurrentMonitoringMin); + Assert.AreEqual(50, mid.CurrentMonitoringMax); + Assert.AreEqual(45, mid.CurrentMonitoringValue); + Assert.AreEqual(0.10m, mid.SelftapMin); + Assert.AreEqual(1.25m, mid.SelftapMax); + Assert.AreEqual(5.48m, mid.SelftapTorque); + Assert.AreEqual(0.10m, mid.PrevailTorqueMonitoringMin); + Assert.AreEqual(9999m, mid.PrevailTorqueMonitoringMax); + Assert.AreEqual(5555m, mid.PrevailTorque); + Assert.AreEqual(4294967295, mid.TighteningId); + Assert.AreEqual(65500, mid.JobSequenceNumber); + Assert.AreEqual(60535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123456", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2001, 6, 2, 9, 54, 9), mid.Timestamp); + Assert.AreEqual(new DateTime(2001, 5, 29, 12, 34, 33), mid.LastChangeInParameterSet); + Assert.AreEqual("Test Parameter Set ", mid.ParameterSetName); + Assert.AreEqual(TorqueValuesUnit.LbfFt, mid.TorqueValuesUnit); + Assert.AreEqual(ResultType.BypassParameterSetResult, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); + Assert.AreEqual(15m, mid.PrevailTorqueCompensateValue); Assert.IsNotNull(mid.TighteningErrorStatus2); - Assert.IsNotNull(mid.CompensatedAngle); - Assert.IsNotNull(mid.FinalAngleDecimal); + Assert.AreEqual(100m, mid.CompensatedAngle); + Assert.AreEqual(9999m, mid.FinalAngleDecimal); AssertEqualPackages(bytes, mid); } @@ -1034,64 +1072,67 @@ public void Mid0061Revision8() var mid = _midInterpreter.Parse(pack); Assert.AreEqual(typeof(Mid0061), mid.GetType()); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.TorqueControllerName); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(0, mid.CellId); + Assert.AreEqual(0, mid.ChannelId); + Assert.AreEqual("RA ST6.2 ETV100 ", mid.TorqueControllerName); + Assert.AreEqual(" ", mid.VinNumber); + Assert.AreEqual(0, mid.JobId); + Assert.AreEqual(1, mid.ParameterSetId); + Assert.AreEqual(Strategy.TorqueControlAndAngleMonitoring, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.IsFalse(mid.TighteningStatus); + Assert.AreEqual(BatchStatus.NotUsed, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.TorqueMinLimit); - Assert.IsNotNull(mid.TorqueMaxLimit); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.AngleMinLimit); - Assert.IsNotNull(mid.AngleMaxLimit); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringMin); - Assert.IsNotNull(mid.CurrentMonitoringMax); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapMin); - Assert.IsNotNull(mid.SelftapMax); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMin); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMax); - Assert.IsNotNull(mid.PrevailTorque); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); - Assert.IsNotNull(mid.PrevailTorqueCompensateValue); + Assert.AreEqual(34m, mid.TorqueMinLimit); + Assert.AreEqual(46m, mid.TorqueMaxLimit); + Assert.AreEqual(40m, mid.TorqueFinalTarget); + Assert.AreEqual(5.05m, mid.Torque); + Assert.AreEqual(20, mid.AngleMinLimit); + Assert.AreEqual(420, mid.AngleMaxLimit); + Assert.AreEqual(0, mid.AngleFinalTarget); + Assert.AreEqual(0, mid.Angle); + Assert.AreEqual(100, mid.RundownAngleMin); + Assert.AreEqual(850, mid.RundownAngleMax); + Assert.AreEqual(4, mid.RundownAngle); + Assert.AreEqual(0, mid.CurrentMonitoringMin); + Assert.AreEqual(150, mid.CurrentMonitoringMax); + Assert.AreEqual(0, mid.CurrentMonitoringValue); + Assert.AreEqual(0m, mid.SelftapMin); + Assert.AreEqual(9999m, mid.SelftapMax); + Assert.AreEqual(0m, mid.SelftapTorque); + Assert.AreEqual(0m, mid.PrevailTorqueMonitoringMin); + Assert.AreEqual(0m, mid.PrevailTorqueMonitoringMax); + Assert.AreEqual(0m, mid.PrevailTorque); + Assert.AreEqual(184887, mid.TighteningId); + Assert.AreEqual(0, mid.JobSequenceNumber); + Assert.AreEqual(0, mid.SyncTighteningId); + Assert.AreEqual(" C0761275", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2020, 6, 25, 1, 4, 39), mid.Timestamp); + Assert.AreEqual(new DateTime(2020, 6, 24, 10, 48, 53), mid.LastChangeInParameterSet); + Assert.AreEqual("Test Parameter Set ", mid.ParameterSetName); + Assert.AreEqual(TorqueValuesUnit.LbfFt, mid.TorqueValuesUnit); + Assert.AreEqual(ResultType.BypassParameterSetResult, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); + Assert.AreEqual(15m, mid.PrevailTorqueCompensateValue); Assert.IsNotNull(mid.TighteningErrorStatus2); - Assert.IsNotNull(mid.CompensatedAngle); - Assert.IsNotNull(mid.FinalAngleDecimal); - Assert.AreNotEqual(0, mid.StartFinalAngle); - Assert.IsNotNull(mid.PostViewTorqueActivated); - Assert.AreNotEqual(0, mid.PostViewTorqueHigh); - Assert.AreNotEqual(0, mid.PostViewTorqueLow); + Assert.AreEqual(100m, mid.CompensatedAngle); + Assert.AreEqual(9999m, mid.FinalAngleDecimal); + Assert.AreEqual(10m, mid.StartFinalAngle); + Assert.AreEqual(PostViewTorque.OnlyPVTHOn, mid.PostViewTorqueActivated); + Assert.AreEqual(6521.43m, mid.PostViewTorqueHigh); + Assert.AreEqual(52.32m, mid.PostViewTorqueLow); AssertEqualPackages(pack, mid); } @@ -1155,64 +1196,67 @@ public void Mid0061ByteRevision8() var mid = _midInterpreter.Parse(bytes.ToArray()); Assert.AreEqual(typeof(Mid0061), mid.GetType()); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.TorqueControllerName); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("airbag7 ", mid.TorqueControllerName); + Assert.AreEqual("KPOL3456JKLO897 ", mid.VinNumber); + Assert.AreEqual(6, mid.JobId); + Assert.AreEqual(3, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleReverse, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.IsFalse(mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.TorqueMinLimit); - Assert.IsNotNull(mid.TorqueMaxLimit); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.AngleMinLimit); - Assert.IsNotNull(mid.AngleMaxLimit); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringMin); - Assert.IsNotNull(mid.CurrentMonitoringMax); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapMin); - Assert.IsNotNull(mid.SelftapMax); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMin); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMax); - Assert.IsNotNull(mid.PrevailTorque); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); - Assert.IsNotNull(mid.PrevailTorqueCompensateValue); + Assert.AreEqual(8.40m, mid.TorqueMinLimit); + Assert.AreEqual(14m, mid.TorqueMaxLimit); + Assert.AreEqual(12m, mid.TorqueFinalTarget); + Assert.AreEqual(7.39m, mid.Torque); + Assert.AreEqual(0, mid.AngleMinLimit); + Assert.AreEqual(9999, mid.AngleMaxLimit); + Assert.AreEqual(0, mid.AngleFinalTarget); + Assert.AreEqual(0, mid.Angle); + Assert.AreEqual(0, mid.RundownAngleMin); + Assert.AreEqual(9999, mid.RundownAngleMax); + Assert.AreEqual(5000, mid.RundownAngle); + Assert.AreEqual(0, mid.CurrentMonitoringMin); + Assert.AreEqual(50, mid.CurrentMonitoringMax); + Assert.AreEqual(45, mid.CurrentMonitoringValue); + Assert.AreEqual(0.10m, mid.SelftapMin); + Assert.AreEqual(1.25m, mid.SelftapMax); + Assert.AreEqual(5.48m, mid.SelftapTorque); + Assert.AreEqual(0.10m, mid.PrevailTorqueMonitoringMin); + Assert.AreEqual(9999m, mid.PrevailTorqueMonitoringMax); + Assert.AreEqual(5555m, mid.PrevailTorque); + Assert.AreEqual(4294967295, mid.TighteningId); + Assert.AreEqual(65500, mid.JobSequenceNumber); + Assert.AreEqual(60535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123456", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2001, 6, 2, 9, 54, 9), mid.Timestamp); + Assert.AreEqual(new DateTime(2001, 5, 29, 12, 34, 33), mid.LastChangeInParameterSet); + Assert.AreEqual("Test Parameter Set ", mid.ParameterSetName); + Assert.AreEqual(TorqueValuesUnit.LbfFt, mid.TorqueValuesUnit); + Assert.AreEqual(ResultType.BypassParameterSetResult, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); + Assert.AreEqual(15m, mid.PrevailTorqueCompensateValue); Assert.IsNotNull(mid.TighteningErrorStatus2); - Assert.IsNotNull(mid.CompensatedAngle); - Assert.IsNotNull(mid.FinalAngleDecimal); - Assert.AreNotEqual(0, mid.StartFinalAngle); - Assert.IsNotNull(mid.PostViewTorqueActivated); - Assert.AreNotEqual(0, mid.PostViewTorqueHigh); - Assert.AreNotEqual(0, mid.PostViewTorqueLow); + Assert.AreEqual(100m, mid.CompensatedAngle); + Assert.AreEqual(9999m, mid.FinalAngleDecimal); + Assert.AreEqual(10m, mid.StartFinalAngle); + Assert.AreEqual(PostViewTorque.OnlyPVTHOn, mid.PostViewTorqueActivated); + Assert.AreEqual(6521.43m, mid.PostViewTorqueHigh); + Assert.AreEqual(52.32m, mid.PostViewTorqueLow); AssertEqualPackages(bytes, mid); } @@ -1224,67 +1268,70 @@ public void Mid0061Revision9() var mid = _midInterpreter.Parse(pack); Assert.AreEqual(typeof(Mid0061), mid.GetType()); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.TorqueControllerName); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(0, mid.CellId); + Assert.AreEqual(0, mid.ChannelId); + Assert.AreEqual("RA ST6.2 ETV100 ", mid.TorqueControllerName); + Assert.AreEqual(" ", mid.VinNumber); + Assert.AreEqual(0, mid.JobId); + Assert.AreEqual(1, mid.ParameterSetId); + Assert.AreEqual(Strategy.TorqueControlAndAngleMonitoring, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.IsFalse(mid.TighteningStatus); + Assert.AreEqual(BatchStatus.NotUsed, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.TorqueMinLimit); - Assert.IsNotNull(mid.TorqueMaxLimit); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.AngleMinLimit); - Assert.IsNotNull(mid.AngleMaxLimit); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringMin); - Assert.IsNotNull(mid.CurrentMonitoringMax); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapMin); - Assert.IsNotNull(mid.SelftapMax); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMin); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMax); - Assert.IsNotNull(mid.PrevailTorque); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); - Assert.IsNotNull(mid.PrevailTorqueCompensateValue); + Assert.AreEqual(34m, mid.TorqueMinLimit); + Assert.AreEqual(46m, mid.TorqueMaxLimit); + Assert.AreEqual(40m, mid.TorqueFinalTarget); + Assert.AreEqual(5.05m, mid.Torque); + Assert.AreEqual(20, mid.AngleMinLimit); + Assert.AreEqual(420, mid.AngleMaxLimit); + Assert.AreEqual(0, mid.AngleFinalTarget); + Assert.AreEqual(0, mid.Angle); + Assert.AreEqual(100, mid.RundownAngleMin); + Assert.AreEqual(850, mid.RundownAngleMax); + Assert.AreEqual(4, mid.RundownAngle); + Assert.AreEqual(0, mid.CurrentMonitoringMin); + Assert.AreEqual(150, mid.CurrentMonitoringMax); + Assert.AreEqual(0, mid.CurrentMonitoringValue); + Assert.AreEqual(0m, mid.SelftapMin); + Assert.AreEqual(9999m, mid.SelftapMax); + Assert.AreEqual(0m, mid.SelftapTorque); + Assert.AreEqual(0m, mid.PrevailTorqueMonitoringMin); + Assert.AreEqual(0m, mid.PrevailTorqueMonitoringMax); + Assert.AreEqual(0m, mid.PrevailTorque); + Assert.AreEqual(184887, mid.TighteningId); + Assert.AreEqual(0, mid.JobSequenceNumber); + Assert.AreEqual(0, mid.SyncTighteningId); + Assert.AreEqual(" C0761275", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2020, 6, 25, 1, 4, 39), mid.Timestamp); + Assert.AreEqual(new DateTime(2020, 6, 24, 10, 48, 53), mid.LastChangeInParameterSet); + Assert.AreEqual("Test Parameter Set ", mid.ParameterSetName); + Assert.AreEqual(TorqueValuesUnit.LbfFt, mid.TorqueValuesUnit); + Assert.AreEqual(ResultType.BypassParameterSetResult, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); + Assert.AreEqual(15m, mid.PrevailTorqueCompensateValue); Assert.IsNotNull(mid.TighteningErrorStatus2); - Assert.IsNotNull(mid.CompensatedAngle); - Assert.IsNotNull(mid.FinalAngleDecimal); - Assert.AreNotEqual(0, mid.StartFinalAngle); - Assert.IsNotNull(mid.PostViewTorqueActivated); - Assert.AreNotEqual(0, mid.PostViewTorqueHigh); - Assert.AreNotEqual(0, mid.PostViewTorqueLow); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpere); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpereMin); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpereMax); + Assert.AreEqual(100m, mid.CompensatedAngle); + Assert.AreEqual(9999m, mid.FinalAngleDecimal); + Assert.AreEqual(10m, mid.StartFinalAngle); + Assert.AreEqual(PostViewTorque.OnlyPVTHOn, mid.PostViewTorqueActivated); + Assert.AreEqual(6521.43m, mid.PostViewTorqueHigh); + Assert.AreEqual(52.32m, mid.PostViewTorqueLow); + Assert.AreEqual(1m, mid.CurrentMonitoringAmpere); + Assert.AreEqual(0.50m, mid.CurrentMonitoringAmpereMin); + Assert.AreEqual(1.50m, mid.CurrentMonitoringAmpereMax); AssertEqualPackages(pack, mid); } @@ -1348,67 +1395,70 @@ public void Mid0061ByteRevision9() var mid = _midInterpreter.Parse(bytes.ToArray()); Assert.AreEqual(typeof(Mid0061), mid.GetType()); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.TorqueControllerName); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("airbag7 ", mid.TorqueControllerName); + Assert.AreEqual("KPOL3456JKLO897 ", mid.VinNumber); + Assert.AreEqual(6, mid.JobId); + Assert.AreEqual(3, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleReverse, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.IsFalse(mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.TorqueMinLimit); - Assert.IsNotNull(mid.TorqueMaxLimit); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.AngleMinLimit); - Assert.IsNotNull(mid.AngleMaxLimit); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringMin); - Assert.IsNotNull(mid.CurrentMonitoringMax); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapMin); - Assert.IsNotNull(mid.SelftapMax); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMin); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMax); - Assert.IsNotNull(mid.PrevailTorque); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); - Assert.IsNotNull(mid.PrevailTorqueCompensateValue); + Assert.AreEqual(8.40m, mid.TorqueMinLimit); + Assert.AreEqual(14m, mid.TorqueMaxLimit); + Assert.AreEqual(12m, mid.TorqueFinalTarget); + Assert.AreEqual(7.39m, mid.Torque); + Assert.AreEqual(0, mid.AngleMinLimit); + Assert.AreEqual(9999, mid.AngleMaxLimit); + Assert.AreEqual(0, mid.AngleFinalTarget); + Assert.AreEqual(0, mid.Angle); + Assert.AreEqual(0, mid.RundownAngleMin); + Assert.AreEqual(9999, mid.RundownAngleMax); + Assert.AreEqual(5000, mid.RundownAngle); + Assert.AreEqual(0, mid.CurrentMonitoringMin); + Assert.AreEqual(50, mid.CurrentMonitoringMax); + Assert.AreEqual(45, mid.CurrentMonitoringValue); + Assert.AreEqual(0.10m, mid.SelftapMin); + Assert.AreEqual(1.25m, mid.SelftapMax); + Assert.AreEqual(5.48m, mid.SelftapTorque); + Assert.AreEqual(0.10m, mid.PrevailTorqueMonitoringMin); + Assert.AreEqual(9999m, mid.PrevailTorqueMonitoringMax); + Assert.AreEqual(5555m, mid.PrevailTorque); + Assert.AreEqual(4294967295, mid.TighteningId); + Assert.AreEqual(65500, mid.JobSequenceNumber); + Assert.AreEqual(60535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123456", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2001, 6, 2, 9, 54, 9), mid.Timestamp); + Assert.AreEqual(new DateTime(2001, 5, 29, 12, 34, 33), mid.LastChangeInParameterSet); + Assert.AreEqual("Test Parameter Set ", mid.ParameterSetName); + Assert.AreEqual(TorqueValuesUnit.LbfFt, mid.TorqueValuesUnit); + Assert.AreEqual(ResultType.BypassParameterSetResult, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); + Assert.AreEqual(15m, mid.PrevailTorqueCompensateValue); Assert.IsNotNull(mid.TighteningErrorStatus2); - Assert.IsNotNull(mid.CompensatedAngle); - Assert.IsNotNull(mid.FinalAngleDecimal); - Assert.AreNotEqual(0, mid.StartFinalAngle); - Assert.IsNotNull(mid.PostViewTorqueActivated); - Assert.AreNotEqual(0, mid.PostViewTorqueHigh); - Assert.AreNotEqual(0, mid.PostViewTorqueLow); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpere); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpereMin); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpereMax); + Assert.AreEqual(100m, mid.CompensatedAngle); + Assert.AreEqual(9999m, mid.FinalAngleDecimal); + Assert.AreEqual(10m, mid.StartFinalAngle); + Assert.AreEqual(PostViewTorque.OnlyPVTHOn, mid.PostViewTorqueActivated); + Assert.AreEqual(6521.43m, mid.PostViewTorqueHigh); + Assert.AreEqual(52.32m, mid.PostViewTorqueLow); + Assert.AreEqual(1m, mid.CurrentMonitoringAmpere); + Assert.AreEqual(0.50m, mid.CurrentMonitoringAmpereMin); + Assert.AreEqual(1.50m, mid.CurrentMonitoringAmpereMax); AssertEqualPackages(bytes, mid); } @@ -1420,79 +1470,80 @@ public void Mid0061Revision10() var mid = _midInterpreter.Parse(pack); Assert.AreEqual(typeof(Mid0061), mid.GetType()); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.TorqueControllerName); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(0, mid.CellId); + Assert.AreEqual(0, mid.ChannelId); + Assert.AreEqual("RA ST6.2 ETV100 ", mid.TorqueControllerName); + Assert.AreEqual(" ", mid.VinNumber); + Assert.AreEqual(0, mid.JobId); + Assert.AreEqual(1, mid.ParameterSetId); + Assert.AreEqual(Strategy.TorqueControlAndAngleMonitoring, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.IsFalse(mid.TighteningStatus); + Assert.AreEqual(BatchStatus.NotUsed, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.TorqueMinLimit); - Assert.IsNotNull(mid.TorqueMaxLimit); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.AngleMinLimit); - Assert.IsNotNull(mid.AngleMaxLimit); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringMin); - Assert.IsNotNull(mid.CurrentMonitoringMax); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapMin); - Assert.IsNotNull(mid.SelftapMax); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMin); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMax); - Assert.IsNotNull(mid.PrevailTorque); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); - Assert.IsNotNull(mid.PrevailTorqueCompensateValue); + Assert.AreEqual(34m, mid.TorqueMinLimit); + Assert.AreEqual(46m, mid.TorqueMaxLimit); + Assert.AreEqual(40m, mid.TorqueFinalTarget); + Assert.AreEqual(5.05m, mid.Torque); + Assert.AreEqual(20, mid.AngleMinLimit); + Assert.AreEqual(420, mid.AngleMaxLimit); + Assert.AreEqual(0, mid.AngleFinalTarget); + Assert.AreEqual(0, mid.Angle); + Assert.AreEqual(100, mid.RundownAngleMin); + Assert.AreEqual(850, mid.RundownAngleMax); + Assert.AreEqual(4, mid.RundownAngle); + Assert.AreEqual(0, mid.CurrentMonitoringMin); + Assert.AreEqual(150, mid.CurrentMonitoringMax); + Assert.AreEqual(0, mid.CurrentMonitoringValue); + Assert.AreEqual(0m, mid.SelftapMin); + Assert.AreEqual(9999m, mid.SelftapMax); + Assert.AreEqual(0m, mid.SelftapTorque); + Assert.AreEqual(0m, mid.PrevailTorqueMonitoringMin); + Assert.AreEqual(0m, mid.PrevailTorqueMonitoringMax); + Assert.AreEqual(0m, mid.PrevailTorque); + Assert.AreEqual(184887, mid.TighteningId); + Assert.AreEqual(0, mid.JobSequenceNumber); + Assert.AreEqual(0, mid.SyncTighteningId); + Assert.AreEqual(" C0761275", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2020, 6, 25, 1, 4, 39), mid.Timestamp); + Assert.AreEqual(new DateTime(2020, 6, 24, 10, 48, 53), mid.LastChangeInParameterSet); + Assert.AreEqual("Test Parameter Set ", mid.ParameterSetName); + Assert.AreEqual(TorqueValuesUnit.LbfFt, mid.TorqueValuesUnit); + Assert.AreEqual(ResultType.BypassParameterSetResult, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); + Assert.AreEqual(15m, mid.PrevailTorqueCompensateValue); Assert.IsNotNull(mid.TighteningErrorStatus2); - Assert.IsNotNull(mid.CompensatedAngle); - Assert.IsNotNull(mid.FinalAngleDecimal); - Assert.AreNotEqual(0, mid.StartFinalAngle); - Assert.IsNotNull(mid.PostViewTorqueActivated); - Assert.AreNotEqual(0, mid.PostViewTorqueHigh); - Assert.AreNotEqual(0, mid.PostViewTorqueLow); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpere); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpereMin); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpereMax); - Assert.AreNotEqual(0, mid.AngleNumeratorScaleFactor); - Assert.AreNotEqual(0, mid.AngleDenominatorScaleFactor); - Assert.IsNotNull(mid.OverallAngleStatus); - Assert.AreNotEqual(0, mid.OverallAngleMin); - Assert.AreNotEqual(0, mid.OverallAngleMax); - Assert.AreNotEqual(0, mid.OverallAngle); - Assert.AreNotEqual(0, mid.PeakTorque); - Assert.AreNotEqual(0, mid.ResidualBreakawayTorque); - Assert.AreNotEqual(0, mid.StartRundownAngle); - Assert.AreNotEqual(0, mid.RundownAngleComplete); - Assert.AreNotEqual(0, mid.OverallAngle); - + Assert.AreEqual(100m, mid.CompensatedAngle); + Assert.AreEqual(9999m, mid.FinalAngleDecimal); + Assert.AreEqual(10m, mid.StartFinalAngle); + Assert.AreEqual(PostViewTorque.OnlyPVTHOn, mid.PostViewTorqueActivated); + Assert.AreEqual(6521.43m, mid.PostViewTorqueHigh); + Assert.AreEqual(52.32m, mid.PostViewTorqueLow); + Assert.AreEqual(1m, mid.CurrentMonitoringAmpere); + Assert.AreEqual(0.50m, mid.CurrentMonitoringAmpereMin); + Assert.AreEqual(1.50m, mid.CurrentMonitoringAmpereMax); + Assert.AreEqual(1, mid.AngleNumeratorScaleFactor); + Assert.AreEqual(1, mid.AngleDenominatorScaleFactor); + Assert.AreEqual(TighteningValueStatus.Ok, mid.OverallAngleStatus); + Assert.AreEqual(-20, mid.OverallAngleMin); + Assert.AreEqual(100, mid.OverallAngleMax); + Assert.AreEqual(120, mid.OverallAngle); + Assert.AreEqual(32.91m, mid.PeakTorque); + Assert.AreEqual(10.05m, mid.ResidualBreakawayTorque); + Assert.AreEqual(1.10m, mid.StartRundownAngle); + Assert.AreEqual(90m, mid.RundownAngleComplete); AssertEqualPackages(pack, mid); } @@ -1556,78 +1607,80 @@ public void Mid0061ByteRevision10() var mid = _midInterpreter.Parse(bytes.ToArray()); Assert.AreEqual(typeof(Mid0061), mid.GetType()); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.TorqueControllerName); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("airbag7 ", mid.TorqueControllerName); + Assert.AreEqual("KPOL3456JKLO897 ", mid.VinNumber); + Assert.AreEqual(6, mid.JobId); + Assert.AreEqual(3, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleReverse, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.IsFalse(mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.TorqueMinLimit); - Assert.IsNotNull(mid.TorqueMaxLimit); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.AngleMinLimit); - Assert.IsNotNull(mid.AngleMaxLimit); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringMin); - Assert.IsNotNull(mid.CurrentMonitoringMax); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapMin); - Assert.IsNotNull(mid.SelftapMax); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMin); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMax); - Assert.IsNotNull(mid.PrevailTorque); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); - Assert.IsNotNull(mid.PrevailTorqueCompensateValue); + Assert.AreEqual(8.40m, mid.TorqueMinLimit); + Assert.AreEqual(14m, mid.TorqueMaxLimit); + Assert.AreEqual(12m, mid.TorqueFinalTarget); + Assert.AreEqual(7.39m, mid.Torque); + Assert.AreEqual(0, mid.AngleMinLimit); + Assert.AreEqual(9999, mid.AngleMaxLimit); + Assert.AreEqual(0, mid.AngleFinalTarget); + Assert.AreEqual(0, mid.Angle); + Assert.AreEqual(0, mid.RundownAngleMin); + Assert.AreEqual(9999, mid.RundownAngleMax); + Assert.AreEqual(5000, mid.RundownAngle); + Assert.AreEqual(0, mid.CurrentMonitoringMin); + Assert.AreEqual(50, mid.CurrentMonitoringMax); + Assert.AreEqual(45, mid.CurrentMonitoringValue); + Assert.AreEqual(0.10m, mid.SelftapMin); + Assert.AreEqual(1.25m, mid.SelftapMax); + Assert.AreEqual(5.48m, mid.SelftapTorque); + Assert.AreEqual(0.10m, mid.PrevailTorqueMonitoringMin); + Assert.AreEqual(9999m, mid.PrevailTorqueMonitoringMax); + Assert.AreEqual(5555m, mid.PrevailTorque); + Assert.AreEqual(4294967295, mid.TighteningId); + Assert.AreEqual(65500, mid.JobSequenceNumber); + Assert.AreEqual(60535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123456", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2001, 6, 2, 9, 54, 9), mid.Timestamp); + Assert.AreEqual(new DateTime(2001, 5, 29, 12, 34, 33), mid.LastChangeInParameterSet); + Assert.AreEqual("Test Parameter Set ", mid.ParameterSetName); + Assert.AreEqual(TorqueValuesUnit.LbfFt, mid.TorqueValuesUnit); + Assert.AreEqual(ResultType.BypassParameterSetResult, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); + Assert.AreEqual(15m, mid.PrevailTorqueCompensateValue); Assert.IsNotNull(mid.TighteningErrorStatus2); - Assert.IsNotNull(mid.CompensatedAngle); - Assert.IsNotNull(mid.FinalAngleDecimal); - Assert.AreNotEqual(0, mid.StartFinalAngle); - Assert.IsNotNull(mid.PostViewTorqueActivated); - Assert.AreNotEqual(0, mid.PostViewTorqueHigh); - Assert.AreNotEqual(0, mid.PostViewTorqueLow); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpere); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpereMin); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpereMax); - Assert.AreNotEqual(0, mid.AngleNumeratorScaleFactor); - Assert.AreNotEqual(0, mid.AngleDenominatorScaleFactor); - Assert.IsNotNull(mid.OverallAngleStatus); - Assert.AreNotEqual(0, mid.OverallAngleMin); - Assert.AreNotEqual(0, mid.OverallAngleMax); - Assert.AreNotEqual(0, mid.OverallAngle); - Assert.AreNotEqual(0, mid.PeakTorque); - Assert.AreNotEqual(0, mid.ResidualBreakawayTorque); - Assert.AreNotEqual(0, mid.StartRundownAngle); - Assert.AreNotEqual(0, mid.RundownAngleComplete); - Assert.AreNotEqual(0, mid.OverallAngle); + Assert.AreEqual(100m, mid.CompensatedAngle); + Assert.AreEqual(9999m, mid.FinalAngleDecimal); + Assert.AreEqual(10m, mid.StartFinalAngle); + Assert.AreEqual(PostViewTorque.OnlyPVTHOn, mid.PostViewTorqueActivated); + Assert.AreEqual(6521.43m, mid.PostViewTorqueHigh); + Assert.AreEqual(52.32m, mid.PostViewTorqueLow); + Assert.AreEqual(1m, mid.CurrentMonitoringAmpere); + Assert.AreEqual(0.50m, mid.CurrentMonitoringAmpereMin); + Assert.AreEqual(1.50m, mid.CurrentMonitoringAmpereMax); + Assert.AreEqual(1, mid.AngleNumeratorScaleFactor); + Assert.AreEqual(1, mid.AngleDenominatorScaleFactor); + Assert.AreEqual(TighteningValueStatus.Ok, mid.OverallAngleStatus); + Assert.AreEqual(-20, mid.OverallAngleMin); + Assert.AreEqual(100, mid.OverallAngleMax); + Assert.AreEqual(120, mid.OverallAngle); + Assert.AreEqual(32.91m, mid.PeakTorque); + Assert.AreEqual(10.05m, mid.ResidualBreakawayTorque); + Assert.AreEqual(1.10m, mid.StartRundownAngle); + Assert.AreEqual(90m, mid.RundownAngleComplete); AssertEqualPackages(bytes, mid); } @@ -1639,80 +1692,82 @@ public void Mid0061Revision11() var mid = _midInterpreter.Parse(pack); Assert.AreEqual(typeof(Mid0061), mid.GetType()); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.TorqueControllerName); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(0, mid.CellId); + Assert.AreEqual(0, mid.ChannelId); + Assert.AreEqual("RA ST6.2 ETV100 ", mid.TorqueControllerName); + Assert.AreEqual(" ", mid.VinNumber); + Assert.AreEqual(0, mid.JobId); + Assert.AreEqual(1, mid.ParameterSetId); + Assert.AreEqual(Strategy.TorqueControlAndAngleMonitoring, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.IsFalse(mid.TighteningStatus); + Assert.AreEqual(BatchStatus.NotUsed, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.TorqueMinLimit); - Assert.IsNotNull(mid.TorqueMaxLimit); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.AngleMinLimit); - Assert.IsNotNull(mid.AngleMaxLimit); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringMin); - Assert.IsNotNull(mid.CurrentMonitoringMax); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapMin); - Assert.IsNotNull(mid.SelftapMax); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMin); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMax); - Assert.IsNotNull(mid.PrevailTorque); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); - Assert.IsNotNull(mid.PrevailTorqueCompensateValue); + Assert.AreEqual(34m, mid.TorqueMinLimit); + Assert.AreEqual(46m, mid.TorqueMaxLimit); + Assert.AreEqual(40m, mid.TorqueFinalTarget); + Assert.AreEqual(5.05m, mid.Torque); + Assert.AreEqual(20, mid.AngleMinLimit); + Assert.AreEqual(420, mid.AngleMaxLimit); + Assert.AreEqual(0, mid.AngleFinalTarget); + Assert.AreEqual(0, mid.Angle); + Assert.AreEqual(100, mid.RundownAngleMin); + Assert.AreEqual(850, mid.RundownAngleMax); + Assert.AreEqual(4, mid.RundownAngle); + Assert.AreEqual(0, mid.CurrentMonitoringMin); + Assert.AreEqual(150, mid.CurrentMonitoringMax); + Assert.AreEqual(0, mid.CurrentMonitoringValue); + Assert.AreEqual(0m, mid.SelftapMin); + Assert.AreEqual(9999m, mid.SelftapMax); + Assert.AreEqual(0m, mid.SelftapTorque); + Assert.AreEqual(0m, mid.PrevailTorqueMonitoringMin); + Assert.AreEqual(0m, mid.PrevailTorqueMonitoringMax); + Assert.AreEqual(0m, mid.PrevailTorque); + Assert.AreEqual(184887, mid.TighteningId); + Assert.AreEqual(0, mid.JobSequenceNumber); + Assert.AreEqual(0, mid.SyncTighteningId); + Assert.AreEqual(" C0761275", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2020, 6, 25, 1, 4, 39), mid.Timestamp); + Assert.AreEqual(new DateTime(2020, 6, 24, 10, 48, 53), mid.LastChangeInParameterSet); + Assert.AreEqual("Test Parameter Set ", mid.ParameterSetName); + Assert.AreEqual(TorqueValuesUnit.LbfFt, mid.TorqueValuesUnit); + Assert.AreEqual(ResultType.BypassParameterSetResult, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); + Assert.AreEqual(15m, mid.PrevailTorqueCompensateValue); Assert.IsNotNull(mid.TighteningErrorStatus2); - Assert.IsNotNull(mid.CompensatedAngle); - Assert.IsNotNull(mid.FinalAngleDecimal); - Assert.AreNotEqual(0, mid.StartFinalAngle); - Assert.IsNotNull(mid.PostViewTorqueActivated); - Assert.AreNotEqual(0, mid.PostViewTorqueHigh); - Assert.AreNotEqual(0, mid.PostViewTorqueLow); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpere); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpereMin); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpereMax); - Assert.AreNotEqual(0, mid.AngleNumeratorScaleFactor); - Assert.AreNotEqual(0, mid.AngleDenominatorScaleFactor); - Assert.IsNotNull(mid.OverallAngleStatus); - Assert.AreNotEqual(0, mid.OverallAngleMin); - Assert.AreNotEqual(0, mid.OverallAngleMax); - Assert.AreNotEqual(0, mid.OverallAngle); - Assert.AreNotEqual(0, mid.PeakTorque); - Assert.AreNotEqual(0, mid.ResidualBreakawayTorque); - Assert.AreNotEqual(0, mid.StartRundownAngle); - Assert.AreNotEqual(0, mid.RundownAngleComplete); - Assert.AreNotEqual(0, mid.OverallAngle); - Assert.AreNotEqual(0, mid.ClickTorque); - Assert.AreNotEqual(0, mid.ClickAngle); + Assert.AreEqual(100m, mid.CompensatedAngle); + Assert.AreEqual(9999m, mid.FinalAngleDecimal); + Assert.AreEqual(10m, mid.StartFinalAngle); + Assert.AreEqual(PostViewTorque.OnlyPVTHOn, mid.PostViewTorqueActivated); + Assert.AreEqual(6521.43m, mid.PostViewTorqueHigh); + Assert.AreEqual(52.32m, mid.PostViewTorqueLow); + Assert.AreEqual(1m, mid.CurrentMonitoringAmpere); + Assert.AreEqual(0.50m, mid.CurrentMonitoringAmpereMin); + Assert.AreEqual(1.50m, mid.CurrentMonitoringAmpereMax); + Assert.AreEqual(1, mid.AngleNumeratorScaleFactor); + Assert.AreEqual(1, mid.AngleDenominatorScaleFactor); + Assert.AreEqual(TighteningValueStatus.Ok, mid.OverallAngleStatus); + Assert.AreEqual(-20, mid.OverallAngleMin); + Assert.AreEqual(100, mid.OverallAngleMax); + Assert.AreEqual(120, mid.OverallAngle); + Assert.AreEqual(32.91m, mid.PeakTorque); + Assert.AreEqual(10.05m, mid.ResidualBreakawayTorque); + Assert.AreEqual(1.10m, mid.StartRundownAngle); + Assert.AreEqual(90m, mid.RundownAngleComplete); + Assert.AreEqual(1.20m, mid.ClickTorque); + Assert.AreEqual(1, mid.ClickAngle); AssertEqualPackages(pack, mid); } @@ -1776,80 +1831,82 @@ public void Mid0061ByteRevision11() var mid = _midInterpreter.Parse(bytes.ToArray()); Assert.AreEqual(typeof(Mid0061), mid.GetType()); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.TorqueControllerName); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("airbag7 ", mid.TorqueControllerName); + Assert.AreEqual("KPOL3456JKLO897 ", mid.VinNumber); + Assert.AreEqual(6, mid.JobId); + Assert.AreEqual(3, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleReverse, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.IsFalse(mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.TorqueMinLimit); - Assert.IsNotNull(mid.TorqueMaxLimit); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.AngleMinLimit); - Assert.IsNotNull(mid.AngleMaxLimit); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringMin); - Assert.IsNotNull(mid.CurrentMonitoringMax); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapMin); - Assert.IsNotNull(mid.SelftapMax); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMin); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMax); - Assert.IsNotNull(mid.PrevailTorque); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); - Assert.IsNotNull(mid.PrevailTorqueCompensateValue); + Assert.AreEqual(8.40m, mid.TorqueMinLimit); + Assert.AreEqual(14m, mid.TorqueMaxLimit); + Assert.AreEqual(12m, mid.TorqueFinalTarget); + Assert.AreEqual(7.39m, mid.Torque); + Assert.AreEqual(0, mid.AngleMinLimit); + Assert.AreEqual(9999, mid.AngleMaxLimit); + Assert.AreEqual(0, mid.AngleFinalTarget); + Assert.AreEqual(0, mid.Angle); + Assert.AreEqual(0, mid.RundownAngleMin); + Assert.AreEqual(9999, mid.RundownAngleMax); + Assert.AreEqual(5000, mid.RundownAngle); + Assert.AreEqual(0, mid.CurrentMonitoringMin); + Assert.AreEqual(50, mid.CurrentMonitoringMax); + Assert.AreEqual(45, mid.CurrentMonitoringValue); + Assert.AreEqual(0.10m, mid.SelftapMin); + Assert.AreEqual(1.25m, mid.SelftapMax); + Assert.AreEqual(5.48m, mid.SelftapTorque); + Assert.AreEqual(0.10m, mid.PrevailTorqueMonitoringMin); + Assert.AreEqual(9999m, mid.PrevailTorqueMonitoringMax); + Assert.AreEqual(5555m, mid.PrevailTorque); + Assert.AreEqual(4294967295, mid.TighteningId); + Assert.AreEqual(65500, mid.JobSequenceNumber); + Assert.AreEqual(60535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123456", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2001, 6, 2, 9, 54, 9), mid.Timestamp); + Assert.AreEqual(new DateTime(2001, 5, 29, 12, 34, 33), mid.LastChangeInParameterSet); + Assert.AreEqual("Test Parameter Set ", mid.ParameterSetName); + Assert.AreEqual(TorqueValuesUnit.LbfFt, mid.TorqueValuesUnit); + Assert.AreEqual(ResultType.BypassParameterSetResult, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); + Assert.AreEqual(15m, mid.PrevailTorqueCompensateValue); Assert.IsNotNull(mid.TighteningErrorStatus2); - Assert.IsNotNull(mid.CompensatedAngle); - Assert.IsNotNull(mid.FinalAngleDecimal); - Assert.AreNotEqual(0, mid.StartFinalAngle); - Assert.IsNotNull(mid.PostViewTorqueActivated); - Assert.AreNotEqual(0, mid.PostViewTorqueHigh); - Assert.AreNotEqual(0, mid.PostViewTorqueLow); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpere); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpereMin); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpereMax); - Assert.AreNotEqual(0, mid.AngleNumeratorScaleFactor); - Assert.AreNotEqual(0, mid.AngleDenominatorScaleFactor); - Assert.IsNotNull(mid.OverallAngleStatus); - Assert.AreNotEqual(0, mid.OverallAngleMin); - Assert.AreNotEqual(0, mid.OverallAngleMax); - Assert.AreNotEqual(0, mid.OverallAngle); - Assert.AreNotEqual(0, mid.PeakTorque); - Assert.AreNotEqual(0, mid.ResidualBreakawayTorque); - Assert.AreNotEqual(0, mid.StartRundownAngle); - Assert.AreNotEqual(0, mid.RundownAngleComplete); - Assert.AreNotEqual(0, mid.OverallAngle); - Assert.AreNotEqual(0, mid.ClickTorque); - Assert.AreNotEqual(0, mid.ClickAngle); + Assert.AreEqual(100m, mid.CompensatedAngle); + Assert.AreEqual(9999m, mid.FinalAngleDecimal); + Assert.AreEqual(10m, mid.StartFinalAngle); + Assert.AreEqual(PostViewTorque.OnlyPVTHOn, mid.PostViewTorqueActivated); + Assert.AreEqual(6521.43m, mid.PostViewTorqueHigh); + Assert.AreEqual(52.32m, mid.PostViewTorqueLow); + Assert.AreEqual(1m, mid.CurrentMonitoringAmpere); + Assert.AreEqual(0.50m, mid.CurrentMonitoringAmpereMin); + Assert.AreEqual(1.50m, mid.CurrentMonitoringAmpereMax); + Assert.AreEqual(1, mid.AngleNumeratorScaleFactor); + Assert.AreEqual(1, mid.AngleDenominatorScaleFactor); + Assert.AreEqual(TighteningValueStatus.Ok, mid.OverallAngleStatus); + Assert.AreEqual(-20, mid.OverallAngleMin); + Assert.AreEqual(100, mid.OverallAngleMax); + Assert.AreEqual(120, mid.OverallAngle); + Assert.AreEqual(32.91m, mid.PeakTorque); + Assert.AreEqual(10.05m, mid.ResidualBreakawayTorque); + Assert.AreEqual(1.10m, mid.StartRundownAngle); + Assert.AreEqual(90m, mid.RundownAngleComplete); + Assert.AreEqual(1.20m, mid.ClickTorque); + Assert.AreEqual(1, mid.ClickAngle); AssertEqualPackages(bytes, mid); } @@ -1861,61 +1918,65 @@ public void Mid0061Revision998() var mid = _midInterpreter.Parse(pack); Assert.AreEqual(typeof(Mid0061), mid.GetType()); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.TorqueControllerName); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(0, mid.CellId); + Assert.AreEqual(0, mid.ChannelId); + Assert.AreEqual("RA ST6.2 ETV100 ", mid.TorqueControllerName); + Assert.AreEqual(" ", mid.VinNumber); + Assert.AreEqual(0, mid.JobId); + Assert.AreEqual(1, mid.ParameterSetId); + Assert.AreEqual(Strategy.TorqueControlAndAngleMonitoring, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.IsFalse(mid.TighteningStatus); + Assert.AreEqual(BatchStatus.NotUsed, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.TorqueMinLimit); - Assert.IsNotNull(mid.TorqueMaxLimit); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.AngleMinLimit); - Assert.IsNotNull(mid.AngleMaxLimit); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringMin); - Assert.IsNotNull(mid.CurrentMonitoringMax); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapMin); - Assert.IsNotNull(mid.SelftapMax); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMin); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMax); - Assert.IsNotNull(mid.PrevailTorque); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); - Assert.IsNotNull(mid.PrevailTorqueCompensateValue); + Assert.AreEqual(34m, mid.TorqueMinLimit); + Assert.AreEqual(46m, mid.TorqueMaxLimit); + Assert.AreEqual(40m, mid.TorqueFinalTarget); + Assert.AreEqual(5.05m, mid.Torque); + Assert.AreEqual(20, mid.AngleMinLimit); + Assert.AreEqual(420, mid.AngleMaxLimit); + Assert.AreEqual(0, mid.AngleFinalTarget); + Assert.AreEqual(0, mid.Angle); + Assert.AreEqual(100, mid.RundownAngleMin); + Assert.AreEqual(850, mid.RundownAngleMax); + Assert.AreEqual(4, mid.RundownAngle); + Assert.AreEqual(0, mid.CurrentMonitoringMin); + Assert.AreEqual(150, mid.CurrentMonitoringMax); + Assert.AreEqual(0, mid.CurrentMonitoringValue); + Assert.AreEqual(0m, mid.SelftapMin); + Assert.AreEqual(9999m, mid.SelftapMax); + Assert.AreEqual(0m, mid.SelftapTorque); + Assert.AreEqual(0m, mid.PrevailTorqueMonitoringMin); + Assert.AreEqual(0m, mid.PrevailTorqueMonitoringMax); + Assert.AreEqual(0m, mid.PrevailTorque); + Assert.AreEqual(184887, mid.TighteningId); + Assert.AreEqual(0, mid.JobSequenceNumber); + Assert.AreEqual(0, mid.SyncTighteningId); + Assert.AreEqual(" C0761275", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2020, 6, 25, 1, 4, 39), mid.Timestamp); + Assert.AreEqual(new DateTime(2020, 6, 24, 10, 48, 53), mid.LastChangeInParameterSet); + Assert.AreEqual("Test Parameter Set ", mid.ParameterSetName); + Assert.AreEqual(TorqueValuesUnit.LbfFt, mid.TorqueValuesUnit); + Assert.AreEqual(ResultType.BypassParameterSetResult, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); + Assert.AreEqual(15m, mid.PrevailTorqueCompensateValue); Assert.IsNotNull(mid.TighteningErrorStatus2); - Assert.IsNotNull(mid.NumberOfStagesInMultistage); - Assert.IsNotNull(mid.NumberOfStageResults); + Assert.AreEqual(2, mid.NumberOfStagesInMultistage); + Assert.AreEqual(2, mid.NumberOfStageResults); Assert.IsNotNull(mid.StageResults); + Assert.AreEqual(2, mid.StageResults.Count); AssertEqualPackages(pack, mid); } @@ -1979,61 +2040,65 @@ public void Mid0061ByteRevision998() var mid = _midInterpreter.Parse(bytes.ToArray()); Assert.AreEqual(typeof(Mid0061), mid.GetType()); - Assert.IsNotNull(mid.CellId); - Assert.IsNotNull(mid.ChannelId); - Assert.IsNotNull(mid.TorqueControllerName); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("airbag7 ", mid.TorqueControllerName); + Assert.AreEqual("KPOL3456JKLO897 ", mid.VinNumber); + Assert.AreEqual(6, mid.JobId); + Assert.AreEqual(3, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleReverse, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.IsFalse(mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.TorqueMinLimit); - Assert.IsNotNull(mid.TorqueMaxLimit); - Assert.IsNotNull(mid.TorqueFinalTarget); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.AngleMinLimit); - Assert.IsNotNull(mid.AngleMaxLimit); - Assert.IsNotNull(mid.AngleFinalTarget); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringMin); - Assert.IsNotNull(mid.CurrentMonitoringMax); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapMin); - Assert.IsNotNull(mid.SelftapMax); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMin); - Assert.IsNotNull(mid.PrevailTorqueMonitoringMax); - Assert.IsNotNull(mid.PrevailTorque); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.ParameterSetName); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); - Assert.IsNotNull(mid.PrevailTorqueCompensateValue); + Assert.AreEqual(8.40m, mid.TorqueMinLimit); + Assert.AreEqual(14m, mid.TorqueMaxLimit); + Assert.AreEqual(12m, mid.TorqueFinalTarget); + Assert.AreEqual(7.39m, mid.Torque); + Assert.AreEqual(0, mid.AngleMinLimit); + Assert.AreEqual(9999, mid.AngleMaxLimit); + Assert.AreEqual(0, mid.AngleFinalTarget); + Assert.AreEqual(0, mid.Angle); + Assert.AreEqual(0, mid.RundownAngleMin); + Assert.AreEqual(9999, mid.RundownAngleMax); + Assert.AreEqual(5000, mid.RundownAngle); + Assert.AreEqual(0, mid.CurrentMonitoringMin); + Assert.AreEqual(50, mid.CurrentMonitoringMax); + Assert.AreEqual(45, mid.CurrentMonitoringValue); + Assert.AreEqual(0.10m, mid.SelftapMin); + Assert.AreEqual(1.25m, mid.SelftapMax); + Assert.AreEqual(5.48m, mid.SelftapTorque); + Assert.AreEqual(0.10m, mid.PrevailTorqueMonitoringMin); + Assert.AreEqual(9999m, mid.PrevailTorqueMonitoringMax); + Assert.AreEqual(5555m, mid.PrevailTorque); + Assert.AreEqual(4294967295, mid.TighteningId); + Assert.AreEqual(65500, mid.JobSequenceNumber); + Assert.AreEqual(60535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123456", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2001, 6, 2, 9, 54, 9), mid.Timestamp); + Assert.AreEqual(new DateTime(2001, 5, 29, 12, 34, 33), mid.LastChangeInParameterSet); + Assert.AreEqual("Test Parameter Set ", mid.ParameterSetName); + Assert.AreEqual(TorqueValuesUnit.LbfFt, mid.TorqueValuesUnit); + Assert.AreEqual(ResultType.BypassParameterSetResult, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); + Assert.AreEqual(15m, mid.PrevailTorqueCompensateValue); Assert.IsNotNull(mid.TighteningErrorStatus2); - Assert.IsNotNull(mid.NumberOfStagesInMultistage); - Assert.IsNotNull(mid.NumberOfStageResults); + Assert.AreEqual(2, mid.NumberOfStagesInMultistage); + Assert.AreEqual(2, mid.NumberOfStageResults); Assert.IsNotNull(mid.StageResults); + Assert.AreEqual(2, mid.StageResults.Count); AssertEqualPackages(bytes, mid); } @@ -2045,20 +2110,20 @@ public void Mid0061Revision999() var mid = _midInterpreter.Parse(pack); Assert.AreEqual(typeof(Mid0061), mid.GetType()); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.TighteningId); + Assert.AreEqual("KPOL3456JKLO897 ", mid.VinNumber); + Assert.AreEqual(2, mid.JobId); + Assert.AreEqual(1, mid.ParameterSetId); + Assert.AreEqual(20, mid.BatchSize); + Assert.AreEqual(19, mid.BatchCounter); + Assert.AreEqual(BatchStatus.NotUsed, mid.BatchStatus); + Assert.IsTrue(mid.TighteningStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.AngleStatus); + Assert.AreEqual(5m, mid.Torque); + Assert.AreEqual(360, mid.Angle); + Assert.AreEqual(new DateTime(2001, 6, 2, 9, 54, 9), mid.Timestamp); + Assert.AreEqual(new DateTime(2000, 6, 2, 9, 54, 9), mid.LastChangeInParameterSet); + Assert.AreEqual(4294967295L, mid.TighteningId); AssertEqualPackages(pack, mid); } @@ -2072,20 +2137,20 @@ public void Mid0061ByteRevision999() Assert.AreEqual(typeof(Mid0061), mid.GetType()); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.LastChangeInParameterSet); - Assert.IsNotNull(mid.TighteningId); + Assert.AreEqual("KPOL3456JKLO897 ", mid.VinNumber); + Assert.AreEqual(2, mid.JobId); + Assert.AreEqual(1, mid.ParameterSetId); + Assert.AreEqual(20, mid.BatchSize); + Assert.AreEqual(19, mid.BatchCounter); + Assert.AreEqual(BatchStatus.NotUsed, mid.BatchStatus); + Assert.IsTrue(mid.TighteningStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.AngleStatus); + Assert.AreEqual(5m, mid.Torque); + Assert.AreEqual(360, mid.Angle); + Assert.AreEqual(new DateTime(2001, 6, 2, 9, 54, 9), mid.Timestamp); + Assert.AreEqual(new DateTime(2000, 6, 2, 9, 54, 9), mid.LastChangeInParameterSet); + Assert.AreEqual(4294967295L, mid.TighteningId); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/Tightening/TestMid0064.cs b/src/MIDTesters.Core/Tightening/TestMid0064.cs index d34b0a55..afc086f9 100644 --- a/src/MIDTesters.Core/Tightening/TestMid0064.cs +++ b/src/MIDTesters.Core/Tightening/TestMid0064.cs @@ -15,7 +15,7 @@ public void Mid0064Revision1() var mid = _midInterpreter.Parse(package); Assert.AreEqual(typeof(Mid0064), mid.GetType()); - Assert.IsNotNull(mid.TighteningId); + Assert.AreEqual(123456789L, mid.TighteningId); AssertEqualPackages(package, mid); } @@ -28,7 +28,7 @@ public void Mid0064ByteRevision1() var mid = _midInterpreter.Parse(bytes); Assert.AreEqual(typeof(Mid0064), mid.GetType()); - Assert.IsNotNull(mid.TighteningId); + Assert.AreEqual(123456789L, mid.TighteningId); AssertEqualPackages(bytes, mid); } @@ -40,7 +40,7 @@ public void Mid0064Revision10() var mid = _midInterpreter.Parse(package); Assert.AreEqual(typeof(Mid0064), mid.GetType()); - Assert.IsNotNull(mid.TighteningId); + Assert.AreEqual(123456789L, mid.TighteningId); Assert.IsTrue(mid.OfflineResult); AssertEqualPackages(package, mid); } @@ -54,7 +54,7 @@ public void Mid0064ByteRevision10() var mid = _midInterpreter.Parse(bytes); Assert.AreEqual(typeof(Mid0064), mid.GetType()); - Assert.IsNotNull(mid.TighteningId); + Assert.AreEqual(123456789L, mid.TighteningId); Assert.IsTrue(mid.OfflineResult); AssertEqualPackages(bytes, mid); } diff --git a/src/MIDTesters.Core/Tightening/TestMid0065.cs b/src/MIDTesters.Core/Tightening/TestMid0065.cs index 232307d5..e9ab52bd 100644 --- a/src/MIDTesters.Core/Tightening/TestMid0065.cs +++ b/src/MIDTesters.Core/Tightening/TestMid0065.cs @@ -1,4 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; +using System; using OpenProtocolInterpreter.Tightening; using System.Collections.Generic; @@ -16,17 +18,17 @@ public void Mid0065Revision1() var mid = _midInterpreter.Parse(package); Assert.AreEqual(typeof(Mid0065), mid.GetType()); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.BatchStatus); + Assert.AreEqual(123456789L, mid.TighteningId); + Assert.AreEqual("AIRBAG ", mid.VinNumber); + Assert.AreEqual(1, mid.ParameterSetId); + Assert.AreEqual(2, mid.BatchCounter); + Assert.AreEqual(false, mid.TighteningStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(14.67m, mid.Torque); + Assert.AreEqual(46, mid.Angle); + Assert.AreEqual(new DateTime(2001,4,22,14,54,34), mid.Timestamp); + Assert.AreEqual(BatchStatus.NotUsed, mid.BatchStatus); AssertEqualPackages(package, mid); } @@ -39,17 +41,17 @@ public void Mid0065ByteRevision1() var mid = _midInterpreter.Parse(bytes); Assert.AreEqual(typeof(Mid0065), mid.GetType()); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.BatchStatus); + Assert.AreEqual(123456789L, mid.TighteningId); + Assert.AreEqual("AIRBAG ", mid.VinNumber); + Assert.AreEqual(1, mid.ParameterSetId); + Assert.AreEqual(2, mid.BatchCounter); + Assert.AreEqual(false, mid.TighteningStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(14.67m, mid.Torque); + Assert.AreEqual(46, mid.Angle); + Assert.AreEqual(new DateTime(2001,4,22,14,54,34), mid.Timestamp); + Assert.AreEqual(BatchStatus.NotUsed, mid.BatchStatus); AssertEqualPackages(bytes, mid); } @@ -61,33 +63,34 @@ public void Mid0065Revision2() var mid = _midInterpreter.Parse(package); Assert.AreEqual(typeof(Mid0065), mid.GetType()); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(123456789L, mid.TighteningId); + Assert.AreEqual("AIRBAG ", mid.VinNumber); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleForward, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueMonitoringStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.AreEqual(false, mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); + Assert.AreEqual(10m, mid.Torque); + Assert.AreEqual(360, mid.Angle); + Assert.AreEqual(250, mid.RundownAngle); + Assert.AreEqual(2, mid.CurrentMonitoringValue); + Assert.AreEqual(2.15m, mid.SelftapTorque); + Assert.AreEqual(10.25m, mid.PrevailTorque); + Assert.AreEqual(12, mid.JobSequenceNumber); + Assert.AreEqual(65535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123 ", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2019,1,5,9,0,5), mid.Timestamp); AssertEqualPackages(package, mid); } @@ -133,33 +136,34 @@ public void Mid0065ByteRevision2() var mid = _midInterpreter.Parse(bytes.ToArray()); Assert.AreEqual(typeof(Mid0065), mid.GetType()); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(123456789L, mid.TighteningId); + Assert.AreEqual("AIRBAG ", mid.VinNumber); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleForward, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueMonitoringStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.AreEqual(false, mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); + Assert.AreEqual(10m, mid.Torque); + Assert.AreEqual(360, mid.Angle); + Assert.AreEqual(250, mid.RundownAngle); + Assert.AreEqual(2, mid.CurrentMonitoringValue); + Assert.AreEqual(2.15m, mid.SelftapTorque); + Assert.AreEqual(10.25m, mid.PrevailTorque); + Assert.AreEqual(12, mid.JobSequenceNumber); + Assert.AreEqual(65535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123 ", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2019,1,5,9,0,5), mid.Timestamp); AssertEqualPackages(bytes, mid); } @@ -171,35 +175,36 @@ public void Mid0065Revision3() var mid = _midInterpreter.Parse(package); Assert.AreEqual(typeof(Mid0065), mid.GetType()); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(123456789L, mid.TighteningId); + Assert.AreEqual("AIRBAG ", mid.VinNumber); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleForward, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueMonitoringStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.AreEqual(false, mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); + Assert.AreEqual(10m, mid.Torque); + Assert.AreEqual(360, mid.Angle); + Assert.AreEqual(250, mid.RundownAngle); + Assert.AreEqual(2, mid.CurrentMonitoringValue); + Assert.AreEqual(2.15m, mid.SelftapTorque); + Assert.AreEqual(10.25m, mid.PrevailTorque); + Assert.AreEqual(12, mid.JobSequenceNumber); + Assert.AreEqual(65535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123 ", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2019,1,5,9,0,5), mid.Timestamp); + Assert.AreEqual(TorqueValuesUnit.Percentage, mid.TorqueValuesUnit); + Assert.AreEqual((ResultType)8, mid.ResultType); AssertEqualPackages(package, mid); } @@ -245,35 +250,36 @@ public void Mid0065ByteRevision3() var mid = _midInterpreter.Parse(bytes.ToArray()); Assert.AreEqual(typeof(Mid0065), mid.GetType()); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(123456789L, mid.TighteningId); + Assert.AreEqual("AIRBAG ", mid.VinNumber); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleForward, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueMonitoringStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.AreEqual(false, mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); + Assert.AreEqual(10m, mid.Torque); + Assert.AreEqual(360, mid.Angle); + Assert.AreEqual(250, mid.RundownAngle); + Assert.AreEqual(2, mid.CurrentMonitoringValue); + Assert.AreEqual(2.15m, mid.SelftapTorque); + Assert.AreEqual(10.25m, mid.PrevailTorque); + Assert.AreEqual(12, mid.JobSequenceNumber); + Assert.AreEqual(65535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123 ", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2019,1,5,9,0,5), mid.Timestamp); + Assert.AreEqual(TorqueValuesUnit.Percentage, mid.TorqueValuesUnit); + Assert.AreEqual((ResultType)8, mid.ResultType); AssertEqualPackages(bytes, mid); } @@ -285,38 +291,39 @@ public void Mid0065Revision4() var mid = _midInterpreter.Parse(package); Assert.AreEqual(typeof(Mid0065), mid.GetType()); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(123456789L, mid.TighteningId); + Assert.AreEqual("AIRBAG ", mid.VinNumber); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleForward, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueMonitoringStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.AreEqual(false, mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); + Assert.AreEqual(10m, mid.Torque); + Assert.AreEqual(360, mid.Angle); + Assert.AreEqual(250, mid.RundownAngle); + Assert.AreEqual(2, mid.CurrentMonitoringValue); + Assert.AreEqual(2.15m, mid.SelftapTorque); + Assert.AreEqual(10.25m, mid.PrevailTorque); + Assert.AreEqual(12, mid.JobSequenceNumber); + Assert.AreEqual(65535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123 ", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2019,1,5,9,0,5), mid.Timestamp); + Assert.AreEqual(TorqueValuesUnit.Percentage, mid.TorqueValuesUnit); + Assert.AreEqual((ResultType)8, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); AssertEqualPackages(package, mid); } @@ -362,38 +369,39 @@ public void Mid0065ByteRevision4() var mid = _midInterpreter.Parse(bytes.ToArray()); Assert.AreEqual(typeof(Mid0065), mid.GetType()); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(123456789L, mid.TighteningId); + Assert.AreEqual("AIRBAG ", mid.VinNumber); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleForward, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueMonitoringStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.AreEqual(false, mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); + Assert.AreEqual(10m, mid.Torque); + Assert.AreEqual(360, mid.Angle); + Assert.AreEqual(250, mid.RundownAngle); + Assert.AreEqual(2, mid.CurrentMonitoringValue); + Assert.AreEqual(2.15m, mid.SelftapTorque); + Assert.AreEqual(10.25m, mid.PrevailTorque); + Assert.AreEqual(12, mid.JobSequenceNumber); + Assert.AreEqual(65535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123 ", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2019,1,5,9,0,5), mid.Timestamp); + Assert.AreEqual(TorqueValuesUnit.Percentage, mid.TorqueValuesUnit); + Assert.AreEqual((ResultType)8, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); AssertEqualPackages(bytes, mid); } @@ -405,39 +413,40 @@ public void Mid0065Revision5() var mid = _midInterpreter.Parse(package); Assert.AreEqual(typeof(Mid0065), mid.GetType()); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(123456789L, mid.TighteningId); + Assert.AreEqual("AIRBAG ", mid.VinNumber); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleForward, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueMonitoringStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.AreEqual(false, mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); + Assert.AreEqual(10m, mid.Torque); + Assert.AreEqual(360, mid.Angle); + Assert.AreEqual(250, mid.RundownAngle); + Assert.AreEqual(2, mid.CurrentMonitoringValue); + Assert.AreEqual(2.15m, mid.SelftapTorque); + Assert.AreEqual(10.25m, mid.PrevailTorque); + Assert.AreEqual(12, mid.JobSequenceNumber); + Assert.AreEqual(65535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123 ", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2019,1,5,9,0,5), mid.Timestamp); + Assert.AreEqual(TorqueValuesUnit.Percentage, mid.TorqueValuesUnit); + Assert.AreEqual((ResultType)8, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); AssertEqualPackages(package, mid); } @@ -483,39 +492,40 @@ public void Mid0065ByteRevision5() var mid = _midInterpreter.Parse(bytes.ToArray()); Assert.AreEqual(typeof(Mid0065), mid.GetType()); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(123456789L, mid.TighteningId); + Assert.AreEqual("AIRBAG ", mid.VinNumber); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleForward, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueMonitoringStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.AreEqual(false, mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); + Assert.AreEqual(10m, mid.Torque); + Assert.AreEqual(360, mid.Angle); + Assert.AreEqual(250, mid.RundownAngle); + Assert.AreEqual(2, mid.CurrentMonitoringValue); + Assert.AreEqual(2.15m, mid.SelftapTorque); + Assert.AreEqual(10.25m, mid.PrevailTorque); + Assert.AreEqual(12, mid.JobSequenceNumber); + Assert.AreEqual(65535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123 ", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2019,1,5,9,0,5), mid.Timestamp); + Assert.AreEqual(TorqueValuesUnit.Percentage, mid.TorqueValuesUnit); + Assert.AreEqual((ResultType)8, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); AssertEqualPackages(bytes, mid); } @@ -527,40 +537,41 @@ public void Mid0065Revision6() var mid = _midInterpreter.Parse(package); Assert.AreEqual(typeof(Mid0065), mid.GetType()); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(123456789L, mid.TighteningId); + Assert.AreEqual("AIRBAG ", mid.VinNumber); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleForward, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueMonitoringStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.AreEqual(false, mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); - Assert.IsNotNull(mid.PrevailTorqueCompensateValue); + Assert.AreEqual(10m, mid.Torque); + Assert.AreEqual(360, mid.Angle); + Assert.AreEqual(250, mid.RundownAngle); + Assert.AreEqual(2, mid.CurrentMonitoringValue); + Assert.AreEqual(2.15m, mid.SelftapTorque); + Assert.AreEqual(10.25m, mid.PrevailTorque); + Assert.AreEqual(12, mid.JobSequenceNumber); + Assert.AreEqual(65535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123 ", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2019,1,5,9,0,5), mid.Timestamp); + Assert.AreEqual(TorqueValuesUnit.Percentage, mid.TorqueValuesUnit); + Assert.AreEqual((ResultType)8, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); + Assert.AreEqual(51m, mid.PrevailTorqueCompensateValue); Assert.IsNotNull(mid.TighteningErrorStatus2); AssertEqualPackages(package, mid); } @@ -622,40 +633,41 @@ public void Mid0065ByteRevision6() var mid = _midInterpreter.Parse(bytes.ToArray()); Assert.AreEqual(typeof(Mid0065), mid.GetType()); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(123456789L, mid.TighteningId); + Assert.AreEqual("AIRBAG ", mid.VinNumber); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleForward, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueMonitoringStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.AreEqual(false, mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); - Assert.IsNotNull(mid.PrevailTorqueCompensateValue); + Assert.AreEqual(10m, mid.Torque); + Assert.AreEqual(360, mid.Angle); + Assert.AreEqual(250, mid.RundownAngle); + Assert.AreEqual(2, mid.CurrentMonitoringValue); + Assert.AreEqual(2.15m, mid.SelftapTorque); + Assert.AreEqual(10.25m, mid.PrevailTorque); + Assert.AreEqual(12, mid.JobSequenceNumber); + Assert.AreEqual(65535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123 ", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2019,1,5,9,0,5), mid.Timestamp); + Assert.AreEqual(TorqueValuesUnit.Percentage, mid.TorqueValuesUnit); + Assert.AreEqual((ResultType)8, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); + Assert.AreEqual(51m, mid.PrevailTorqueCompensateValue); Assert.IsNotNull(mid.TighteningErrorStatus2); AssertEqualPackages(bytes, mid); } @@ -668,43 +680,44 @@ public void Mid0065Revision7() var mid = _midInterpreter.Parse(package); Assert.AreEqual(typeof(Mid0065), mid.GetType()); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(123456789L, mid.TighteningId); + Assert.AreEqual("AIRBAG ", mid.VinNumber); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleForward, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueMonitoringStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.AreEqual(false, mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); - Assert.IsNotNull(mid.PrevailTorqueCompensateValue); + Assert.AreEqual(10m, mid.Torque); + Assert.AreEqual(360, mid.Angle); + Assert.AreEqual(250, mid.RundownAngle); + Assert.AreEqual(2, mid.CurrentMonitoringValue); + Assert.AreEqual(2.15m, mid.SelftapTorque); + Assert.AreEqual(10.25m, mid.PrevailTorque); + Assert.AreEqual(12, mid.JobSequenceNumber); + Assert.AreEqual(65535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123 ", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2019,1,5,9,0,5), mid.Timestamp); + Assert.AreEqual(TorqueValuesUnit.Percentage, mid.TorqueValuesUnit); + Assert.AreEqual((ResultType)8, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); + Assert.AreEqual(51m, mid.PrevailTorqueCompensateValue); Assert.IsNotNull(mid.TighteningErrorStatus2); - Assert.AreNotEqual(0, mid.StationId); - Assert.IsNotNull(mid.StationName); + Assert.AreEqual(4294967295, mid.StationId); + Assert.AreEqual("Station Name ", mid.StationName); AssertEqualPackages(package, mid); } @@ -768,43 +781,44 @@ public void Mid0065ByteRevision7() var mid = _midInterpreter.Parse(bytes.ToArray()); Assert.AreEqual(typeof(Mid0065), mid.GetType()); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(123456789L, mid.TighteningId); + Assert.AreEqual("AIRBAG ", mid.VinNumber); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleForward, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueMonitoringStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.AreEqual(false, mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); - Assert.IsNotNull(mid.PrevailTorqueCompensateValue); + Assert.AreEqual(10m, mid.Torque); + Assert.AreEqual(360, mid.Angle); + Assert.AreEqual(250, mid.RundownAngle); + Assert.AreEqual(2, mid.CurrentMonitoringValue); + Assert.AreEqual(2.15m, mid.SelftapTorque); + Assert.AreEqual(10.25m, mid.PrevailTorque); + Assert.AreEqual(12, mid.JobSequenceNumber); + Assert.AreEqual(65535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123 ", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2019,1,5,9,0,5), mid.Timestamp); + Assert.AreEqual(TorqueValuesUnit.Percentage, mid.TorqueValuesUnit); + Assert.AreEqual((ResultType)8, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); + Assert.AreEqual(51m, mid.PrevailTorqueCompensateValue); Assert.IsNotNull(mid.TighteningErrorStatus2); - Assert.AreNotEqual(0, mid.StationId); - Assert.IsNotNull(mid.StationName); + Assert.AreEqual(4294967295, mid.StationId); + Assert.AreEqual("Station Name ", mid.StationName); AssertEqualPackages(bytes, mid); } @@ -816,47 +830,48 @@ public void Mid0065Revision8() var mid = _midInterpreter.Parse(package); Assert.AreEqual(typeof(Mid0065), mid.GetType()); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(123456789L, mid.TighteningId); + Assert.AreEqual("AIRBAG ", mid.VinNumber); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleForward, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueMonitoringStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.AreEqual(false, mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); - Assert.IsNotNull(mid.PrevailTorqueCompensateValue); + Assert.AreEqual(10m, mid.Torque); + Assert.AreEqual(360, mid.Angle); + Assert.AreEqual(250, mid.RundownAngle); + Assert.AreEqual(2, mid.CurrentMonitoringValue); + Assert.AreEqual(2.15m, mid.SelftapTorque); + Assert.AreEqual(10.25m, mid.PrevailTorque); + Assert.AreEqual(12, mid.JobSequenceNumber); + Assert.AreEqual(65535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123 ", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2019,1,5,9,0,5), mid.Timestamp); + Assert.AreEqual(TorqueValuesUnit.Percentage, mid.TorqueValuesUnit); + Assert.AreEqual((ResultType)8, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); + Assert.AreEqual(51m, mid.PrevailTorqueCompensateValue); Assert.IsNotNull(mid.TighteningErrorStatus2); - Assert.AreNotEqual(0, mid.StationId); - Assert.IsNotNull(mid.StationName); - Assert.AreNotEqual(0, mid.StartFinalAngle); - Assert.IsNotNull(mid.PostViewTorqueActivated); - Assert.AreNotEqual(0, mid.PostViewTorqueHigh); - Assert.AreNotEqual(0, mid.PostViewTorqueLow); + Assert.AreEqual(4294967295, mid.StationId); + Assert.AreEqual("Station Name ", mid.StationName); + Assert.AreEqual(10m, mid.StartFinalAngle); + Assert.AreEqual(PostViewTorque.OnlyPVTHOn, mid.PostViewTorqueActivated); + Assert.AreEqual(6521.43m, mid.PostViewTorqueHigh); + Assert.AreEqual(52.32m, mid.PostViewTorqueLow); AssertEqualPackages(package, mid); } @@ -920,47 +935,48 @@ public void Mid0065ByteRevision8() var mid = _midInterpreter.Parse(bytes.ToArray()); Assert.AreEqual(typeof(Mid0065), mid.GetType()); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(123456789L, mid.TighteningId); + Assert.AreEqual("AIRBAG ", mid.VinNumber); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleForward, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueMonitoringStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.AreEqual(false, mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); - Assert.IsNotNull(mid.PrevailTorqueCompensateValue); + Assert.AreEqual(10m, mid.Torque); + Assert.AreEqual(360, mid.Angle); + Assert.AreEqual(250, mid.RundownAngle); + Assert.AreEqual(2, mid.CurrentMonitoringValue); + Assert.AreEqual(2.15m, mid.SelftapTorque); + Assert.AreEqual(10.25m, mid.PrevailTorque); + Assert.AreEqual(12, mid.JobSequenceNumber); + Assert.AreEqual(65535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123 ", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2019,1,5,9,0,5), mid.Timestamp); + Assert.AreEqual(TorqueValuesUnit.Percentage, mid.TorqueValuesUnit); + Assert.AreEqual((ResultType)8, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); + Assert.AreEqual(51m, mid.PrevailTorqueCompensateValue); Assert.IsNotNull(mid.TighteningErrorStatus2); - Assert.AreNotEqual(0, mid.StationId); - Assert.IsNotNull(mid.StationName); - Assert.AreNotEqual(0, mid.StartFinalAngle); - Assert.IsNotNull(mid.PostViewTorqueActivated); - Assert.AreNotEqual(0, mid.PostViewTorqueHigh); - Assert.AreNotEqual(0, mid.PostViewTorqueLow); + Assert.AreEqual(4294967295, mid.StationId); + Assert.AreEqual("Station Name ", mid.StationName); + Assert.AreEqual(10m, mid.StartFinalAngle); + Assert.AreEqual(PostViewTorque.OnlyPVTHOn, mid.PostViewTorqueActivated); + Assert.AreEqual(6521.43m, mid.PostViewTorqueHigh); + Assert.AreEqual(52.32m, mid.PostViewTorqueLow); AssertEqualPackages(bytes, mid); } @@ -972,50 +988,51 @@ public void Mid0065Revision9() var mid = _midInterpreter.Parse(package); Assert.AreEqual(typeof(Mid0065), mid.GetType()); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(123456789L, mid.TighteningId); + Assert.AreEqual("AIRBAG ", mid.VinNumber); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleForward, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueMonitoringStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.AreEqual(false, mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); - Assert.IsNotNull(mid.PrevailTorqueCompensateValue); + Assert.AreEqual(10m, mid.Torque); + Assert.AreEqual(360, mid.Angle); + Assert.AreEqual(250, mid.RundownAngle); + Assert.AreEqual(2, mid.CurrentMonitoringValue); + Assert.AreEqual(2.15m, mid.SelftapTorque); + Assert.AreEqual(10.25m, mid.PrevailTorque); + Assert.AreEqual(12, mid.JobSequenceNumber); + Assert.AreEqual(65535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123 ", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2019,1,5,9,0,5), mid.Timestamp); + Assert.AreEqual(TorqueValuesUnit.Percentage, mid.TorqueValuesUnit); + Assert.AreEqual((ResultType)8, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); + Assert.AreEqual(51m, mid.PrevailTorqueCompensateValue); Assert.IsNotNull(mid.TighteningErrorStatus2); - Assert.AreNotEqual(0, mid.StationId); - Assert.IsNotNull(mid.StationName); - Assert.AreNotEqual(0, mid.StartFinalAngle); - Assert.IsNotNull(mid.PostViewTorqueActivated); - Assert.AreNotEqual(0, mid.PostViewTorqueHigh); - Assert.AreNotEqual(0, mid.PostViewTorqueLow); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpere); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpereMin); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpereMax); + Assert.AreEqual(4294967295, mid.StationId); + Assert.AreEqual("Station Name ", mid.StationName); + Assert.AreEqual(10m, mid.StartFinalAngle); + Assert.AreEqual(PostViewTorque.OnlyPVTHOn, mid.PostViewTorqueActivated); + Assert.AreEqual(6521.43m, mid.PostViewTorqueHigh); + Assert.AreEqual(52.32m, mid.PostViewTorqueLow); + Assert.AreEqual(1m, mid.CurrentMonitoringAmpere); + Assert.AreEqual(0.5m, mid.CurrentMonitoringAmpereMin); + Assert.AreEqual(1.5m, mid.CurrentMonitoringAmpereMax); AssertEqualPackages(package, mid); } @@ -1079,50 +1096,51 @@ public void Mid0065ByteRevision9() var mid = _midInterpreter.Parse(bytes.ToArray()); Assert.AreEqual(typeof(Mid0065), mid.GetType()); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(123456789L, mid.TighteningId); + Assert.AreEqual("AIRBAG ", mid.VinNumber); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleForward, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueMonitoringStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.AreEqual(false, mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); - Assert.IsNotNull(mid.PrevailTorqueCompensateValue); + Assert.AreEqual(10m, mid.Torque); + Assert.AreEqual(360, mid.Angle); + Assert.AreEqual(250, mid.RundownAngle); + Assert.AreEqual(2, mid.CurrentMonitoringValue); + Assert.AreEqual(2.15m, mid.SelftapTorque); + Assert.AreEqual(10.25m, mid.PrevailTorque); + Assert.AreEqual(12, mid.JobSequenceNumber); + Assert.AreEqual(65535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123 ", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2019,1,5,9,0,5), mid.Timestamp); + Assert.AreEqual(TorqueValuesUnit.Percentage, mid.TorqueValuesUnit); + Assert.AreEqual((ResultType)8, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); + Assert.AreEqual(51m, mid.PrevailTorqueCompensateValue); Assert.IsNotNull(mid.TighteningErrorStatus2); - Assert.AreNotEqual(0, mid.StationId); - Assert.IsNotNull(mid.StationName); - Assert.AreNotEqual(0, mid.StartFinalAngle); - Assert.IsNotNull(mid.PostViewTorqueActivated); - Assert.AreNotEqual(0, mid.PostViewTorqueHigh); - Assert.AreNotEqual(0, mid.PostViewTorqueLow); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpere); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpereMin); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpereMax); + Assert.AreEqual(4294967295, mid.StationId); + Assert.AreEqual("Station Name ", mid.StationName); + Assert.AreEqual(10m, mid.StartFinalAngle); + Assert.AreEqual(PostViewTorque.OnlyPVTHOn, mid.PostViewTorqueActivated); + Assert.AreEqual(6521.43m, mid.PostViewTorqueHigh); + Assert.AreEqual(52.32m, mid.PostViewTorqueLow); + Assert.AreEqual(1m, mid.CurrentMonitoringAmpere); + Assert.AreEqual(0.5m, mid.CurrentMonitoringAmpereMin); + Assert.AreEqual(1.5m, mid.CurrentMonitoringAmpereMax); AssertEqualPackages(bytes, mid); } @@ -1134,61 +1152,61 @@ public void Mid0065Revision10() var mid = _midInterpreter.Parse(pack); Assert.AreEqual(typeof(Mid0065), mid.GetType()); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(123456789L, mid.TighteningId); + Assert.AreEqual("AIRBAG ", mid.VinNumber); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleForward, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueMonitoringStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.AreEqual(false, mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); - Assert.IsNotNull(mid.PrevailTorqueCompensateValue); + Assert.AreEqual(10m, mid.Torque); + Assert.AreEqual(360, mid.Angle); + Assert.AreEqual(250, mid.RundownAngle); + Assert.AreEqual(2, mid.CurrentMonitoringValue); + Assert.AreEqual(2.15m, mid.SelftapTorque); + Assert.AreEqual(10.25m, mid.PrevailTorque); + Assert.AreEqual(12, mid.JobSequenceNumber); + Assert.AreEqual(65535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123 ", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2019,1,5,9,0,5), mid.Timestamp); + Assert.AreEqual(TorqueValuesUnit.Percentage, mid.TorqueValuesUnit); + Assert.AreEqual((ResultType)8, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); + Assert.AreEqual(51m, mid.PrevailTorqueCompensateValue); Assert.IsNotNull(mid.TighteningErrorStatus2); - Assert.AreNotEqual(0, mid.StationId); - Assert.IsNotNull(mid.StationName); - Assert.AreNotEqual(0, mid.StartFinalAngle); - Assert.IsNotNull(mid.PostViewTorqueActivated); - Assert.AreNotEqual(0, mid.PostViewTorqueHigh); - Assert.AreNotEqual(0, mid.PostViewTorqueLow); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpere); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpereMin); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpereMax); - Assert.AreNotEqual(0, mid.AngleNumeratorScaleFactor); - Assert.AreNotEqual(0, mid.AngleDenominatorScaleFactor); - Assert.IsNotNull(mid.OverallAngleStatus); - Assert.AreNotEqual(0, mid.OverallAngleMin); - Assert.AreNotEqual(0, mid.OverallAngleMax); - Assert.AreNotEqual(0, mid.OverallAngle); - Assert.AreNotEqual(0, mid.PeakTorque); - Assert.AreNotEqual(0, mid.ResidualBreakawayTorque); - Assert.AreNotEqual(0, mid.StartRundownAngle); - Assert.AreNotEqual(0, mid.RundownAngleComplete); - + Assert.AreEqual(4294967295, mid.StationId); + Assert.AreEqual("Station Name ", mid.StationName); + Assert.AreEqual(10m, mid.StartFinalAngle); + Assert.AreEqual(PostViewTorque.OnlyPVTHOn, mid.PostViewTorqueActivated); + Assert.AreEqual(6521.43m, mid.PostViewTorqueHigh); + Assert.AreEqual(52.32m, mid.PostViewTorqueLow); + Assert.AreEqual(1m, mid.CurrentMonitoringAmpere); + Assert.AreEqual(0.5m, mid.CurrentMonitoringAmpereMin); + Assert.AreEqual(1.5m, mid.CurrentMonitoringAmpereMax); + Assert.AreEqual(1, mid.AngleNumeratorScaleFactor); + Assert.AreEqual(1, mid.AngleDenominatorScaleFactor); + Assert.AreEqual(TighteningValueStatus.Ok, mid.OverallAngleStatus); + Assert.AreEqual(-20, mid.OverallAngleMin); + Assert.AreEqual(100, mid.OverallAngleMax); + Assert.AreEqual(120, mid.OverallAngle); + Assert.AreEqual(32.91m, mid.PeakTorque); + Assert.AreEqual(10.05m, mid.ResidualBreakawayTorque); + Assert.AreEqual(1.1m, mid.StartRundownAngle); + Assert.AreEqual(90m, mid.RundownAngleComplete); AssertEqualPackages(pack, mid); } @@ -1252,61 +1270,61 @@ public void Mid0065ByteRevision10() var mid = _midInterpreter.Parse(bytes.ToArray()); Assert.AreEqual(typeof(Mid0065), mid.GetType()); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(123456789L, mid.TighteningId); + Assert.AreEqual("AIRBAG ", mid.VinNumber); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleForward, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueMonitoringStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.AreEqual(false, mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); - Assert.IsNotNull(mid.PrevailTorqueCompensateValue); + Assert.AreEqual(10m, mid.Torque); + Assert.AreEqual(360, mid.Angle); + Assert.AreEqual(250, mid.RundownAngle); + Assert.AreEqual(2, mid.CurrentMonitoringValue); + Assert.AreEqual(2.15m, mid.SelftapTorque); + Assert.AreEqual(10.25m, mid.PrevailTorque); + Assert.AreEqual(12, mid.JobSequenceNumber); + Assert.AreEqual(65535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123 ", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2019,1,5,9,0,5), mid.Timestamp); + Assert.AreEqual(TorqueValuesUnit.Percentage, mid.TorqueValuesUnit); + Assert.AreEqual((ResultType)8, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); + Assert.AreEqual(51m, mid.PrevailTorqueCompensateValue); Assert.IsNotNull(mid.TighteningErrorStatus2); - Assert.AreNotEqual(0, mid.StationId); - Assert.IsNotNull(mid.StationName); - Assert.AreNotEqual(0, mid.StartFinalAngle); - Assert.IsNotNull(mid.PostViewTorqueActivated); - Assert.AreNotEqual(0, mid.PostViewTorqueHigh); - Assert.AreNotEqual(0, mid.PostViewTorqueLow); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpere); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpereMin); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpereMax); - Assert.AreNotEqual(0, mid.AngleNumeratorScaleFactor); - Assert.AreNotEqual(0, mid.AngleDenominatorScaleFactor); - Assert.IsNotNull(mid.OverallAngleStatus); - Assert.AreNotEqual(0, mid.OverallAngleMin); - Assert.AreNotEqual(0, mid.OverallAngleMax); - Assert.AreNotEqual(0, mid.OverallAngle); - Assert.AreNotEqual(0, mid.PeakTorque); - Assert.AreNotEqual(0, mid.ResidualBreakawayTorque); - Assert.AreNotEqual(0, mid.StartRundownAngle); - Assert.AreNotEqual(0, mid.RundownAngleComplete); - + Assert.AreEqual(4294967295, mid.StationId); + Assert.AreEqual("Station Name ", mid.StationName); + Assert.AreEqual(10m, mid.StartFinalAngle); + Assert.AreEqual(PostViewTorque.OnlyPVTHOn, mid.PostViewTorqueActivated); + Assert.AreEqual(6521.43m, mid.PostViewTorqueHigh); + Assert.AreEqual(52.32m, mid.PostViewTorqueLow); + Assert.AreEqual(1m, mid.CurrentMonitoringAmpere); + Assert.AreEqual(0.5m, mid.CurrentMonitoringAmpereMin); + Assert.AreEqual(1.5m, mid.CurrentMonitoringAmpereMax); + Assert.AreEqual(1, mid.AngleNumeratorScaleFactor); + Assert.AreEqual(1, mid.AngleDenominatorScaleFactor); + Assert.AreEqual(TighteningValueStatus.Ok, mid.OverallAngleStatus); + Assert.AreEqual(-20, mid.OverallAngleMin); + Assert.AreEqual(100, mid.OverallAngleMax); + Assert.AreEqual(120, mid.OverallAngle); + Assert.AreEqual(32.91m, mid.PeakTorque); + Assert.AreEqual(10.05m, mid.ResidualBreakawayTorque); + Assert.AreEqual(1.1m, mid.StartRundownAngle); + Assert.AreEqual(90m, mid.RundownAngleComplete); AssertEqualPackages(bytes, mid); } @@ -1318,63 +1336,63 @@ public void Mid0065Revision11() var mid = _midInterpreter.Parse(pack); Assert.AreEqual(typeof(Mid0065), mid.GetType()); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(123456789L, mid.TighteningId); + Assert.AreEqual("AIRBAG ", mid.VinNumber); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleForward, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueMonitoringStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.AreEqual(false, mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); - Assert.IsNotNull(mid.PrevailTorqueCompensateValue); + Assert.AreEqual(10m, mid.Torque); + Assert.AreEqual(360, mid.Angle); + Assert.AreEqual(250, mid.RundownAngle); + Assert.AreEqual(2, mid.CurrentMonitoringValue); + Assert.AreEqual(2.15m, mid.SelftapTorque); + Assert.AreEqual(10.25m, mid.PrevailTorque); + Assert.AreEqual(12, mid.JobSequenceNumber); + Assert.AreEqual(65535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123 ", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2019,1,5,9,0,5), mid.Timestamp); + Assert.AreEqual(TorqueValuesUnit.Percentage, mid.TorqueValuesUnit); + Assert.AreEqual((ResultType)8, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); + Assert.AreEqual(51m, mid.PrevailTorqueCompensateValue); Assert.IsNotNull(mid.TighteningErrorStatus2); - Assert.AreNotEqual(0, mid.StationId); - Assert.IsNotNull(mid.StationName); - Assert.AreNotEqual(0, mid.StartFinalAngle); - Assert.IsNotNull(mid.PostViewTorqueActivated); - Assert.AreNotEqual(0, mid.PostViewTorqueHigh); - Assert.AreNotEqual(0, mid.PostViewTorqueLow); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpere); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpereMin); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpereMax); - Assert.AreNotEqual(0, mid.AngleNumeratorScaleFactor); - Assert.AreNotEqual(0, mid.AngleDenominatorScaleFactor); - Assert.IsNotNull(mid.OverallAngleStatus); - Assert.AreNotEqual(0, mid.OverallAngleMin); - Assert.AreNotEqual(0, mid.OverallAngleMax); - Assert.AreNotEqual(0, mid.OverallAngle); - Assert.AreNotEqual(0, mid.PeakTorque); - Assert.AreNotEqual(0, mid.ResidualBreakawayTorque); - Assert.AreNotEqual(0, mid.StartRundownAngle); - Assert.AreNotEqual(0, mid.RundownAngleComplete); - Assert.AreNotEqual(0, mid.ClickTorque); - Assert.AreNotEqual(0, mid.ClickAngle); - + Assert.AreEqual(4294967295, mid.StationId); + Assert.AreEqual("Station Name ", mid.StationName); + Assert.AreEqual(10m, mid.StartFinalAngle); + Assert.AreEqual(PostViewTorque.OnlyPVTHOn, mid.PostViewTorqueActivated); + Assert.AreEqual(6521.43m, mid.PostViewTorqueHigh); + Assert.AreEqual(52.32m, mid.PostViewTorqueLow); + Assert.AreEqual(1m, mid.CurrentMonitoringAmpere); + Assert.AreEqual(0.5m, mid.CurrentMonitoringAmpereMin); + Assert.AreEqual(1.5m, mid.CurrentMonitoringAmpereMax); + Assert.AreEqual(1, mid.AngleNumeratorScaleFactor); + Assert.AreEqual(1, mid.AngleDenominatorScaleFactor); + Assert.AreEqual(TighteningValueStatus.Ok, mid.OverallAngleStatus); + Assert.AreEqual(-20, mid.OverallAngleMin); + Assert.AreEqual(100, mid.OverallAngleMax); + Assert.AreEqual(120, mid.OverallAngle); + Assert.AreEqual(32.91m, mid.PeakTorque); + Assert.AreEqual(10.05m, mid.ResidualBreakawayTorque); + Assert.AreEqual(1.1m, mid.StartRundownAngle); + Assert.AreEqual(90m, mid.RundownAngleComplete); + Assert.AreEqual(1.2m, mid.ClickTorque); + Assert.AreEqual(1, mid.ClickAngle); AssertEqualPackages(pack, mid); } @@ -1438,63 +1456,63 @@ public void Mid0065ByteRevision11() var mid = _midInterpreter.Parse(bytes.ToArray()); Assert.AreEqual(typeof(Mid0065), mid.GetType()); - Assert.IsNotNull(mid.TighteningId); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.JobId); - Assert.IsNotNull(mid.ParameterSetId); - Assert.IsNotNull(mid.Strategy); + Assert.AreEqual(123456789L, mid.TighteningId); + Assert.AreEqual("AIRBAG ", mid.VinNumber); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual(2, mid.ParameterSetId); + Assert.AreEqual(Strategy.RotateSpindleForward, mid.Strategy); Assert.IsNotNull(mid.StrategyOptions); - Assert.IsNotNull(mid.BatchSize); - Assert.IsNotNull(mid.BatchCounter); - Assert.IsNotNull(mid.TighteningStatus); - Assert.IsNotNull(mid.BatchStatus); - Assert.IsNotNull(mid.TorqueStatus); - Assert.IsNotNull(mid.AngleStatus); - Assert.IsNotNull(mid.RundownAngleStatus); - Assert.IsNotNull(mid.CurrentMonitoringStatus); - Assert.IsNotNull(mid.SelftapStatus); - Assert.IsNotNull(mid.PrevailTorqueMonitoringStatus); - Assert.IsNotNull(mid.PrevailTorqueCompensateStatus); + Assert.AreEqual(0, mid.BatchSize); + Assert.AreEqual(0, mid.BatchCounter); + Assert.AreEqual(false, mid.TighteningStatus); + Assert.AreEqual(BatchStatus.Nok, mid.BatchStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.TorqueStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.AngleStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.RundownAngleStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.CurrentMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Low, mid.SelftapStatus); + Assert.AreEqual(TighteningValueStatus.High, mid.PrevailTorqueMonitoringStatus); + Assert.AreEqual(TighteningValueStatus.Ok, mid.PrevailTorqueCompensateStatus); Assert.IsNotNull(mid.TighteningErrorStatus); - Assert.IsNotNull(mid.Torque); - Assert.IsNotNull(mid.Angle); - Assert.IsNotNull(mid.RundownAngle); - Assert.IsNotNull(mid.CurrentMonitoringValue); - Assert.IsNotNull(mid.SelftapTorque); - Assert.IsNotNull(mid.JobSequenceNumber); - Assert.IsNotNull(mid.SyncTighteningId); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.Timestamp); - Assert.IsNotNull(mid.TorqueValuesUnit); - Assert.IsNotNull(mid.ResultType); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); - Assert.IsNotNull(mid.CustomerTighteningErrorCode); - Assert.IsNotNull(mid.PrevailTorqueCompensateValue); + Assert.AreEqual(10m, mid.Torque); + Assert.AreEqual(360, mid.Angle); + Assert.AreEqual(250, mid.RundownAngle); + Assert.AreEqual(2, mid.CurrentMonitoringValue); + Assert.AreEqual(2.15m, mid.SelftapTorque); + Assert.AreEqual(10.25m, mid.PrevailTorque); + Assert.AreEqual(12, mid.JobSequenceNumber); + Assert.AreEqual(65535, mid.SyncTighteningId); + Assert.AreEqual("ABCDEFG-123 ", mid.ToolSerialNumber); + Assert.AreEqual(new DateTime(2019,1,5,9,0,5), mid.Timestamp); + Assert.AreEqual(TorqueValuesUnit.Percentage, mid.TorqueValuesUnit); + Assert.AreEqual((ResultType)8, mid.ResultType); + Assert.AreEqual("Identifier result part 2 ", mid.IdentifierResultPart2); + Assert.AreEqual("Identifier result part 3 ", mid.IdentifierResultPart3); + Assert.AreEqual("Identifier result part 4 ", mid.IdentifierResultPart4); + Assert.AreEqual("E124", mid.CustomerTighteningErrorCode); + Assert.AreEqual(51m, mid.PrevailTorqueCompensateValue); Assert.IsNotNull(mid.TighteningErrorStatus2); - Assert.AreNotEqual(0, mid.StationId); - Assert.IsNotNull(mid.StationName); - Assert.AreNotEqual(0, mid.StartFinalAngle); - Assert.IsNotNull(mid.PostViewTorqueActivated); - Assert.AreNotEqual(0, mid.PostViewTorqueHigh); - Assert.AreNotEqual(0, mid.PostViewTorqueLow); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpere); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpereMin); - Assert.AreNotEqual(0, mid.CurrentMonitoringAmpereMax); - Assert.AreNotEqual(0, mid.AngleNumeratorScaleFactor); - Assert.AreNotEqual(0, mid.AngleDenominatorScaleFactor); - Assert.IsNotNull(mid.OverallAngleStatus); - Assert.AreNotEqual(0, mid.OverallAngleMin); - Assert.AreNotEqual(0, mid.OverallAngleMax); - Assert.AreNotEqual(0, mid.OverallAngle); - Assert.AreNotEqual(0, mid.PeakTorque); - Assert.AreNotEqual(0, mid.ResidualBreakawayTorque); - Assert.AreNotEqual(0, mid.StartRundownAngle); - Assert.AreNotEqual(0, mid.RundownAngleComplete); - Assert.AreNotEqual(0, mid.ClickTorque); - Assert.AreNotEqual(0, mid.ClickAngle); - + Assert.AreEqual(4294967295, mid.StationId); + Assert.AreEqual("Station Name ", mid.StationName); + Assert.AreEqual(10m, mid.StartFinalAngle); + Assert.AreEqual(PostViewTorque.OnlyPVTHOn, mid.PostViewTorqueActivated); + Assert.AreEqual(6521.43m, mid.PostViewTorqueHigh); + Assert.AreEqual(52.32m, mid.PostViewTorqueLow); + Assert.AreEqual(1m, mid.CurrentMonitoringAmpere); + Assert.AreEqual(0.5m, mid.CurrentMonitoringAmpereMin); + Assert.AreEqual(1.5m, mid.CurrentMonitoringAmpereMax); + Assert.AreEqual(1, mid.AngleNumeratorScaleFactor); + Assert.AreEqual(1, mid.AngleDenominatorScaleFactor); + Assert.AreEqual(TighteningValueStatus.Ok, mid.OverallAngleStatus); + Assert.AreEqual(-20, mid.OverallAngleMin); + Assert.AreEqual(100, mid.OverallAngleMax); + Assert.AreEqual(120, mid.OverallAngle); + Assert.AreEqual(32.91m, mid.PeakTorque); + Assert.AreEqual(10.05m, mid.ResidualBreakawayTorque); + Assert.AreEqual(1.1m, mid.StartRundownAngle); + Assert.AreEqual(90m, mid.RundownAngleComplete); + Assert.AreEqual(1.2m, mid.ClickTorque); + Assert.AreEqual(1, mid.ClickAngle); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/Tightening/TestMid0066.cs b/src/MIDTesters.Core/Tightening/TestMid0066.cs index 028c59dd..02eebb88 100644 --- a/src/MIDTesters.Core/Tightening/TestMid0066.cs +++ b/src/MIDTesters.Core/Tightening/TestMid0066.cs @@ -15,7 +15,6 @@ public void Mid0066Revision1() var mid = _midInterpreter.Parse(package); Assert.AreEqual(typeof(Mid0066), mid.GetType()); - Assert.IsNotNull(mid.NumberOfOfflineResults); Assert.AreEqual(14, mid.NumberOfOfflineResults); AssertEqualPackages(package, mid); } @@ -29,7 +28,6 @@ public void Mid0066ByteRevision1() var mid = _midInterpreter.Parse(bytes); Assert.AreEqual(typeof(Mid0066), mid.GetType()); - Assert.IsNotNull(mid.NumberOfOfflineResults); Assert.AreEqual(14, mid.NumberOfOfflineResults); AssertEqualPackages(package, mid); } @@ -42,8 +40,6 @@ public void Mid0066Revision2() var mid = _midInterpreter.Parse(package); Assert.AreEqual(typeof(Mid0066), mid.GetType()); - Assert.IsNotNull(mid.NumberOfOfflineResults); - Assert.IsNotNull(mid.NumberOfOfflineCurves); Assert.AreEqual(14, mid.NumberOfOfflineResults); Assert.AreEqual(11, mid.NumberOfOfflineCurves); AssertEqualPackages(package, mid); @@ -58,8 +54,6 @@ public void Mid0066ByteRevision2() var mid = _midInterpreter.Parse(bytes); Assert.AreEqual(typeof(Mid0066), mid.GetType()); - Assert.IsNotNull(mid.NumberOfOfflineResults); - Assert.IsNotNull(mid.NumberOfOfflineCurves); Assert.AreEqual(14, mid.NumberOfOfflineResults); Assert.AreEqual(11, mid.NumberOfOfflineCurves); AssertEqualPackages(package, mid); diff --git a/src/MIDTesters.Core/Tightening/TestMid0902.cs b/src/MIDTesters.Core/Tightening/TestMid0902.cs index 2817a036..cc83ecb6 100644 --- a/src/MIDTesters.Core/Tightening/TestMid0902.cs +++ b/src/MIDTesters.Core/Tightening/TestMid0902.cs @@ -1,5 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter.Tightening; +using System; namespace MIDTesters.Core.Tightening { @@ -15,14 +16,14 @@ public void Mid0902Revision1() var mid = _midInterpreter.Parse(package); Assert.AreEqual(typeof(Mid0902), mid.GetType()); - Assert.AreNotEqual(0, mid.Capacity); - Assert.AreNotEqual(0, mid.OldestSequenceNumber); - Assert.IsNotNull(mid.OldestTime); - Assert.AreNotEqual(0, mid.NewestSequenceNumber); - Assert.IsNotNull(mid.NewestTime); - Assert.AreNotEqual(0, mid.NumberOfPIDs); + Assert.AreEqual(2134L, mid.Capacity); + Assert.AreEqual(1L, mid.OldestSequenceNumber); + Assert.AreEqual(new DateTime(2022, 12, 21, 13, 2, 33), mid.OldestTime); + Assert.AreEqual(5819L, mid.NewestSequenceNumber); + Assert.AreEqual(new DateTime(2023, 12, 10, 12, 28, 54), mid.NewestTime); + Assert.AreEqual(2, mid.NumberOfPIDs); Assert.IsNotNull(mid.VariableDataFields); - Assert.AreNotEqual(0, mid.VariableDataFields.Count); + Assert.AreEqual(2, mid.VariableDataFields.Count); AssertEqualPackages(package, mid); } @@ -35,14 +36,14 @@ public void Mid0902ByteRevision1() var mid = _midInterpreter.Parse(bytes); Assert.AreEqual(typeof(Mid0902), mid.GetType()); - Assert.AreNotEqual(0, mid.Capacity); - Assert.AreNotEqual(0, mid.OldestSequenceNumber); - Assert.IsNotNull(mid.OldestTime); - Assert.AreNotEqual(0, mid.NewestSequenceNumber); - Assert.IsNotNull(mid.NewestTime); - Assert.AreNotEqual(0, mid.NumberOfPIDs); + Assert.AreEqual(2134L, mid.Capacity); + Assert.AreEqual(1L, mid.OldestSequenceNumber); + Assert.AreEqual(new DateTime(2022, 12, 21, 13, 2, 33), mid.OldestTime); + Assert.AreEqual(5819L, mid.NewestSequenceNumber); + Assert.AreEqual(new DateTime(2023, 12, 10, 12, 28, 54), mid.NewestTime); + Assert.AreEqual(2, mid.NumberOfPIDs); Assert.IsNotNull(mid.VariableDataFields); - Assert.AreNotEqual(0, mid.VariableDataFields.Count); + Assert.AreEqual(2, mid.VariableDataFields.Count); AssertEqualPackages(package, mid); } } From 8805a9ccdf5dbe3ba8deed0f24eb9dff6fc5ee6a Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 29 Mar 2026 23:38:06 -0300 Subject: [PATCH 18/78] Updated time tests --- src/MIDTesters.Core/Time/TestMid0081.cs | 5 +++-- src/MIDTesters.Core/Time/TestMid0082.cs | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/MIDTesters.Core/Time/TestMid0081.cs b/src/MIDTesters.Core/Time/TestMid0081.cs index afab20a2..157358f1 100644 --- a/src/MIDTesters.Core/Time/TestMid0081.cs +++ b/src/MIDTesters.Core/Time/TestMid0081.cs @@ -1,5 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter.Time; +using System; namespace MIDTesters.Time { @@ -14,7 +15,7 @@ public void Mid0081Revision1() string pack = @"00390081 2017-12-01:20:12:45"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.Time); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.Time); AssertEqualPackages(pack, mid, true); } @@ -26,7 +27,7 @@ public void Mid0081ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.Time); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.Time); AssertEqualPackages(bytes, mid, true); } } diff --git a/src/MIDTesters.Core/Time/TestMid0082.cs b/src/MIDTesters.Core/Time/TestMid0082.cs index 52f34b36..074b65c6 100644 --- a/src/MIDTesters.Core/Time/TestMid0082.cs +++ b/src/MIDTesters.Core/Time/TestMid0082.cs @@ -1,5 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter.Time; +using System; namespace MIDTesters.Time { @@ -14,7 +15,7 @@ public void Mid0082Revision1() string pack = @"00390082 2017-12-01:20:12:45"; var mid = _midInterpreter.Parse(pack); - Assert.IsNotNull(mid.Time); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.Time); AssertEqualPackages(pack, mid, true); } @@ -26,7 +27,7 @@ public void Mid0082ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.Time); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.Time); AssertEqualPackages(bytes, mid, true); } } From dd4134853571b0d626184fed69758d71262dc3e9 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 29 Mar 2026 23:38:14 -0300 Subject: [PATCH 19/78] Updated Tool tests --- src/MIDTesters.Core/Tool/TestMid0040.cs | 4 +- src/MIDTesters.Core/Tool/TestMid0041.cs | 460 ++++++++++++------------ src/MIDTesters.Core/Tool/TestMid0042.cs | 9 +- src/MIDTesters.Core/Tool/TestMid0043.cs | 4 +- src/MIDTesters.Core/Tool/TestMid0045.cs | 21 +- src/MIDTesters.Core/Tool/TestMid0046.cs | 5 +- src/MIDTesters.Core/Tool/TestMid0047.cs | 5 +- src/MIDTesters.Core/Tool/TestMid0048.cs | 12 +- src/MIDTesters.Core/Tool/TestMid0701.cs | 8 +- src/MIDTesters.Core/Tool/TestMid0702.cs | 4 +- src/MIDTesters.Core/Tool/TestMid0703.cs | 4 +- src/MIDTesters.Core/Tool/TestMid0704.cs | 10 +- 12 files changed, 270 insertions(+), 276 deletions(-) diff --git a/src/MIDTesters.Core/Tool/TestMid0040.cs b/src/MIDTesters.Core/Tool/TestMid0040.cs index 7cf42ef5..647d76e8 100644 --- a/src/MIDTesters.Core/Tool/TestMid0040.cs +++ b/src/MIDTesters.Core/Tool/TestMid0040.cs @@ -37,7 +37,7 @@ public void Mid0040Revisions6And7() string package = "00260040007 010001"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.ToolNumber); + Assert.AreEqual(1, mid.ToolNumber); AssertEqualPackages(package, mid); } @@ -49,7 +49,7 @@ public void Mid0040ByteRevisions6And7() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ToolNumber); + Assert.AreEqual(1, mid.ToolNumber); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/Tool/TestMid0041.cs b/src/MIDTesters.Core/Tool/TestMid0041.cs index ce786c87..66e4722a 100644 --- a/src/MIDTesters.Core/Tool/TestMid0041.cs +++ b/src/MIDTesters.Core/Tool/TestMid0041.cs @@ -1,4 +1,6 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.Tool; namespace MIDTesters.Tool @@ -14,10 +16,10 @@ public void Mid0041Revision1() string package = "00810041001 01ABCDEFG-123456024294967295032017-12-01:20:12:4504GFEDCBA-10"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.ToolNumberOfTightenings); - Assert.IsNotNull(mid.LastCalibrationDate); - Assert.IsNotNull(mid.ControllerSerialNumber); + Assert.AreEqual("ABCDEFG-123456", mid.ToolSerialNumber.TrimEnd()); + Assert.AreEqual(4294967295L, mid.ToolNumberOfTightenings); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.LastCalibrationDate); + Assert.AreEqual("GFEDCBA-10", mid.ControllerSerialNumber.TrimEnd()); AssertEqualPackages(package, mid); } @@ -29,10 +31,10 @@ public void Mid0041ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.ToolNumberOfTightenings); - Assert.IsNotNull(mid.LastCalibrationDate); - Assert.IsNotNull(mid.ControllerSerialNumber); + Assert.AreEqual("ABCDEFG-123456", mid.ToolSerialNumber.TrimEnd()); + Assert.AreEqual(4294967295L, mid.ToolNumberOfTightenings); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.LastCalibrationDate); + Assert.AreEqual("GFEDCBA-10", mid.ControllerSerialNumber.TrimEnd()); AssertEqualPackages(bytes, mid); } @@ -43,20 +45,19 @@ public void Mid0041Revision2() string package = "01560041002 01ABCDEFG-123456024294967295032017-12-01:20:12:4504GFEDCBA-1005002000062018-06-04:10:12:45074284967295081009551011111Version 1.0.0 "; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.ToolNumberOfTightenings); - Assert.IsNotNull(mid.LastCalibrationDate); - Assert.IsNotNull(mid.ControllerSerialNumber); - Assert.IsNotNull(mid.CalibrationValue); - Assert.IsNotNull(mid.LastServiceDate); - Assert.IsNotNull(mid.TighteningsSinceService); - Assert.IsNotNull(mid.ToolType); - Assert.IsNotNull(mid.MotorSize); - Assert.IsNotNull(mid.OpenEndData); - Assert.IsNotNull(mid.OpenEndData.UseOpenEnd); - Assert.IsNotNull(mid.OpenEndData.TighteningDirection); - Assert.IsNotNull(mid.OpenEndData.MotorRotation); - Assert.IsNotNull(mid.ControllerSoftwareVersion); + Assert.AreEqual("ABCDEFG-123456", mid.ToolSerialNumber.TrimEnd()); + Assert.AreEqual(4294967295L, mid.ToolNumberOfTightenings); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.LastCalibrationDate); + Assert.AreEqual("GFEDCBA-10", mid.ControllerSerialNumber.TrimEnd()); + Assert.AreEqual(20.00m, mid.CalibrationValue); + Assert.AreEqual(new DateTime(2018, 6, 4, 10, 12, 45), mid.LastServiceDate); + Assert.AreEqual(4284967295L, mid.TighteningsSinceService); + Assert.AreEqual(ToolType.STB_Tool, mid.ToolType); + Assert.AreEqual(55, mid.MotorSize); + Assert.IsTrue(mid.OpenEndData.UseOpenEnd); + Assert.AreEqual(TighteningDirection.Counterclockwise, mid.OpenEndData.TighteningDirection); + Assert.AreEqual(MotorRotation.Inverted, mid.OpenEndData.MotorRotation); + Assert.AreEqual("Version 1.0.0", mid.ControllerSoftwareVersion.TrimEnd()); AssertEqualPackages(package, mid); } @@ -68,20 +69,19 @@ public void Mid0041ByteRevision2() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.ToolNumberOfTightenings); - Assert.IsNotNull(mid.LastCalibrationDate); - Assert.IsNotNull(mid.ControllerSerialNumber); - Assert.IsNotNull(mid.CalibrationValue); - Assert.IsNotNull(mid.LastServiceDate); - Assert.IsNotNull(mid.TighteningsSinceService); - Assert.IsNotNull(mid.ToolType); - Assert.IsNotNull(mid.MotorSize); - Assert.IsNotNull(mid.OpenEndData); - Assert.IsNotNull(mid.OpenEndData.UseOpenEnd); - Assert.IsNotNull(mid.OpenEndData.TighteningDirection); - Assert.IsNotNull(mid.OpenEndData.MotorRotation); - Assert.IsNotNull(mid.ControllerSoftwareVersion); + Assert.AreEqual("ABCDEFG-123456", mid.ToolSerialNumber.TrimEnd()); + Assert.AreEqual(4294967295L, mid.ToolNumberOfTightenings); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.LastCalibrationDate); + Assert.AreEqual("GFEDCBA-10", mid.ControllerSerialNumber.TrimEnd()); + Assert.AreEqual(20.00m, mid.CalibrationValue); + Assert.AreEqual(new DateTime(2018, 6, 4, 10, 12, 45), mid.LastServiceDate); + Assert.AreEqual(4284967295L, mid.TighteningsSinceService); + Assert.AreEqual(ToolType.STB_Tool, mid.ToolType); + Assert.AreEqual(55, mid.MotorSize); + Assert.IsTrue(mid.OpenEndData.UseOpenEnd); + Assert.AreEqual(TighteningDirection.Counterclockwise, mid.OpenEndData.TighteningDirection); + Assert.AreEqual(MotorRotation.Inverted, mid.OpenEndData.MotorRotation); + Assert.AreEqual("Version 1.0.0", mid.ControllerSoftwareVersion.TrimEnd()); AssertEqualPackages(bytes, mid); } @@ -92,23 +92,22 @@ public void Mid0041Revision3() string package = "01800041003 01ABCDEFG-123456024294967295032017-12-01:20:12:4504GFEDCBA-1005002000062018-06-04:10:12:45074284967295081009551011111Version 1.0.0 120006001300123014004000"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.ToolNumberOfTightenings); - Assert.IsNotNull(mid.LastCalibrationDate); - Assert.IsNotNull(mid.ControllerSerialNumber); - Assert.IsNotNull(mid.CalibrationValue); - Assert.IsNotNull(mid.LastServiceDate); - Assert.IsNotNull(mid.TighteningsSinceService); - Assert.IsNotNull(mid.ToolType); - Assert.IsNotNull(mid.MotorSize); - Assert.IsNotNull(mid.OpenEndData); - Assert.IsNotNull(mid.OpenEndData.UseOpenEnd); - Assert.IsNotNull(mid.OpenEndData.TighteningDirection); - Assert.IsNotNull(mid.OpenEndData.MotorRotation); - Assert.IsNotNull(mid.ControllerSoftwareVersion); - Assert.IsNotNull(mid.ToolMaxTorque); - Assert.IsNotNull(mid.GearRatio); - Assert.IsNotNull(mid.ToolFullSpeed); + Assert.AreEqual("ABCDEFG-123456", mid.ToolSerialNumber.TrimEnd()); + Assert.AreEqual(4294967295L, mid.ToolNumberOfTightenings); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.LastCalibrationDate); + Assert.AreEqual("GFEDCBA-10", mid.ControllerSerialNumber.TrimEnd()); + Assert.AreEqual(20.00m, mid.CalibrationValue); + Assert.AreEqual(new DateTime(2018, 6, 4, 10, 12, 45), mid.LastServiceDate); + Assert.AreEqual(4284967295L, mid.TighteningsSinceService); + Assert.AreEqual(ToolType.STB_Tool, mid.ToolType); + Assert.AreEqual(55, mid.MotorSize); + Assert.IsTrue(mid.OpenEndData.UseOpenEnd); + Assert.AreEqual(TighteningDirection.Counterclockwise, mid.OpenEndData.TighteningDirection); + Assert.AreEqual(MotorRotation.Inverted, mid.OpenEndData.MotorRotation); + Assert.AreEqual("Version 1.0.0", mid.ControllerSoftwareVersion.TrimEnd()); + Assert.AreEqual(6.00m, mid.ToolMaxTorque); + Assert.AreEqual(12.30m, mid.GearRatio); + Assert.AreEqual(40.00m, mid.ToolFullSpeed); AssertEqualPackages(package, mid); } @@ -120,23 +119,22 @@ public void Mid0041ByteRevision3() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.ToolNumberOfTightenings); - Assert.IsNotNull(mid.LastCalibrationDate); - Assert.IsNotNull(mid.ControllerSerialNumber); - Assert.IsNotNull(mid.CalibrationValue); - Assert.IsNotNull(mid.LastServiceDate); - Assert.IsNotNull(mid.TighteningsSinceService); - Assert.IsNotNull(mid.ToolType); - Assert.IsNotNull(mid.MotorSize); - Assert.IsNotNull(mid.OpenEndData); - Assert.IsNotNull(mid.OpenEndData.UseOpenEnd); - Assert.IsNotNull(mid.OpenEndData.TighteningDirection); - Assert.IsNotNull(mid.OpenEndData.MotorRotation); - Assert.IsNotNull(mid.ControllerSoftwareVersion); - Assert.IsNotNull(mid.ToolMaxTorque); - Assert.IsNotNull(mid.GearRatio); - Assert.IsNotNull(mid.ToolFullSpeed); + Assert.AreEqual("ABCDEFG-123456", mid.ToolSerialNumber.TrimEnd()); + Assert.AreEqual(4294967295L, mid.ToolNumberOfTightenings); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.LastCalibrationDate); + Assert.AreEqual("GFEDCBA-10", mid.ControllerSerialNumber.TrimEnd()); + Assert.AreEqual(20.00m, mid.CalibrationValue); + Assert.AreEqual(new DateTime(2018, 6, 4, 10, 12, 45), mid.LastServiceDate); + Assert.AreEqual(4284967295L, mid.TighteningsSinceService); + Assert.AreEqual(ToolType.STB_Tool, mid.ToolType); + Assert.AreEqual(55, mid.MotorSize); + Assert.IsTrue(mid.OpenEndData.UseOpenEnd); + Assert.AreEqual(TighteningDirection.Counterclockwise, mid.OpenEndData.TighteningDirection); + Assert.AreEqual(MotorRotation.Inverted, mid.OpenEndData.MotorRotation); + Assert.AreEqual("Version 1.0.0", mid.ControllerSoftwareVersion.TrimEnd()); + Assert.AreEqual(6.00m, mid.ToolMaxTorque); + Assert.AreEqual(12.30m, mid.GearRatio); + Assert.AreEqual(40.00m, mid.ToolFullSpeed); AssertEqualPackages(bytes, mid); } @@ -147,24 +145,23 @@ public void Mid0041Revision4() string package = "01840041004 01ABCDEFG-123456024294967295032017-12-01:20:12:4504GFEDCBA-1005002000062018-06-04:10:12:45074284967295081009551011111Version 1.0.0 1200060013001230140040001503"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.ToolNumberOfTightenings); - Assert.IsNotNull(mid.LastCalibrationDate); - Assert.IsNotNull(mid.ControllerSerialNumber); - Assert.IsNotNull(mid.CalibrationValue); - Assert.IsNotNull(mid.LastServiceDate); - Assert.IsNotNull(mid.TighteningsSinceService); - Assert.IsNotNull(mid.ToolType); - Assert.IsNotNull(mid.MotorSize); - Assert.IsNotNull(mid.OpenEndData); - Assert.IsNotNull(mid.OpenEndData.UseOpenEnd); - Assert.IsNotNull(mid.OpenEndData.TighteningDirection); - Assert.IsNotNull(mid.OpenEndData.MotorRotation); - Assert.IsNotNull(mid.ControllerSoftwareVersion); - Assert.IsNotNull(mid.ToolMaxTorque); - Assert.IsNotNull(mid.GearRatio); - Assert.IsNotNull(mid.ToolFullSpeed); - Assert.IsNotNull(mid.PrimaryTool); + Assert.AreEqual("ABCDEFG-123456", mid.ToolSerialNumber.TrimEnd()); + Assert.AreEqual(4294967295L, mid.ToolNumberOfTightenings); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.LastCalibrationDate); + Assert.AreEqual("GFEDCBA-10", mid.ControllerSerialNumber.TrimEnd()); + Assert.AreEqual(20.00m, mid.CalibrationValue); + Assert.AreEqual(new DateTime(2018, 6, 4, 10, 12, 45), mid.LastServiceDate); + Assert.AreEqual(4284967295L, mid.TighteningsSinceService); + Assert.AreEqual(ToolType.STB_Tool, mid.ToolType); + Assert.AreEqual(55, mid.MotorSize); + Assert.IsTrue(mid.OpenEndData.UseOpenEnd); + Assert.AreEqual(TighteningDirection.Counterclockwise, mid.OpenEndData.TighteningDirection); + Assert.AreEqual(MotorRotation.Inverted, mid.OpenEndData.MotorRotation); + Assert.AreEqual("Version 1.0.0", mid.ControllerSoftwareVersion.TrimEnd()); + Assert.AreEqual(6.00m, mid.ToolMaxTorque); + Assert.AreEqual(12.30m, mid.GearRatio); + Assert.AreEqual(40.00m, mid.ToolFullSpeed); + Assert.AreEqual(PrimaryTool.IRC_W, mid.PrimaryTool); AssertEqualPackages(package, mid); } @@ -176,24 +173,23 @@ public void Mid0041ByteRevision4() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.ToolNumberOfTightenings); - Assert.IsNotNull(mid.LastCalibrationDate); - Assert.IsNotNull(mid.ControllerSerialNumber); - Assert.IsNotNull(mid.CalibrationValue); - Assert.IsNotNull(mid.LastServiceDate); - Assert.IsNotNull(mid.TighteningsSinceService); - Assert.IsNotNull(mid.ToolType); - Assert.IsNotNull(mid.MotorSize); - Assert.IsNotNull(mid.OpenEndData); - Assert.IsNotNull(mid.OpenEndData.UseOpenEnd); - Assert.IsNotNull(mid.OpenEndData.TighteningDirection); - Assert.IsNotNull(mid.OpenEndData.MotorRotation); - Assert.IsNotNull(mid.ControllerSoftwareVersion); - Assert.IsNotNull(mid.ToolMaxTorque); - Assert.IsNotNull(mid.GearRatio); - Assert.IsNotNull(mid.ToolFullSpeed); - Assert.IsNotNull(mid.PrimaryTool); + Assert.AreEqual("ABCDEFG-123456", mid.ToolSerialNumber.TrimEnd()); + Assert.AreEqual(4294967295L, mid.ToolNumberOfTightenings); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.LastCalibrationDate); + Assert.AreEqual("GFEDCBA-10", mid.ControllerSerialNumber.TrimEnd()); + Assert.AreEqual(20.00m, mid.CalibrationValue); + Assert.AreEqual(new DateTime(2018, 6, 4, 10, 12, 45), mid.LastServiceDate); + Assert.AreEqual(4284967295L, mid.TighteningsSinceService); + Assert.AreEqual(ToolType.STB_Tool, mid.ToolType); + Assert.AreEqual(55, mid.MotorSize); + Assert.IsTrue(mid.OpenEndData.UseOpenEnd); + Assert.AreEqual(TighteningDirection.Counterclockwise, mid.OpenEndData.TighteningDirection); + Assert.AreEqual(MotorRotation.Inverted, mid.OpenEndData.MotorRotation); + Assert.AreEqual("Version 1.0.0", mid.ControllerSoftwareVersion.TrimEnd()); + Assert.AreEqual(6.00m, mid.ToolMaxTorque); + Assert.AreEqual(12.30m, mid.GearRatio); + Assert.AreEqual(40.00m, mid.ToolFullSpeed); + Assert.AreEqual(PrimaryTool.IRC_W, mid.PrimaryTool); AssertEqualPackages(bytes, mid); } @@ -204,25 +200,24 @@ public void Mid0041Revision5() string package = "01980041005 01ABCDEFG-123456024294967295032017-12-01:20:12:4504GFEDCBA-1005002000062018-06-04:10:12:45074284967295081009551011111Version 1.0.0 120006001300123014004000150316Tool Model "; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.ToolNumberOfTightenings); - Assert.IsNotNull(mid.LastCalibrationDate); - Assert.IsNotNull(mid.ControllerSerialNumber); - Assert.IsNotNull(mid.CalibrationValue); - Assert.IsNotNull(mid.LastServiceDate); - Assert.IsNotNull(mid.TighteningsSinceService); - Assert.IsNotNull(mid.ToolType); - Assert.IsNotNull(mid.MotorSize); - Assert.IsNotNull(mid.OpenEndData); - Assert.IsNotNull(mid.OpenEndData.UseOpenEnd); - Assert.IsNotNull(mid.OpenEndData.TighteningDirection); - Assert.IsNotNull(mid.OpenEndData.MotorRotation); - Assert.IsNotNull(mid.ControllerSoftwareVersion); - Assert.IsNotNull(mid.ToolMaxTorque); - Assert.IsNotNull(mid.GearRatio); - Assert.IsNotNull(mid.ToolFullSpeed); - Assert.IsNotNull(mid.PrimaryTool); - Assert.IsNotNull(mid.ToolModel); + Assert.AreEqual("ABCDEFG-123456", mid.ToolSerialNumber.TrimEnd()); + Assert.AreEqual(4294967295L, mid.ToolNumberOfTightenings); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.LastCalibrationDate); + Assert.AreEqual("GFEDCBA-10", mid.ControllerSerialNumber.TrimEnd()); + Assert.AreEqual(20.00m, mid.CalibrationValue); + Assert.AreEqual(new DateTime(2018, 6, 4, 10, 12, 45), mid.LastServiceDate); + Assert.AreEqual(4284967295L, mid.TighteningsSinceService); + Assert.AreEqual(ToolType.STB_Tool, mid.ToolType); + Assert.AreEqual(55, mid.MotorSize); + Assert.IsTrue(mid.OpenEndData.UseOpenEnd); + Assert.AreEqual(TighteningDirection.Counterclockwise, mid.OpenEndData.TighteningDirection); + Assert.AreEqual(MotorRotation.Inverted, mid.OpenEndData.MotorRotation); + Assert.AreEqual("Version 1.0.0", mid.ControllerSoftwareVersion.TrimEnd()); + Assert.AreEqual(6.00m, mid.ToolMaxTorque); + Assert.AreEqual(12.30m, mid.GearRatio); + Assert.AreEqual(40.00m, mid.ToolFullSpeed); + Assert.AreEqual(PrimaryTool.IRC_W, mid.PrimaryTool); + Assert.AreEqual("Tool Model", mid.ToolModel.TrimEnd()); AssertEqualPackages(package, mid); } @@ -234,25 +229,24 @@ public void Mid0041ByteRevision5() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.ToolNumberOfTightenings); - Assert.IsNotNull(mid.LastCalibrationDate); - Assert.IsNotNull(mid.ControllerSerialNumber); - Assert.IsNotNull(mid.CalibrationValue); - Assert.IsNotNull(mid.LastServiceDate); - Assert.IsNotNull(mid.TighteningsSinceService); - Assert.IsNotNull(mid.ToolType); - Assert.IsNotNull(mid.MotorSize); - Assert.IsNotNull(mid.OpenEndData); - Assert.IsNotNull(mid.OpenEndData.UseOpenEnd); - Assert.IsNotNull(mid.OpenEndData.TighteningDirection); - Assert.IsNotNull(mid.OpenEndData.MotorRotation); - Assert.IsNotNull(mid.ControllerSoftwareVersion); - Assert.IsNotNull(mid.ToolMaxTorque); - Assert.IsNotNull(mid.GearRatio); - Assert.IsNotNull(mid.ToolFullSpeed); - Assert.IsNotNull(mid.PrimaryTool); - Assert.IsNotNull(mid.ToolModel); + Assert.AreEqual("ABCDEFG-123456", mid.ToolSerialNumber.TrimEnd()); + Assert.AreEqual(4294967295L, mid.ToolNumberOfTightenings); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.LastCalibrationDate); + Assert.AreEqual("GFEDCBA-10", mid.ControllerSerialNumber.TrimEnd()); + Assert.AreEqual(20.00m, mid.CalibrationValue); + Assert.AreEqual(new DateTime(2018, 6, 4, 10, 12, 45), mid.LastServiceDate); + Assert.AreEqual(4284967295L, mid.TighteningsSinceService); + Assert.AreEqual(ToolType.STB_Tool, mid.ToolType); + Assert.AreEqual(55, mid.MotorSize); + Assert.IsTrue(mid.OpenEndData.UseOpenEnd); + Assert.AreEqual(TighteningDirection.Counterclockwise, mid.OpenEndData.TighteningDirection); + Assert.AreEqual(MotorRotation.Inverted, mid.OpenEndData.MotorRotation); + Assert.AreEqual("Version 1.0.0", mid.ControllerSoftwareVersion.TrimEnd()); + Assert.AreEqual(6.00m, mid.ToolMaxTorque); + Assert.AreEqual(12.30m, mid.GearRatio); + Assert.AreEqual(40.00m, mid.ToolFullSpeed); + Assert.AreEqual(PrimaryTool.IRC_W, mid.PrimaryTool); + Assert.AreEqual("Tool Model", mid.ToolModel.TrimEnd()); AssertEqualPackages(bytes, mid); } @@ -263,27 +257,26 @@ public void Mid0041Revision6() string package = "02360041006 01ABCDEFG-123456024294967295032017-12-01:20:12:4504GFEDCBA-1005002000062018-06-04:10:12:45074284967295081009551011111Version 1.0.0 120006001300123014004000150316Tool Model 17000118Tool Article Number "; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.ToolNumberOfTightenings); - Assert.IsNotNull(mid.LastCalibrationDate); - Assert.IsNotNull(mid.ControllerSerialNumber); - Assert.IsNotNull(mid.CalibrationValue); - Assert.IsNotNull(mid.LastServiceDate); - Assert.IsNotNull(mid.TighteningsSinceService); - Assert.IsNotNull(mid.ToolType); - Assert.IsNotNull(mid.MotorSize); - Assert.IsNotNull(mid.OpenEndData); - Assert.IsNotNull(mid.OpenEndData.UseOpenEnd); - Assert.IsNotNull(mid.OpenEndData.TighteningDirection); - Assert.IsNotNull(mid.OpenEndData.MotorRotation); - Assert.IsNotNull(mid.ControllerSoftwareVersion); - Assert.IsNotNull(mid.ToolMaxTorque); - Assert.IsNotNull(mid.GearRatio); - Assert.IsNotNull(mid.ToolFullSpeed); - Assert.IsNotNull(mid.PrimaryTool); - Assert.IsNotNull(mid.ToolModel); - Assert.IsNotNull(mid.ToolNumber); - Assert.IsNotNull(mid.ToolArticleNumber); + Assert.AreEqual("ABCDEFG-123456", mid.ToolSerialNumber.TrimEnd()); + Assert.AreEqual(4294967295L, mid.ToolNumberOfTightenings); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.LastCalibrationDate); + Assert.AreEqual("GFEDCBA-10", mid.ControllerSerialNumber.TrimEnd()); + Assert.AreEqual(20.00m, mid.CalibrationValue); + Assert.AreEqual(new DateTime(2018, 6, 4, 10, 12, 45), mid.LastServiceDate); + Assert.AreEqual(4284967295L, mid.TighteningsSinceService); + Assert.AreEqual(ToolType.STB_Tool, mid.ToolType); + Assert.AreEqual(55, mid.MotorSize); + Assert.IsTrue(mid.OpenEndData.UseOpenEnd); + Assert.AreEqual(TighteningDirection.Counterclockwise, mid.OpenEndData.TighteningDirection); + Assert.AreEqual(MotorRotation.Inverted, mid.OpenEndData.MotorRotation); + Assert.AreEqual("Version 1.0.0", mid.ControllerSoftwareVersion.TrimEnd()); + Assert.AreEqual(6.00m, mid.ToolMaxTorque); + Assert.AreEqual(12.30m, mid.GearRatio); + Assert.AreEqual(40.00m, mid.ToolFullSpeed); + Assert.AreEqual(PrimaryTool.IRC_W, mid.PrimaryTool); + Assert.AreEqual("Tool Model", mid.ToolModel.TrimEnd()); + Assert.AreEqual(1, mid.ToolNumber); + Assert.AreEqual("Tool Article Number", mid.ToolArticleNumber.TrimEnd()); AssertEqualPackages(package, mid); } @@ -295,27 +288,26 @@ public void Mid0041ByteRevision6() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.ToolNumberOfTightenings); - Assert.IsNotNull(mid.LastCalibrationDate); - Assert.IsNotNull(mid.ControllerSerialNumber); - Assert.IsNotNull(mid.CalibrationValue); - Assert.IsNotNull(mid.LastServiceDate); - Assert.IsNotNull(mid.TighteningsSinceService); - Assert.IsNotNull(mid.ToolType); - Assert.IsNotNull(mid.MotorSize); - Assert.IsNotNull(mid.OpenEndData); - Assert.IsNotNull(mid.OpenEndData.UseOpenEnd); - Assert.IsNotNull(mid.OpenEndData.TighteningDirection); - Assert.IsNotNull(mid.OpenEndData.MotorRotation); - Assert.IsNotNull(mid.ControllerSoftwareVersion); - Assert.IsNotNull(mid.ToolMaxTorque); - Assert.IsNotNull(mid.GearRatio); - Assert.IsNotNull(mid.ToolFullSpeed); - Assert.IsNotNull(mid.PrimaryTool); - Assert.IsNotNull(mid.ToolModel); - Assert.IsNotNull(mid.ToolNumber); - Assert.IsNotNull(mid.ToolArticleNumber); + Assert.AreEqual("ABCDEFG-123456", mid.ToolSerialNumber.TrimEnd()); + Assert.AreEqual(4294967295L, mid.ToolNumberOfTightenings); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.LastCalibrationDate); + Assert.AreEqual("GFEDCBA-10", mid.ControllerSerialNumber.TrimEnd()); + Assert.AreEqual(20.00m, mid.CalibrationValue); + Assert.AreEqual(new DateTime(2018, 6, 4, 10, 12, 45), mid.LastServiceDate); + Assert.AreEqual(4284967295L, mid.TighteningsSinceService); + Assert.AreEqual(ToolType.STB_Tool, mid.ToolType); + Assert.AreEqual(55, mid.MotorSize); + Assert.IsTrue(mid.OpenEndData.UseOpenEnd); + Assert.AreEqual(TighteningDirection.Counterclockwise, mid.OpenEndData.TighteningDirection); + Assert.AreEqual(MotorRotation.Inverted, mid.OpenEndData.MotorRotation); + Assert.AreEqual("Version 1.0.0", mid.ControllerSoftwareVersion.TrimEnd()); + Assert.AreEqual(6.00m, mid.ToolMaxTorque); + Assert.AreEqual(12.30m, mid.GearRatio); + Assert.AreEqual(40.00m, mid.ToolFullSpeed); + Assert.AreEqual(PrimaryTool.IRC_W, mid.PrimaryTool); + Assert.AreEqual("Tool Model", mid.ToolModel.TrimEnd()); + Assert.AreEqual(1, mid.ToolNumber); + Assert.AreEqual("Tool Article Number", mid.ToolArticleNumber.TrimEnd()); AssertEqualPackages(bytes, mid); } @@ -326,30 +318,29 @@ public void Mid0041Revision7() string package = "02600041007 01ABCDEFG-123456024294967295032017-12-01:20:12:4504GFEDCBA-1005002000062018-06-04:10:12:45074284967295081009551011111Version 1.0.0 120006001300123014004000150316Tool Model 17000118Tool Article Number 190010002003200021013000"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.ToolNumberOfTightenings); - Assert.IsNotNull(mid.LastCalibrationDate); - Assert.IsNotNull(mid.ControllerSerialNumber); - Assert.IsNotNull(mid.CalibrationValue); - Assert.IsNotNull(mid.LastServiceDate); - Assert.IsNotNull(mid.TighteningsSinceService); - Assert.IsNotNull(mid.ToolType); - Assert.IsNotNull(mid.MotorSize); - Assert.IsNotNull(mid.OpenEndData); - Assert.IsNotNull(mid.OpenEndData.UseOpenEnd); - Assert.IsNotNull(mid.OpenEndData.TighteningDirection); - Assert.IsNotNull(mid.OpenEndData.MotorRotation); - Assert.IsNotNull(mid.ControllerSoftwareVersion); - Assert.IsNotNull(mid.ToolMaxTorque); - Assert.IsNotNull(mid.GearRatio); - Assert.IsNotNull(mid.ToolFullSpeed); - Assert.IsNotNull(mid.PrimaryTool); - Assert.IsNotNull(mid.ToolModel); - Assert.IsNotNull(mid.ToolNumber); - Assert.IsNotNull(mid.ToolArticleNumber); - Assert.IsNotNull(mid.RundownMinSpeed); - Assert.IsNotNull(mid.DownshiftMaxSpeed); - Assert.IsNotNull(mid.DownshiftMinSpeed); + Assert.AreEqual("ABCDEFG-123456", mid.ToolSerialNumber.TrimEnd()); + Assert.AreEqual(4294967295L, mid.ToolNumberOfTightenings); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.LastCalibrationDate); + Assert.AreEqual("GFEDCBA-10", mid.ControllerSerialNumber.TrimEnd()); + Assert.AreEqual(20.00m, mid.CalibrationValue); + Assert.AreEqual(new DateTime(2018, 6, 4, 10, 12, 45), mid.LastServiceDate); + Assert.AreEqual(4284967295L, mid.TighteningsSinceService); + Assert.AreEqual(ToolType.STB_Tool, mid.ToolType); + Assert.AreEqual(55, mid.MotorSize); + Assert.IsTrue(mid.OpenEndData.UseOpenEnd); + Assert.AreEqual(TighteningDirection.Counterclockwise, mid.OpenEndData.TighteningDirection); + Assert.AreEqual(MotorRotation.Inverted, mid.OpenEndData.MotorRotation); + Assert.AreEqual("Version 1.0.0", mid.ControllerSoftwareVersion.TrimEnd()); + Assert.AreEqual(6.00m, mid.ToolMaxTorque); + Assert.AreEqual(12.30m, mid.GearRatio); + Assert.AreEqual(40.00m, mid.ToolFullSpeed); + Assert.AreEqual(PrimaryTool.IRC_W, mid.PrimaryTool); + Assert.AreEqual("Tool Model", mid.ToolModel.TrimEnd()); + Assert.AreEqual(1, mid.ToolNumber); + Assert.AreEqual("Tool Article Number", mid.ToolArticleNumber.TrimEnd()); + Assert.AreEqual(10.00m, mid.RundownMinSpeed); + Assert.AreEqual(320.00m, mid.DownshiftMaxSpeed); + Assert.AreEqual(130.00m, mid.DownshiftMinSpeed); AssertEqualPackages(package, mid); } @@ -361,30 +352,29 @@ public void Mid0041ByteRevision7() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ToolSerialNumber); - Assert.IsNotNull(mid.ToolNumberOfTightenings); - Assert.IsNotNull(mid.LastCalibrationDate); - Assert.IsNotNull(mid.ControllerSerialNumber); - Assert.IsNotNull(mid.CalibrationValue); - Assert.IsNotNull(mid.LastServiceDate); - Assert.IsNotNull(mid.TighteningsSinceService); - Assert.IsNotNull(mid.ToolType); - Assert.IsNotNull(mid.MotorSize); - Assert.IsNotNull(mid.OpenEndData); - Assert.IsNotNull(mid.OpenEndData.UseOpenEnd); - Assert.IsNotNull(mid.OpenEndData.TighteningDirection); - Assert.IsNotNull(mid.OpenEndData.MotorRotation); - Assert.IsNotNull(mid.ControllerSoftwareVersion); - Assert.IsNotNull(mid.ToolMaxTorque); - Assert.IsNotNull(mid.GearRatio); - Assert.IsNotNull(mid.ToolFullSpeed); - Assert.IsNotNull(mid.PrimaryTool); - Assert.IsNotNull(mid.ToolModel); - Assert.IsNotNull(mid.ToolNumber); - Assert.IsNotNull(mid.ToolArticleNumber); - Assert.IsNotNull(mid.RundownMinSpeed); - Assert.IsNotNull(mid.DownshiftMaxSpeed); - Assert.IsNotNull(mid.DownshiftMinSpeed); + Assert.AreEqual("ABCDEFG-123456", mid.ToolSerialNumber.TrimEnd()); + Assert.AreEqual(4294967295L, mid.ToolNumberOfTightenings); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.LastCalibrationDate); + Assert.AreEqual("GFEDCBA-10", mid.ControllerSerialNumber.TrimEnd()); + Assert.AreEqual(20.00m, mid.CalibrationValue); + Assert.AreEqual(new DateTime(2018, 6, 4, 10, 12, 45), mid.LastServiceDate); + Assert.AreEqual(4284967295L, mid.TighteningsSinceService); + Assert.AreEqual(ToolType.STB_Tool, mid.ToolType); + Assert.AreEqual(55, mid.MotorSize); + Assert.IsTrue(mid.OpenEndData.UseOpenEnd); + Assert.AreEqual(TighteningDirection.Counterclockwise, mid.OpenEndData.TighteningDirection); + Assert.AreEqual(MotorRotation.Inverted, mid.OpenEndData.MotorRotation); + Assert.AreEqual("Version 1.0.0", mid.ControllerSoftwareVersion.TrimEnd()); + Assert.AreEqual(6.00m, mid.ToolMaxTorque); + Assert.AreEqual(12.30m, mid.GearRatio); + Assert.AreEqual(40.00m, mid.ToolFullSpeed); + Assert.AreEqual(PrimaryTool.IRC_W, mid.PrimaryTool); + Assert.AreEqual("Tool Model", mid.ToolModel.TrimEnd()); + Assert.AreEqual(1, mid.ToolNumber); + Assert.AreEqual("Tool Article Number", mid.ToolArticleNumber.TrimEnd()); + Assert.AreEqual(10.00m, mid.RundownMinSpeed); + Assert.AreEqual(320.00m, mid.DownshiftMaxSpeed); + Assert.AreEqual(130.00m, mid.DownshiftMinSpeed); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/Tool/TestMid0042.cs b/src/MIDTesters.Core/Tool/TestMid0042.cs index 23532aa9..127690ea 100644 --- a/src/MIDTesters.Core/Tool/TestMid0042.cs +++ b/src/MIDTesters.Core/Tool/TestMid0042.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.Tool; namespace MIDTesters.Tool @@ -37,8 +38,8 @@ public void Mid0042Revision2() string package = "00300042002 0100420201"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.ToolNumber); - Assert.IsNotNull(mid.DisableType); + Assert.AreEqual(42, mid.ToolNumber); + Assert.AreEqual(DisableType.InhibitNok, mid.DisableType); AssertEqualPackages(package, mid); } @@ -50,8 +51,8 @@ public void Mid0042ByteRevision2() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ToolNumber); - Assert.IsNotNull(mid.DisableType); + Assert.AreEqual(42, mid.ToolNumber); + Assert.AreEqual(DisableType.Disable, mid.DisableType); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/Tool/TestMid0043.cs b/src/MIDTesters.Core/Tool/TestMid0043.cs index 353b2892..99882106 100644 --- a/src/MIDTesters.Core/Tool/TestMid0043.cs +++ b/src/MIDTesters.Core/Tool/TestMid0043.cs @@ -37,7 +37,7 @@ public void Mid0043Revision2() string package = "00260043002 010042"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.ToolNumber); + Assert.AreEqual(42, mid.ToolNumber); AssertEqualPackages(package, mid); } @@ -49,7 +49,7 @@ public void Mid0043ByteRevision2() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ToolNumber); + Assert.AreEqual(32, mid.ToolNumber); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/Tool/TestMid0045.cs b/src/MIDTesters.Core/Tool/TestMid0045.cs index 6f456dd3..e4bec91d 100644 --- a/src/MIDTesters.Core/Tool/TestMid0045.cs +++ b/src/MIDTesters.Core/Tool/TestMid0045.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.Tool; namespace MIDTesters.Tool @@ -14,8 +15,8 @@ public void Mid0045Revision1() string package = "00310045 01402003000"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.CalibrationValueUnit); - Assert.IsNotNull(mid.CalibrationValue); + Assert.AreEqual(CalibrationUnit.Kpm, mid.CalibrationValueUnit); + Assert.AreEqual(30.00m, mid.CalibrationValue); AssertEqualPackages(package, mid, true); } @@ -27,8 +28,8 @@ public void Mid0045ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.CalibrationValueUnit); - Assert.IsNotNull(mid.CalibrationValue); + Assert.AreEqual(CalibrationUnit.Kpm, mid.CalibrationValueUnit); + Assert.AreEqual(30.00m, mid.CalibrationValue); AssertEqualPackages(bytes, mid, true); } @@ -40,9 +41,9 @@ public void Mid0045Revision2() var mid = _midInterpreter.Parse(package); Assert.AreEqual(typeof(Mid0045), mid.GetType()); - Assert.IsNotNull(mid.CalibrationValueUnit); - Assert.IsNotNull(mid.CalibrationValue); - Assert.IsNotNull(mid.ChannelNumber); + Assert.AreEqual(CalibrationUnit.Kpm, mid.CalibrationValueUnit); + Assert.AreEqual(30.00m, mid.CalibrationValue); + Assert.AreEqual(1, mid.ChannelNumber); AssertEqualPackages(package, mid); } @@ -55,9 +56,9 @@ public void Mid0045ByteRevision2() var mid = _midInterpreter.Parse(bytes); Assert.AreEqual(typeof(Mid0045), mid.GetType()); - Assert.IsNotNull(mid.CalibrationValueUnit); - Assert.IsNotNull(mid.CalibrationValue); - Assert.IsNotNull(mid.ChannelNumber); + Assert.AreEqual(CalibrationUnit.Kpm, mid.CalibrationValueUnit); + Assert.AreEqual(30.00m, mid.CalibrationValue); + Assert.AreEqual(2, mid.ChannelNumber); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/Tool/TestMid0046.cs b/src/MIDTesters.Core/Tool/TestMid0046.cs index d5677b6a..4aa2d4d3 100644 --- a/src/MIDTesters.Core/Tool/TestMid0046.cs +++ b/src/MIDTesters.Core/Tool/TestMid0046.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.Tool; namespace MIDTesters.Tool @@ -14,7 +15,7 @@ public void Mid0046Revision1() string package = "00240046001 0102"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.PrimaryTool); + Assert.AreEqual(PrimaryTool.IRC_B, mid.PrimaryTool); AssertEqualPackages(package, mid); } @@ -26,7 +27,7 @@ public void Mid0046ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.PrimaryTool); + Assert.AreEqual(PrimaryTool.IRC_B, mid.PrimaryTool); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/Tool/TestMid0047.cs b/src/MIDTesters.Core/Tool/TestMid0047.cs index 6d2d33de..8c7e4210 100644 --- a/src/MIDTesters.Core/Tool/TestMid0047.cs +++ b/src/MIDTesters.Core/Tool/TestMid0047.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.Tool; namespace MIDTesters.Tool @@ -14,7 +15,7 @@ public void Mid0047Revision1() string package = "00240047001 0103"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.PairingHandlingType); + Assert.AreEqual(PairingHandlingType.FetchLatestPairingStatus, mid.PairingHandlingType); AssertEqualPackages(package, mid); } @@ -26,7 +27,7 @@ public void Mid0047ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.PairingHandlingType); + Assert.AreEqual(PairingHandlingType.FetchLatestPairingStatus, mid.PairingHandlingType); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/Tool/TestMid0048.cs b/src/MIDTesters.Core/Tool/TestMid0048.cs index 5e037e4d..5552d199 100644 --- a/src/MIDTesters.Core/Tool/TestMid0048.cs +++ b/src/MIDTesters.Core/Tool/TestMid0048.cs @@ -1,4 +1,6 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.Tool; namespace MIDTesters.Tool @@ -14,8 +16,8 @@ public void Mid0048Revision1() string package = "00450048001 0107022017-12-01:20:12:45"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.PairingStatus); - Assert.IsNotNull(mid.TimeStamp); + Assert.AreEqual(PairingStatus.Denied, mid.PairingStatus); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.TimeStamp); AssertEqualPackages(package, mid); } @@ -27,8 +29,8 @@ public void Mid0047ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.PairingStatus); - Assert.IsNotNull(mid.TimeStamp); + Assert.AreEqual(PairingStatus.Denied, mid.PairingStatus); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.TimeStamp); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/Tool/TestMid0701.cs b/src/MIDTesters.Core/Tool/TestMid0701.cs index 7ef77579..1695f523 100644 --- a/src/MIDTesters.Core/Tool/TestMid0701.cs +++ b/src/MIDTesters.Core/Tool/TestMid0701.cs @@ -14,8 +14,8 @@ public void Mid0701Revision1() string package = "02110701001 0020001Tool 1 Serial number Tool 1 Model Name Tool 1 Model Article Number 0002Tool 2 Serial number Tool 2 Model Name Tool 2 Model Article Number "; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.Tools); - Assert.AreNotEqual(0, mid.TotalTools); + Assert.AreEqual(2, mid.Tools.Count); + Assert.AreEqual(2, mid.TotalTools); AssertEqualPackages(package, mid); } @@ -27,8 +27,8 @@ public void Mid0701ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.Tools); - Assert.AreNotEqual(0, mid.TotalTools); + Assert.AreEqual(2, mid.Tools.Count); + Assert.AreEqual(2, mid.TotalTools); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/Tool/TestMid0702.cs b/src/MIDTesters.Core/Tool/TestMid0702.cs index 140a885e..31ddce66 100644 --- a/src/MIDTesters.Core/Tool/TestMid0702.cs +++ b/src/MIDTesters.Core/Tool/TestMid0702.cs @@ -14,7 +14,7 @@ public void Mid0701Revision1() string package = "01050702001 0100400301200012040000000QST50-150CTT01202012040000000SERIALNUMBER0120300201000000011"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.ToolDataUpload); + Assert.AreEqual(3, mid.ToolDataUpload.Count); Assert.AreEqual(3, mid.NumberOfToolParameters); Assert.AreEqual(40, mid.ToolNumber); AssertEqualPackages(package, mid); @@ -28,7 +28,7 @@ public void Mid0701ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.ToolDataUpload); + Assert.AreEqual(3, mid.ToolDataUpload.Count); Assert.AreEqual(3, mid.NumberOfToolParameters); Assert.AreEqual(40, mid.ToolNumber); AssertEqualPackages(bytes, mid); diff --git a/src/MIDTesters.Core/Tool/TestMid0703.cs b/src/MIDTesters.Core/Tool/TestMid0703.cs index 1f7be974..ee502e04 100644 --- a/src/MIDTesters.Core/Tool/TestMid0703.cs +++ b/src/MIDTesters.Core/Tool/TestMid0703.cs @@ -14,7 +14,7 @@ public void Mid0701Revision1() string package = "01050703001 0100400301200012040000000QST50-150CTT01202012040000000SERIALNUMBER0120300201000000011"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.CalibrationParameters); + Assert.AreEqual(3, mid.CalibrationParameters.Count); Assert.AreEqual(3, mid.NumberOfCalibrationParameters); Assert.AreEqual(40, mid.ToolNumber); AssertEqualPackages(package, mid); @@ -28,7 +28,7 @@ public void Mid0701ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.CalibrationParameters); + Assert.AreEqual(3, mid.CalibrationParameters.Count); Assert.AreEqual(3, mid.NumberOfCalibrationParameters); Assert.AreEqual(40, mid.ToolNumber); AssertEqualPackages(bytes, mid); diff --git a/src/MIDTesters.Core/Tool/TestMid0704.cs b/src/MIDTesters.Core/Tool/TestMid0704.cs index 09fc5eb5..1b21595d 100644 --- a/src/MIDTesters.Core/Tool/TestMid0704.cs +++ b/src/MIDTesters.Core/Tool/TestMid0704.cs @@ -14,9 +14,8 @@ public void Mid0704Revision1() string package = "00700704001 00201200012040000000QST50-150CTT012150010200000003"; var mid = _midInterpreter.Parse(package); - Assert.AreNotEqual(0, mid.NumberOfDataFields); - Assert.IsNotNull(mid.VariableDataFields); - Assert.AreNotEqual(0, mid.VariableDataFields.Count); + Assert.AreEqual(2, mid.NumberOfDataFields); + Assert.AreEqual(2, mid.VariableDataFields.Count); AssertEqualPackages(package, mid); } @@ -28,9 +27,8 @@ public void Mid0704ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.AreNotEqual(0, mid.NumberOfDataFields); - Assert.IsNotNull(mid.VariableDataFields); - Assert.AreNotEqual(0, mid.VariableDataFields.Count); + Assert.AreEqual(2, mid.NumberOfDataFields); + Assert.AreEqual(2, mid.VariableDataFields.Count); AssertEqualPackages(bytes, mid); } } From 5749db8cd5aa6e29ec9a2966033ada0031377509 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 29 Mar 2026 23:38:25 -0300 Subject: [PATCH 20/78] updated user interface tests --- .../UserInterface/TestMid0110.cs | 4 +-- .../UserInterface/TestMid0111.cs | 25 ++++++++++--------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/MIDTesters.Core/UserInterface/TestMid0110.cs b/src/MIDTesters.Core/UserInterface/TestMid0110.cs index 3725199c..765a9c49 100644 --- a/src/MIDTesters.Core/UserInterface/TestMid0110.cs +++ b/src/MIDTesters.Core/UserInterface/TestMid0110.cs @@ -14,7 +14,7 @@ public void Mid0110Revision1() string package = "00240110001 TEST"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.UserText); + Assert.AreEqual("TEST", mid.UserText); AssertEqualPackages(package, mid); } @@ -26,7 +26,7 @@ public void Mid0110ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.UserText); + Assert.AreEqual("TEST", mid.UserText); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/UserInterface/TestMid0111.cs b/src/MIDTesters.Core/UserInterface/TestMid0111.cs index baa311ba..339eed57 100644 --- a/src/MIDTesters.Core/UserInterface/TestMid0111.cs +++ b/src/MIDTesters.Core/UserInterface/TestMid0111.cs @@ -1,5 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter.UserInterface; +using OpenProtocolInterpreter; namespace MIDTesters.UserInterface { @@ -14,12 +15,12 @@ public void Mid0111Revision1() string package = "01370111001 01200502103Header Text 04Line 2 Text 05Line 3 Text 06Line 4 Text "; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.TextDuration); - Assert.IsNotNull(mid.RemovalCondition); - Assert.IsNotNull(mid.Line1); - Assert.IsNotNull(mid.Line2); - Assert.IsNotNull(mid.Line3); - Assert.IsNotNull(mid.Line4); + Assert.AreEqual(2005, mid.TextDuration); + Assert.AreEqual(RemovalCondition.Acknowledge, mid.RemovalCondition); + Assert.AreEqual("Header Text", mid.Line1.TrimEnd()); + Assert.AreEqual("Line 2 Text", mid.Line2.TrimEnd()); + Assert.AreEqual("Line 3 Text", mid.Line3.TrimEnd()); + Assert.AreEqual("Line 4 Text", mid.Line4.TrimEnd()); AssertEqualPackages(package, mid); } @@ -31,12 +32,12 @@ public void Mid0111ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.TextDuration); - Assert.IsNotNull(mid.RemovalCondition); - Assert.IsNotNull(mid.Line1); - Assert.IsNotNull(mid.Line2); - Assert.IsNotNull(mid.Line3); - Assert.IsNotNull(mid.Line4); + Assert.AreEqual(2005, mid.TextDuration); + Assert.AreEqual(RemovalCondition.Acknowledge, mid.RemovalCondition); + Assert.AreEqual("Header Text", mid.Line1.TrimEnd()); + Assert.AreEqual("Line 2 Text", mid.Line2.TrimEnd()); + Assert.AreEqual("Line 3 Text", mid.Line3.TrimEnd()); + Assert.AreEqual("Line 4 Text", mid.Line4.TrimEnd()); AssertEqualPackages(bytes, mid); } } From 42f44a84ef2e8b9fa0ebed8ee0bc2be0e0d45d34 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 29 Mar 2026 23:38:32 -0300 Subject: [PATCH 21/78] Updated vin tests --- src/MIDTesters.Core/Vin/TestMid0050.cs | 4 +-- src/MIDTesters.Core/Vin/TestMid0052.cs | 40 +++++++++++++------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/MIDTesters.Core/Vin/TestMid0050.cs b/src/MIDTesters.Core/Vin/TestMid0050.cs index dfd7b7c8..0b1a70c0 100644 --- a/src/MIDTesters.Core/Vin/TestMid0050.cs +++ b/src/MIDTesters.Core/Vin/TestMid0050.cs @@ -14,7 +14,7 @@ public void Mid0050Revision1() string package = "00350050001 VehicleIdNumber"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.VinNumber); + Assert.AreEqual("VehicleIdNumber", mid.VinNumber); AssertEqualPackages(package, mid); } @@ -26,7 +26,7 @@ public void Mid0050ByteRevision1() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.VinNumber); + Assert.AreEqual("VehicleIdNumber", mid.VinNumber); AssertEqualPackages(bytes, mid); } } diff --git a/src/MIDTesters.Core/Vin/TestMid0052.cs b/src/MIDTesters.Core/Vin/TestMid0052.cs index f2f1c9cf..957b39a6 100644 --- a/src/MIDTesters.Core/Vin/TestMid0052.cs +++ b/src/MIDTesters.Core/Vin/TestMid0052.cs @@ -17,7 +17,7 @@ public void Mid0052Revision1VehicleIdLengthHigher() string package = "00470052001 VehicleIdNumberHigherThan25"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.VinNumber); + Assert.AreEqual("VehicleIdNumberHigherThan25", mid.VinNumber); AssertEqualPackages(package, mid); } @@ -29,7 +29,7 @@ public void Mid0052ByteRevision1VehicleIdLengthHigher() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.VinNumber); + Assert.AreEqual("VehicleIdNumberHigherThan25", mid.VinNumber); AssertEqualPackages(bytes, mid); } @@ -40,7 +40,7 @@ public void Mid0052Revision1VehicleIdLengthLower() string package = "00450052001 VehicleIdNumber \0"; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.VinNumber); + Assert.AreEqual("VehicleIdNumber", mid.VinNumber.TrimEnd('\0', ' ')); mid.Header.StationId = mid.Header.SpindleId = null; Assert.AreEqual(package, mid.PackWithNul()); } @@ -53,7 +53,7 @@ public void Mid0052ByteRevision1VehicleIdLengthLower() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.VinNumber); + Assert.AreEqual("VehicleIdNumber", mid.VinNumber.TrimEnd('\0', ' ')); mid.Header.StationId = mid.Header.SpindleId = null; Assert.IsTrue(mid.PackBytesWithNul().SequenceEqual(bytes)); } @@ -65,10 +65,10 @@ public void Mid0052Revision2VehicleIdLengthHigher() string package = "01300052002 01VehicleIdNumberHigherThan2502IdentifierPart2 03IdentifierPart3 04IdentifierPart4 "; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); + Assert.AreEqual("VehicleIdNumberHigherThan25", mid.VinNumber); + Assert.AreEqual("IdentifierPart2", mid.IdentifierResultPart2.TrimEnd()); + Assert.AreEqual("IdentifierPart3", mid.IdentifierResultPart3.TrimEnd()); + Assert.AreEqual("IdentifierPart4", mid.IdentifierResultPart4.TrimEnd()); AssertEqualPackages(package, mid); } @@ -80,10 +80,10 @@ public void Mid0052ByteRevision2VehicleIdLengthHigher() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); + Assert.AreEqual("VehicleIdNumberHigherThan25", mid.VinNumber); + Assert.AreEqual("IdentifierPart2", mid.IdentifierResultPart2.TrimEnd()); + Assert.AreEqual("IdentifierPart3", mid.IdentifierResultPart3.TrimEnd()); + Assert.AreEqual("IdentifierPart4", mid.IdentifierResultPart4.TrimEnd()); AssertEqualPackages(bytes, mid); } @@ -94,10 +94,10 @@ public void Mid0052Revision2VehicleIdLengthLower() string package = "01280052002 01VehicleIdNumber 02IdentifierPart2 03IdentifierPart3 04IdentifierPart4 "; var mid = _midInterpreter.Parse(package); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); + Assert.AreEqual("VehicleIdNumber", mid.VinNumber.TrimEnd()); + Assert.AreEqual("IdentifierPart2", mid.IdentifierResultPart2.TrimEnd()); + Assert.AreEqual("IdentifierPart3", mid.IdentifierResultPart3.TrimEnd()); + Assert.AreEqual("IdentifierPart4", mid.IdentifierResultPart4.TrimEnd()); AssertEqualPackages(package, mid); } @@ -109,10 +109,10 @@ public void Mid0052ByteRevision2VehicleIdLengthLower() byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); - Assert.IsNotNull(mid.VinNumber); - Assert.IsNotNull(mid.IdentifierResultPart2); - Assert.IsNotNull(mid.IdentifierResultPart3); - Assert.IsNotNull(mid.IdentifierResultPart4); + Assert.AreEqual("VehicleIdNumber", mid.VinNumber.TrimEnd()); + Assert.AreEqual("IdentifierPart2", mid.IdentifierResultPart2.TrimEnd()); + Assert.AreEqual("IdentifierPart3", mid.IdentifierResultPart3.TrimEnd()); + Assert.AreEqual("IdentifierPart4", mid.IdentifierResultPart4.TrimEnd()); AssertEqualPackages(bytes, mid); } } From 3c33afe4ec4b717cc2c80057121d3f338dba9958 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 29 Mar 2026 23:44:51 -0300 Subject: [PATCH 22/78] updated settings.json --- .vscode/settings.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 6f871fb6..6efb6c15 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,10 @@ "omnisharp.enableRoslynAnalyzers": true, "omnisharp.enableEditorConfigSupport": true, "omnisharp.enableDecompilationSupport": true, - "dotnet.completion.showCompletionItemsFromUnimportedNamespaces": true + "dotnet.completion.showCompletionItemsFromUnimportedNamespaces": true, + "chat.tools.terminal.autoApprove": { + "dotnet build": true, + "dotnet test": true + }, + "dotnet.preferCSharpExtension": true } From 05c0a4154ccd88a960ec0ce0d4a516ac10b8ad4b Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Thu, 2 Apr 2026 22:22:45 -0300 Subject: [PATCH 23/78] Added ReadOnlySpan overload --- src/MIDTesters.Core/TestCustomMid.cs | 5 +- src/OpenProtocolInterpreter/Alarm/Mid0071.cs | 2 +- src/OpenProtocolInterpreter/Alarm/Mid0074.cs | 5 +- src/OpenProtocolInterpreter/Alarm/Mid0076.cs | 4 +- src/OpenProtocolInterpreter/Alarm/Mid1000.cs | 2 +- .../ApplicationSelector/Mid0251.cs | 5 +- .../ApplicationSelector/Mid0254.cs | 5 +- .../ApplicationSelector/Mid0255.cs | 5 +- .../Communication/Mid0004.cs | 5 +- .../Communication/Mid0006.cs | 5 +- .../Communication/Mid0008.cs | 5 +- .../Communication/Mid0009.cs | 5 +- src/OpenProtocolInterpreter/DataField.cs | 23 ++-- .../IOInterface/DigitalInput.cs | 24 +++- .../IOInterface/Mid0215.cs | 7 +- .../IOInterface/Relay.cs | 24 +++- .../Job/Advanced/Mid0140.cs | 5 +- src/OpenProtocolInterpreter/Job/Mid0031.cs | 3 +- src/OpenProtocolInterpreter/Job/Mid0032.cs | 3 +- src/OpenProtocolInterpreter/Job/Mid0033.cs | 3 +- src/OpenProtocolInterpreter/Job/Mid0035.cs | 4 +- src/OpenProtocolInterpreter/Job/Mid0038.cs | 3 +- src/OpenProtocolInterpreter/Job/Mid0039.cs | 3 +- src/OpenProtocolInterpreter/MID.cs | 113 ++++++++++++++---- .../MultiSpindle/Mid0091.cs | 4 +- .../MultiSpindle/Mid0101.cs | 6 +- .../MultipleIdentifiers/IdentifierStatus.cs | 14 +++ .../MultipleIdentifiers/Mid0150.cs | 3 +- .../MultipleIdentifiers/Mid0152.cs | 3 +- .../OpenProtocolConvert.cs | 37 ++++++ .../OpenProtocolInterpreter.csproj | 4 + .../PLCUserData/Mid0240.cs | 3 +- .../PLCUserData/Mid0242.cs | 3 +- .../PLCUserData/Mid0245.cs | 3 +- .../ParameterSet/Mid0011.cs | 3 +- .../ParameterSet/Mid0015.cs | 4 +- .../ParameterSet/Mid0019.cs | 3 +- .../ParameterSet/Mid2505.cs | 3 +- .../PowerMACS/Mid0106.cs | 6 +- .../PowerMACS/Mid0107.cs | 8 +- src/OpenProtocolInterpreter/Result/Mid1201.cs | 6 +- src/OpenProtocolInterpreter/Result/Mid1202.cs | 3 +- .../Tightening/Mid0061.cs | 2 +- .../Tightening/Mid0065.cs | 2 +- .../Tightening/Mid0066.cs | 3 +- .../Tightening/Mid0902.cs | 4 +- .../Tightening/StageResult.cs | 21 +++- .../Tightening/StrategyOptions.cs | 10 +- .../Tightening/TighteningErrorStatus.cs | 17 ++- src/OpenProtocolInterpreter/Tool/Mid0701.cs | 3 +- src/OpenProtocolInterpreter/Tool/Mid0702.cs | 3 +- src/OpenProtocolInterpreter/Tool/Mid0703.cs | 3 +- src/OpenProtocolInterpreter/Tool/Mid0704.cs | 3 +- .../Tool/OpenEndData.cs | 14 ++- src/OpenProtocolInterpreter/Tool/ToolData.cs | 26 +++- .../VariableDataField.cs | 35 ++++++ src/OpenProtocolInterpreter/Vin/Mid0050.cs | 3 +- src/OpenProtocolInterpreter/Vin/Mid0052.cs | 3 +- .../_internals/SafeAccessRevisionsFields.cs | 5 - .../_internals/StringExtensions.cs | 15 ++- 60 files changed, 437 insertions(+), 119 deletions(-) diff --git a/src/MIDTesters.Core/TestCustomMid.cs b/src/MIDTesters.Core/TestCustomMid.cs index 9b871895..4e84c337 100644 --- a/src/MIDTesters.Core/TestCustomMid.cs +++ b/src/MIDTesters.Core/TestCustomMid.cs @@ -3,6 +3,9 @@ using OpenProtocolInterpreter.Time; using System; using System.Collections.Generic; +using System.Linq; +using System.Reflection.Metadata; +using System.Threading.Tasks; namespace MIDTesters { @@ -57,7 +60,7 @@ public string FormattedDate public OverridedMid0081() { - + } public override string Pack() diff --git a/src/OpenProtocolInterpreter/Alarm/Mid0071.cs b/src/OpenProtocolInterpreter/Alarm/Mid0071.cs index 65491f21..988b67ff 100644 --- a/src/OpenProtocolInterpreter/Alarm/Mid0071.cs +++ b/src/OpenProtocolInterpreter/Alarm/Mid0071.cs @@ -59,7 +59,7 @@ public Mid0071(int revision) : this(new Header() } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); HandleRevision(); diff --git a/src/OpenProtocolInterpreter/Alarm/Mid0074.cs b/src/OpenProtocolInterpreter/Alarm/Mid0074.cs index 239f3eaf..d25b5bd3 100644 --- a/src/OpenProtocolInterpreter/Alarm/Mid0074.cs +++ b/src/OpenProtocolInterpreter/Alarm/Mid0074.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.Alarm { @@ -40,7 +41,7 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); RevisionsByFields[1][(int)DataFields.ErrorCode].Size = Header.Revision == 1 ? 4 : 5; diff --git a/src/OpenProtocolInterpreter/Alarm/Mid0076.cs b/src/OpenProtocolInterpreter/Alarm/Mid0076.cs index 1300d3fd..a63de368 100644 --- a/src/OpenProtocolInterpreter/Alarm/Mid0076.cs +++ b/src/OpenProtocolInterpreter/Alarm/Mid0076.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; namespace OpenProtocolInterpreter.Alarm @@ -64,7 +64,7 @@ public Mid0076(int revision) : this(new Header() } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); HandleRevision(); diff --git a/src/OpenProtocolInterpreter/Alarm/Mid1000.cs b/src/OpenProtocolInterpreter/Alarm/Mid1000.cs index 12e867de..3650f577 100644 --- a/src/OpenProtocolInterpreter/Alarm/Mid1000.cs +++ b/src/OpenProtocolInterpreter/Alarm/Mid1000.cs @@ -54,7 +54,7 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); var dataFieldsField = GetField(1, DataFields.EachAlarmDataField); diff --git a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0251.cs b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0251.cs index cc713022..651c0dd8 100644 --- a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0251.cs +++ b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0251.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Text; namespace OpenProtocolInterpreter.ApplicationSelector @@ -51,7 +52,7 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); diff --git a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0254.cs b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0254.cs index 86a1b29e..8ff3e684 100644 --- a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0254.cs +++ b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0254.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Text; @@ -51,7 +52,7 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { base.Parse(package); GreenLights = ParseGreenLights(GetField(1, DataFields.GreenLightCommand).Value).ToList(); diff --git a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0255.cs b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0255.cs index 774aa843..88547698 100644 --- a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0255.cs +++ b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0255.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Text; namespace OpenProtocolInterpreter.ApplicationSelector @@ -50,7 +51,7 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { base.Parse(package); RedLights = ParseRedLights(GetField(1, DataFields.RedLightCommand).Value); diff --git a/src/OpenProtocolInterpreter/Communication/Mid0004.cs b/src/OpenProtocolInterpreter/Communication/Mid0004.cs index b1878b3c..793e9ef3 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0004.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0004.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.Communication { @@ -57,7 +58,7 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); HandleRevision(); diff --git a/src/OpenProtocolInterpreter/Communication/Mid0006.cs b/src/OpenProtocolInterpreter/Communication/Mid0006.cs index dce6fd7e..7cb5ef76 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0006.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0006.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.Communication { @@ -53,7 +54,7 @@ public Mid0006(Header header) : base(header) { } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); GetField(1, DataFields.ExtraData).Size = Header.Length - 29; diff --git a/src/OpenProtocolInterpreter/Communication/Mid0008.cs b/src/OpenProtocolInterpreter/Communication/Mid0008.cs index 7da632b7..4746975d 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0008.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0008.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.Communication { @@ -56,7 +57,7 @@ public Mid0008(Header header) : base(header) { } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); GetField(1, DataFields.ExtraData).Size = Header.Length - 29; diff --git a/src/OpenProtocolInterpreter/Communication/Mid0009.cs b/src/OpenProtocolInterpreter/Communication/Mid0009.cs index de8d4f4a..577e9991 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0009.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0009.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.Communication { @@ -56,7 +57,7 @@ public Mid0009(Header header) : base(header) { } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); GetField(1, DataFields.ExtraData).Size = Header.Length - 29; diff --git a/src/OpenProtocolInterpreter/DataField.cs b/src/OpenProtocolInterpreter/DataField.cs index d9f6cb05..b660a073 100644 --- a/src/OpenProtocolInterpreter/DataField.cs +++ b/src/OpenProtocolInterpreter/DataField.cs @@ -20,15 +20,13 @@ public class DataField public int Size { get; set; } public string Value { get; set; } public byte[] RawValue { get; set; } + public ReadOnlySpan Span => Value.AsSpan(); public int TotalSize => HasPrefix ? 2 + Size : Size; public DataField(int field, int index, int size, bool hasPrefix = true) + : this(field, index, size, ' ', PaddingOrientation.RightPadded, hasPrefix) { - HasPrefix = hasPrefix; - Field = field; - Index = index; - Size = size; - _paddingChar = ' '; + } public DataField(Enum field, int index, int size, bool hasPrefix = true) @@ -37,6 +35,12 @@ public DataField(Enum field, int index, int size, bool hasPrefix = true) } + public DataField(Enum field, int index, int size, char paddingChar, PaddingOrientation paddingOrientation = PaddingOrientation.RightPadded, bool hasPrefix = true) + : this(field.GetHashCode(), index, size, paddingChar, paddingOrientation, hasPrefix) + { + + } + public DataField(int field, int index, int size, char paddingChar, PaddingOrientation paddingOrientation = PaddingOrientation.RightPadded, bool hasPrefix = true) { _paddingChar = paddingChar; @@ -47,12 +51,6 @@ public DataField(int field, int index, int size, char paddingChar, PaddingOrient Size = size; } - public DataField(Enum field, int index, int size, char paddingChar, PaddingOrientation paddingOrientation = PaddingOrientation.RightPadded, bool hasPrefix = true) - : this(field.GetHashCode(), index, size, paddingChar, paddingOrientation, hasPrefix) - { - - } - public virtual T GetValue(Func converter) { if (string.IsNullOrWhiteSpace(Value)) @@ -93,6 +91,9 @@ public virtual void SetValue(string value) SetValue(OpenProtocolConvert.TruncatePadded, value); } + public virtual void SetValue(ReadOnlySpan value) + => SetValue(value.ToString()); + public static DataField String(int field, int index, int size, bool hasPrefix = true) => new(field, index, size, ' ', PaddingOrientation.RightPadded, hasPrefix); public static DataField String(int field, int index, int size, PaddingOrientation paddingOrientation, bool hasPrefix = true) diff --git a/src/OpenProtocolInterpreter/IOInterface/DigitalInput.cs b/src/OpenProtocolInterpreter/IOInterface/DigitalInput.cs index 5402e168..069a5d54 100644 --- a/src/OpenProtocolInterpreter/IOInterface/DigitalInput.cs +++ b/src/OpenProtocolInterpreter/IOInterface/DigitalInput.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.IOInterface { @@ -25,6 +26,15 @@ public static DigitalInput Parse(string section) }; } + public static DigitalInput Parse(ReadOnlySpan section) + { + return new DigitalInput() + { + Number = (DigitalInputNumber)OpenProtocolConvert.ToInt32(section.Slice(0, 3)), + Status = OpenProtocolConvert.ToBoolean(section.Slice(3, 1)) + }; + } + public static IEnumerable ParseAll(string value) { if(string.IsNullOrEmpty(value)) @@ -40,5 +50,17 @@ public static IEnumerable ParseAll(string value) } } + public static IEnumerable ParseAll(ReadOnlySpan value) + { + if (value.IsEmpty) + return System.Array.Empty(); + + var result = new System.Collections.Generic.List(); + const int sectionSize = 4; + for (int i = 0; i < value.Length; i += sectionSize) + result.Add(Parse(value.Slice(i, sectionSize))); + return result; + } + } } diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs index 1bdcbf0b..faea8fc8 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Text; @@ -105,7 +106,7 @@ public override string Pack() return builder.ToString(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); @@ -115,7 +116,7 @@ public override Mid Parse(string package) var digitalListField = GetField(revision, DataFields.DigitalInputList); if (revision > 1) { - int numberOfRelays = OpenProtocolConvert.ToInt32(GetValue(GetField(2, DataFields.NumberOfRelays), package)); + int numberOfRelays = OpenProtocolConvert.ToInt32(GetValue(GetField(2, DataFields.NumberOfRelays), package).ToString()); relayListField.Size = numberOfRelays * 4; var numberOfDigitalInputsField = GetField(2, DataFields.NumberOfDigitalInputs); diff --git a/src/OpenProtocolInterpreter/IOInterface/Relay.cs b/src/OpenProtocolInterpreter/IOInterface/Relay.cs index d8b15b9c..5d68ba48 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Relay.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Relay.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.IOInterface { @@ -25,6 +26,15 @@ public static Relay Parse(string section) }; } + public static Relay Parse(ReadOnlySpan section) + { + return new Relay() + { + Number = (RelayNumber)OpenProtocolConvert.ToInt32(section.Slice(0, 3)), + Status = OpenProtocolConvert.ToBoolean(section.Slice(3, 1)) + }; + } + public static IEnumerable ParseAll(string value) { if(string.IsNullOrEmpty(value)) @@ -39,5 +49,17 @@ public static IEnumerable ParseAll(string value) yield return Parse(section); } } + + public static IEnumerable ParseAll(ReadOnlySpan value) + { + if (value.IsEmpty) + return System.Array.Empty(); + + var result = new System.Collections.Generic.List(); + const int sectionSize = 4; + for (int i = 0; i < value.Length; i += sectionSize) + result.Add(Parse(value.Slice(i, sectionSize))); + return result; + } } } diff --git a/src/OpenProtocolInterpreter/Job/Advanced/Mid0140.cs b/src/OpenProtocolInterpreter/Job/Advanced/Mid0140.cs index 54fd75e3..6733f135 100644 --- a/src/OpenProtocolInterpreter/Job/Advanced/Mid0140.cs +++ b/src/OpenProtocolInterpreter/Job/Advanced/Mid0140.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; @@ -155,14 +156,14 @@ public override string Pack() return string.Concat(BuildHeader(), base.Pack(revision, ref prefixIndex)); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); int length = Header.Length; var revision = Header.StandardizedRevision; ResetDataFields(revision); var numberOfParameterSetsField = RevisionsByFields[revision].First(x => x.Field == (int)DataFields.NumberOfParameterSets); - var numberOfParameterSets = int.Parse(package.Substring(numberOfParameterSetsField.Index + 2, numberOfParameterSetsField.Size)); + var numberOfParameterSets = int.Parse(package.Slice(numberOfParameterSetsField.Index + 2, numberOfParameterSetsField.Size).ToString()); var jobListField = GetField(revision, DataFields.JobList); jobListField.Size = numberOfParameterSets * AdvancedJob.GetDefaultSize(revision); AdjustDataFieldsIndexes(jobListField.Index + jobListField.Size + 2, revision); diff --git a/src/OpenProtocolInterpreter/Job/Mid0031.cs b/src/OpenProtocolInterpreter/Job/Mid0031.cs index a4509c47..3d0dad1a 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0031.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0031.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Text; @@ -56,7 +57,7 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); HandleRevisions(); diff --git a/src/OpenProtocolInterpreter/Job/Mid0032.cs b/src/OpenProtocolInterpreter/Job/Mid0032.cs index 43831d93..97c732ab 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0032.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0032.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace OpenProtocolInterpreter.Job @@ -38,7 +39,7 @@ public Mid0032(int revision) : this(new Header() { } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); HandleRevision(); diff --git a/src/OpenProtocolInterpreter/Job/Mid0033.cs b/src/OpenProtocolInterpreter/Job/Mid0033.cs index dacce8e0..12872d41 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0033.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0033.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; @@ -104,7 +105,7 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); HandleRevisions(); diff --git a/src/OpenProtocolInterpreter/Job/Mid0035.cs b/src/OpenProtocolInterpreter/Job/Mid0035.cs index 687db0eb..ebbdb03f 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0035.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0035.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; namespace OpenProtocolInterpreter.Job @@ -122,7 +122,7 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); HandleRevision(); diff --git a/src/OpenProtocolInterpreter/Job/Mid0038.cs b/src/OpenProtocolInterpreter/Job/Mid0038.cs index 1963ffb5..c2722e8a 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0038.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0038.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace OpenProtocolInterpreter.Job @@ -44,7 +45,7 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); HandleRevision(); diff --git a/src/OpenProtocolInterpreter/Job/Mid0039.cs b/src/OpenProtocolInterpreter/Job/Mid0039.cs index 544ff06c..4b9490b0 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0039.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0039.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace OpenProtocolInterpreter.Job @@ -44,7 +45,7 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); HandleRevisions(); diff --git a/src/OpenProtocolInterpreter/MID.cs b/src/OpenProtocolInterpreter/MID.cs index 69fe3281..d1abec32 100644 --- a/src/OpenProtocolInterpreter/MID.cs +++ b/src/OpenProtocolInterpreter/MID.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using System.Text; namespace OpenProtocolInterpreter @@ -97,38 +96,28 @@ protected virtual string Pack(List dataFields, ref int prefixIndex) return builder.ToString(); } - protected virtual Dictionary> RegisterDatafields() => new(); + protected virtual Dictionary> RegisterDatafields() => []; protected virtual Header ProcessHeader(string package) + => ProcessHeader(package.AsSpan()); + + protected virtual Header ProcessHeader(ReadOnlySpan package) { if (package.Length < 20) { - package = package.PadRight(20, ' '); - } - - static bool IsNotEmptyOrZero(string package, out int value) - { - value = 0; - return !string.IsNullOrWhiteSpace(package) && int.TryParse(package, out value) && value > 0; + Span buffer = stackalloc char[20]; + package.CopyTo(buffer); + buffer.Slice(package.Length).Fill(' '); + return ParseHeader(buffer); } - var header = new Header - { - Length = int.Parse(package.Substring(0, 4)), - Mid = int.Parse(package.Substring(4, 4)), - Revision = IsNotEmptyOrZero(package.Substring(8, 3), out var revision) ? revision : 1, - NoAckFlag = !string.IsNullOrWhiteSpace(package.Substring(11, 1)), - StationId = int.TryParse(package.Substring(12, 2), out var stationId) ? stationId : 1, - SpindleId = int.TryParse(package.Substring(14, 2), out var spindleId) ? spindleId : 1, - SequenceNumber = IsNotEmptyOrZero(package.Substring(16, 2), out var sequenceNumber) ? sequenceNumber : default(int?), - NumberOfMessages = IsNotEmptyOrZero(package.Substring(18, 1), out var numberOfMessages) ? numberOfMessages : default(int?), - MessageNumber = IsNotEmptyOrZero(package.Substring(19, 1), out var messageNumber) ? messageNumber : default(int?) - }; - - return header; + return ParseHeader(package); } public virtual Mid Parse(string package) + => Parse(package.AsSpan()); + + public virtual Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); ProcessDataFields(package); @@ -153,7 +142,22 @@ protected virtual void ProcessDataFields(string package) } } + protected virtual void ProcessDataFields(ReadOnlySpan package) + { + if (!RevisionsByFields.Any()) + return; + + int revision = Header.Revision > 0 ? Header.Revision : 1; + for (int i = 1; i <= revision; i++) + { + ProcessDataFields(i, package); + } + } + protected virtual void ProcessDataFields(int revision, string package) + => ProcessDataFields(revision, package.AsSpan()); + + protected virtual void ProcessDataFields(int revision, ReadOnlySpan package) { if (RevisionsByFields.TryGetValue(revision, out var fields)) { @@ -162,9 +166,12 @@ protected virtual void ProcessDataFields(int revision, string package) } protected virtual void ProcessDataFields(List dataFields, string package) + => ProcessDataFields(dataFields, package.AsSpan()); + + protected virtual void ProcessDataFields(List dataFields, ReadOnlySpan package) { foreach (var dataField in dataFields) - dataField.Value = GetValue(dataField, package); + dataField.SetValue(GetValue(dataField, package)); } protected string GetValue(DataField field, string package) @@ -179,6 +186,19 @@ protected string GetValue(DataField field, string package) } } + protected ReadOnlySpan GetValue(DataField field, ReadOnlySpan package) + { + try + { + return field.HasPrefix ? package.Slice(2 + field.Index, field.Size) : package.Slice(field.Index, field.Size); + } + catch (ArgumentOutOfRangeException) + { + return ReadOnlySpan.Empty; + } + } + + protected byte[] GetValue(DataField field, byte[] package) { try @@ -214,8 +234,51 @@ protected DataField GetField(int revision, TEnum field) where TEnum : str => GetField(revision, field.GetHashCode()); protected static string ToAscii(byte[] bytes) => Encoding.ASCII.GetString(bytes); - protected static byte[] ToBytes(string value) => Encoding.ASCII.GetBytes(value); + protected static Span ToBytesSpan(string value) => ToBytes(value.AsSpan()); + protected static Span ToBytes(ReadOnlySpan value) + { + var buffer = new byte[value.Length]; +#if NETSTANDARD2_0 + Encoding.ASCII.GetBytes(value.ToString(), 0, value.Length, buffer, 0); +#else + Encoding.ASCII.GetBytes(value, buffer); +#endif + return buffer; + } + private Header ParseHeader(ReadOnlySpan package) + { +#if NETSTANDARD2_0 + static bool IsNotEmptyOrZero(ReadOnlySpan package, out int value) + { + value = 0; + return !package.IsWhiteSpace() && int.TryParse(package.ToString(), out value) && value > 0; + } + static int ParseInt(ReadOnlySpan span) => int.Parse(span.ToString()); + static bool TryParseInt(ReadOnlySpan span, out int value) => int.TryParse(span.ToString(), out value); +#else + static bool IsNotEmptyOrZero(ReadOnlySpan package, out int value) + { + value = 0; + return !package.IsWhiteSpace() && int.TryParse(package, out value) && value > 0; + } + static int ParseInt(ReadOnlySpan span) => int.Parse(span); + static bool TryParseInt(ReadOnlySpan span, out int value) => int.TryParse(span, out value); +#endif + + return new Header + { + Length = ParseInt(package.Slice(0, 4)), + Mid = ParseInt(package.Slice(4, 4)), + Revision = IsNotEmptyOrZero(package.Slice(8, 3), out var revision) ? revision : 1, + NoAckFlag = !package.Slice(11, 1).IsWhiteSpace(), + StationId = TryParseInt(package.Slice(12, 2), out var stationId) ? stationId : 1, + SpindleId = TryParseInt(package.Slice(14, 2), out var spindleId) ? spindleId : 1, + SequenceNumber = IsNotEmptyOrZero(package.Slice(16, 2), out var sequenceNumber) ? sequenceNumber : default(int?), + NumberOfMessages = IsNotEmptyOrZero(package.Slice(18, 1), out var numberOfMessages) ? numberOfMessages : default(int?), + MessageNumber = IsNotEmptyOrZero(package.Slice(19, 1), out var messageNumber) ? messageNumber : default(int?) + }; + } } } diff --git a/src/OpenProtocolInterpreter/MultiSpindle/Mid0091.cs b/src/OpenProtocolInterpreter/MultiSpindle/Mid0091.cs index 6f2fce75..e4d663be 100644 --- a/src/OpenProtocolInterpreter/MultiSpindle/Mid0091.cs +++ b/src/OpenProtocolInterpreter/MultiSpindle/Mid0091.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Text; @@ -59,7 +59,7 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); var spindleField = GetField(1, DataFields.SpindleStatus); diff --git a/src/OpenProtocolInterpreter/MultiSpindle/Mid0101.cs b/src/OpenProtocolInterpreter/MultiSpindle/Mid0101.cs index 2620b3ab..1476a961 100644 --- a/src/OpenProtocolInterpreter/MultiSpindle/Mid0101.cs +++ b/src/OpenProtocolInterpreter/MultiSpindle/Mid0101.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; namespace OpenProtocolInterpreter.MultiSpindle @@ -146,11 +146,11 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); var spindleOrPressesField = GetField(1, DataFields.NumberOfSpindlesOrPresses); - int spindleOrPresses = OpenProtocolConvert.ToInt32(package.Substring(spindleOrPressesField.Index + 2, spindleOrPressesField.Size)); + int spindleOrPresses = OpenProtocolConvert.ToInt32(GetValue(spindleOrPressesField, package).ToString()); var spindesOrPressesStatusField = GetField(1, DataFields.SpindesOrPressesStatus); spindesOrPressesStatusField.Size = spindleOrPresses * 18; if(Header.Revision > 3) diff --git a/src/OpenProtocolInterpreter/MultipleIdentifiers/IdentifierStatus.cs b/src/OpenProtocolInterpreter/MultipleIdentifiers/IdentifierStatus.cs index 7e2ce082..5b9c9e0c 100644 --- a/src/OpenProtocolInterpreter/MultipleIdentifiers/IdentifierStatus.cs +++ b/src/OpenProtocolInterpreter/MultipleIdentifiers/IdentifierStatus.cs @@ -35,5 +35,19 @@ public static IdentifierStatus Parse(string section) ResultPart = section.SafeSubstring(5, 25) }; } + + public static IdentifierStatus Parse(ReadOnlySpan section) + { + if (section.IsEmpty) + return default; + + return new IdentifierStatus() + { + IdentifierTypeNumber = OpenProtocolConvert.ToInt32(section.Slice(0, 1)), + IncludedInWorkOrder = OpenProtocolConvert.ToBoolean(section.Slice(1, 2)), + StatusInWorkOrder = (StatusInWorkOrder)OpenProtocolConvert.ToInt32(section.Slice(3, 2)), + ResultPart = section.SafeSubstring(5, 25) + }; + } } } diff --git a/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0150.cs b/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0150.cs index 4b5268b9..477e5e0c 100644 --- a/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0150.cs +++ b/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0150.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace OpenProtocolInterpreter.MultipleIdentifiers @@ -44,7 +45,7 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); GetField(1, DataFields.IdentifierData).Size = Header.Length - 20; diff --git a/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0152.cs b/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0152.cs index 5310743b..cc54846f 100644 --- a/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0152.cs +++ b/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0152.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace OpenProtocolInterpreter.MultipleIdentifiers @@ -46,7 +47,7 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { base.Parse(package); diff --git a/src/OpenProtocolInterpreter/OpenProtocolConvert.cs b/src/OpenProtocolInterpreter/OpenProtocolConvert.cs index dfaa9088..c87cde19 100644 --- a/src/OpenProtocolInterpreter/OpenProtocolConvert.cs +++ b/src/OpenProtocolInterpreter/OpenProtocolConvert.cs @@ -26,6 +26,17 @@ public static bool ToBoolean(string value) return Convert.ToBoolean(intValue); } + public static bool ToBoolean(ReadOnlySpan value) + { + int intValue = 0; +#if NETSTANDARD2_0 + if (!value.IsEmpty) int.TryParse(value.ToString(), out intValue); +#else + if (!value.IsEmpty) int.TryParse(value, out intValue); +#endif + return Convert.ToBoolean(intValue); + } + public static string ToString(DateTime value) => value.ToString("yyyy-MM-dd:HH:mm:ss"); @@ -82,6 +93,12 @@ public static decimal ToTruncatedDecimal(string value) return intValue / 100m; } + public static decimal ToTruncatedDecimal(ReadOnlySpan value) + { + int intValue = ToInt32(value); + return intValue / 100m; + } + public static string ToString(int value) => value.ToString(); @@ -100,6 +117,16 @@ public static int ToInt32(string value) return convertedValue; } + public static int ToInt32(ReadOnlySpan value) + { +#if NETSTANDARD2_0 + int.TryParse(value.ToString(), out int convertedValue); +#else + int.TryParse(value, out int convertedValue); +#endif + return convertedValue; + } + public static string ToString(long value) => value.ToString(); @@ -112,6 +139,16 @@ public static long ToInt64(string value) return convertedValue; } + public static long ToInt64(ReadOnlySpan value) + { +#if NETSTANDARD2_0 + long.TryParse(value.ToString(), out long convertedValue); +#else + long.TryParse(value, out long convertedValue); +#endif + return convertedValue; + } + public static string ToString(IEnumerable value) { var builder = new StringBuilder(); diff --git a/src/OpenProtocolInterpreter/OpenProtocolInterpreter.csproj b/src/OpenProtocolInterpreter/OpenProtocolInterpreter.csproj index 22e57ae6..a20357ed 100644 --- a/src/OpenProtocolInterpreter/OpenProtocolInterpreter.csproj +++ b/src/OpenProtocolInterpreter/OpenProtocolInterpreter.csproj @@ -35,4 +35,8 @@ + + + + diff --git a/src/OpenProtocolInterpreter/PLCUserData/Mid0240.cs b/src/OpenProtocolInterpreter/PLCUserData/Mid0240.cs index df0b690f..f71d7604 100644 --- a/src/OpenProtocolInterpreter/PLCUserData/Mid0240.cs +++ b/src/OpenProtocolInterpreter/PLCUserData/Mid0240.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace OpenProtocolInterpreter.PLCUserData @@ -41,7 +42,7 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); GetField(1, DataFields.UserData).Size = Header.Length - 20; diff --git a/src/OpenProtocolInterpreter/PLCUserData/Mid0242.cs b/src/OpenProtocolInterpreter/PLCUserData/Mid0242.cs index e6288ff6..dd1e1ecb 100644 --- a/src/OpenProtocolInterpreter/PLCUserData/Mid0242.cs +++ b/src/OpenProtocolInterpreter/PLCUserData/Mid0242.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace OpenProtocolInterpreter.PLCUserData @@ -33,7 +34,7 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); GetField(1, DataFields.UserData).Size = Header.Length - 20; diff --git a/src/OpenProtocolInterpreter/PLCUserData/Mid0245.cs b/src/OpenProtocolInterpreter/PLCUserData/Mid0245.cs index 34260ec6..88b239f9 100644 --- a/src/OpenProtocolInterpreter/PLCUserData/Mid0245.cs +++ b/src/OpenProtocolInterpreter/PLCUserData/Mid0245.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace OpenProtocolInterpreter.PLCUserData @@ -89,7 +90,7 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); GetField(1, DataFields.UserData).Size = Header.Length - 23; diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid0011.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid0011.cs index 0a960eb7..b5d7a867 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid0011.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid0011.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; @@ -46,7 +47,7 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid0015.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid0015.cs index f35bcbac..e980ed50 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid0015.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid0015.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; namespace OpenProtocolInterpreter.ParameterSet @@ -121,7 +121,7 @@ public override string Pack() return BuildHeader() + base.Pack(Header.StandardizedRevision, ref index); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); ProcessDataFields(Header.StandardizedRevision, package); diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid0019.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid0019.cs index 50af294a..46d8ac30 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid0019.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid0019.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace OpenProtocolInterpreter.ParameterSet @@ -46,7 +47,7 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); HandleRevision(); diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid2505.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid2505.cs index 61da610b..f68a4779 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid2505.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid2505.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; @@ -57,7 +58,7 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); var dataFieldsField = GetField(1, DataFields.DataFields); diff --git a/src/OpenProtocolInterpreter/PowerMACS/Mid0106.cs b/src/OpenProtocolInterpreter/PowerMACS/Mid0106.cs index 1e4346db..1209b46a 100644 --- a/src/OpenProtocolInterpreter/PowerMACS/Mid0106.cs +++ b/src/OpenProtocolInterpreter/PowerMACS/Mid0106.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -152,11 +152,11 @@ public override string Pack() return builder.ToString(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); - int numberOfBolts = OpenProtocolConvert.ToInt32(package.Substring(GetField(1, DataFields.NumberOfBolts).Index + 2, GetField(1, DataFields.NumberOfBolts).Size)); + int numberOfBolts = OpenProtocolConvert.ToInt32(GetValue(GetField(1, DataFields.NumberOfBolts), package).ToString()); GetField(1, DataFields.BoltData).Size *= numberOfBolts; var numberOfSpecialValuesField = GetField(1, DataFields.NumberOfSpecialValues); diff --git a/src/OpenProtocolInterpreter/PowerMACS/Mid0107.cs b/src/OpenProtocolInterpreter/PowerMACS/Mid0107.cs index c315a568..05d8a1f3 100644 --- a/src/OpenProtocolInterpreter/PowerMACS/Mid0107.cs +++ b/src/OpenProtocolInterpreter/PowerMACS/Mid0107.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -140,11 +140,11 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); - int numberOfBoltResults = OpenProtocolConvert.ToInt32(GetValue(GetField(1, DataFields.NumberOfBoltResults), package)); + int numberOfBoltResults = OpenProtocolConvert.ToInt32(GetValue(GetField(1, DataFields.NumberOfBoltResults), package).ToString()); var boltResultField = GetField(1, DataFields.BoltResults); boltResultField.Size = 29 * numberOfBoltResults; @@ -157,7 +157,7 @@ public override Mid Parse(string package) var stepResultsField = GetField(1, DataFields.StepResults); stepResultsField.Index = 2 + allStepDataSentField.Index + allStepDataSentField.Size; - int numberOfStepResults = OpenProtocolConvert.ToInt32(GetValue(numberOfStepResultsField, package)); + int numberOfStepResults = OpenProtocolConvert.ToInt32(GetValue(numberOfStepResultsField, package).ToString()); stepResultsField.Size = 31 * numberOfStepResults; var numberOfSpecialValuesField = GetField(1, DataFields.NumberOfSpecialValues); diff --git a/src/OpenProtocolInterpreter/Result/Mid1201.cs b/src/OpenProtocolInterpreter/Result/Mid1201.cs index e1360e0b..8fa7919a 100644 --- a/src/OpenProtocolInterpreter/Result/Mid1201.cs +++ b/src/OpenProtocolInterpreter/Result/Mid1201.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -108,13 +108,13 @@ public override string Pack() return string.Concat(BuildHeader(), base.Pack(revision, ref index)); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); var revision = Header.StandardizedRevision; var rawTotalObjectData = GetValue(GetField(revision, DataFields.NumberOfObjects), package); - int totalObjectData = OpenProtocolConvert.ToInt32(rawTotalObjectData); + int totalObjectData = OpenProtocolConvert.ToInt32(rawTotalObjectData.ToString()); var objectDataField = GetField(revision, DataFields.ObjectData); objectDataField.Size = totalObjectData * ObjectData.Size(Header.Revision); diff --git a/src/OpenProtocolInterpreter/Result/Mid1202.cs b/src/OpenProtocolInterpreter/Result/Mid1202.cs index f6afabe9..7382fe24 100644 --- a/src/OpenProtocolInterpreter/Result/Mid1202.cs +++ b/src/OpenProtocolInterpreter/Result/Mid1202.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; @@ -86,7 +87,7 @@ public override string Pack() return string.Concat(BuildHeader(), base.Pack(revision, ref prefixIndex)); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); var revision = Header.StandardizedRevision; diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0061.cs b/src/OpenProtocolInterpreter/Tightening/Mid0061.cs index 50f02f32..d50d56ce 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0061.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0061.cs @@ -496,7 +496,7 @@ public override string Pack() return builder.ToString(); } - protected override void ProcessDataFields(string package) + protected override void ProcessDataFields(ReadOnlySpan package) { if (Header.Revision == 1 || Header.Revision == 999) { diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0065.cs b/src/OpenProtocolInterpreter/Tightening/Mid0065.cs index 8fbace3d..6e9582ef 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0065.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0065.cs @@ -365,7 +365,7 @@ public override string Pack() return builder.ToString(); } - protected override void ProcessDataFields(string package) + protected override void ProcessDataFields(ReadOnlySpan package) { if (Header.Revision == 1) { diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0066.cs b/src/OpenProtocolInterpreter/Tightening/Mid0066.cs index d85e3b60..8e8c5997 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0066.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0066.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace OpenProtocolInterpreter.Tightening @@ -36,7 +37,7 @@ public Mid0066(Header header) : base(header) { } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); HandleRevisionSizes(); diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0902.cs b/src/OpenProtocolInterpreter/Tightening/Mid0902.cs index 128091f2..34a766c7 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0902.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0902.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; @@ -69,7 +69,7 @@ public override string Pack() return string.Concat(BuildHeader(), base.Pack(revision, ref index)); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); diff --git a/src/OpenProtocolInterpreter/Tightening/StageResult.cs b/src/OpenProtocolInterpreter/Tightening/StageResult.cs index 1f69f45a..99e7deeb 100644 --- a/src/OpenProtocolInterpreter/Tightening/StageResult.cs +++ b/src/OpenProtocolInterpreter/Tightening/StageResult.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.Tightening { @@ -25,6 +26,15 @@ public static StageResult Parse(string value) }; } + public static StageResult Parse(ReadOnlySpan value) + { + return new StageResult() + { + Torque = OpenProtocolConvert.ToTruncatedDecimal(value.Slice(0, 6)), + Angle = OpenProtocolConvert.ToInt32(value.Slice(6, 5)) + }; + } + public static IEnumerable ParseAll(string value) { const int sectionSize = 11; @@ -34,5 +44,14 @@ public static IEnumerable ParseAll(string value) yield return Parse(section); } } + + public static IEnumerable ParseAll(ReadOnlySpan value) + { + var result = new List(); + const int sectionSize = 11; + for (int i = 0; i < value.Length; i += sectionSize) + result.Add(Parse(value.Slice(i, sectionSize))); + return result; + } } } diff --git a/src/OpenProtocolInterpreter/Tightening/StrategyOptions.cs b/src/OpenProtocolInterpreter/Tightening/StrategyOptions.cs index bc900ced..4db0b8fe 100644 --- a/src/OpenProtocolInterpreter/Tightening/StrategyOptions.cs +++ b/src/OpenProtocolInterpreter/Tightening/StrategyOptions.cs @@ -1,4 +1,5 @@ -using System.Text; +using System; +using System.Text; namespace OpenProtocolInterpreter.Tightening { @@ -64,6 +65,13 @@ public static StrategyOptions Parse(string value) return Parse(bytes); } + public static StrategyOptions Parse(ReadOnlySpan value) + { + var intValue = OpenProtocolConvert.ToInt32(value); + var bytes = System.BitConverter.GetBytes(intValue); + return Parse(bytes); + } + public static StrategyOptions Parse(byte[] value) { return new StrategyOptions() diff --git a/src/OpenProtocolInterpreter/Tightening/TighteningErrorStatus.cs b/src/OpenProtocolInterpreter/Tightening/TighteningErrorStatus.cs index 57c6e0b3..849f3090 100644 --- a/src/OpenProtocolInterpreter/Tightening/TighteningErrorStatus.cs +++ b/src/OpenProtocolInterpreter/Tightening/TighteningErrorStatus.cs @@ -1,4 +1,5 @@ -using System.Text; +using System; +using System.Text; namespace OpenProtocolInterpreter.Tightening { @@ -109,6 +110,13 @@ public static TighteningErrorStatus Parse(string value) return Parse(bytes); } + public static TighteningErrorStatus Parse(ReadOnlySpan value) + { + var longValue = OpenProtocolConvert.ToInt64(value); + var bytes = System.BitConverter.GetBytes(longValue); + return Parse(bytes); + } + public static TighteningErrorStatus Parse(byte[] value) { return new TighteningErrorStatus @@ -244,6 +252,13 @@ public static TighteningErrorStatus2 Parse(string value) return Parse(bytes); } + public static TighteningErrorStatus2 Parse(ReadOnlySpan value) + { + var longValue = OpenProtocolConvert.ToInt64(value); + var bytes = System.BitConverter.GetBytes(longValue); + return Parse(bytes); + } + public static TighteningErrorStatus2 Parse(byte[] value) { var obj = new TighteningErrorStatus2() diff --git a/src/OpenProtocolInterpreter/Tool/Mid0701.cs b/src/OpenProtocolInterpreter/Tool/Mid0701.cs index e18bcd16..ad8b4c32 100644 --- a/src/OpenProtocolInterpreter/Tool/Mid0701.cs +++ b/src/OpenProtocolInterpreter/Tool/Mid0701.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; @@ -42,7 +43,7 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); diff --git a/src/OpenProtocolInterpreter/Tool/Mid0702.cs b/src/OpenProtocolInterpreter/Tool/Mid0702.cs index 2290aae8..aa0d4531 100644 --- a/src/OpenProtocolInterpreter/Tool/Mid0702.cs +++ b/src/OpenProtocolInterpreter/Tool/Mid0702.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; @@ -35,7 +36,7 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); var dataFieldsField = GetField(1, DataFields.EachToolDataUpload); diff --git a/src/OpenProtocolInterpreter/Tool/Mid0703.cs b/src/OpenProtocolInterpreter/Tool/Mid0703.cs index eaa21f85..81eeafef 100644 --- a/src/OpenProtocolInterpreter/Tool/Mid0703.cs +++ b/src/OpenProtocolInterpreter/Tool/Mid0703.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; @@ -48,7 +49,7 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); var dataFieldsField = GetField(1, DataFields.EachCalibrationParameter); diff --git a/src/OpenProtocolInterpreter/Tool/Mid0704.cs b/src/OpenProtocolInterpreter/Tool/Mid0704.cs index edfabba6..15d3e69e 100644 --- a/src/OpenProtocolInterpreter/Tool/Mid0704.cs +++ b/src/OpenProtocolInterpreter/Tool/Mid0704.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; @@ -49,7 +50,7 @@ public override string Pack() return string.Concat(BuildHeader(), base.Pack(revision, ref index)); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); diff --git a/src/OpenProtocolInterpreter/Tool/OpenEndData.cs b/src/OpenProtocolInterpreter/Tool/OpenEndData.cs index a585a811..fe75e201 100644 --- a/src/OpenProtocolInterpreter/Tool/OpenEndData.cs +++ b/src/OpenProtocolInterpreter/Tool/OpenEndData.cs @@ -1,4 +1,6 @@ -namespace OpenProtocolInterpreter.Tool +using System; + +namespace OpenProtocolInterpreter.Tool { public class OpenEndData { @@ -34,5 +36,15 @@ public static OpenEndData Parse(string value) MotorRotation = (MotorRotation)OpenProtocolConvert.ToInt32(value[2].ToString()), }; } + + public static OpenEndData Parse(ReadOnlySpan value) + { + return new OpenEndData() + { + UseOpenEnd = OpenProtocolConvert.ToBoolean(value.Slice(0, 1)), + TighteningDirection = (TighteningDirection)OpenProtocolConvert.ToInt32(value.Slice(1, 1)), + MotorRotation = (MotorRotation)OpenProtocolConvert.ToInt32(value.Slice(2, 1)), + }; + } } } diff --git a/src/OpenProtocolInterpreter/Tool/ToolData.cs b/src/OpenProtocolInterpreter/Tool/ToolData.cs index a20c562d..1246e7d0 100644 --- a/src/OpenProtocolInterpreter/Tool/ToolData.cs +++ b/src/OpenProtocolInterpreter/Tool/ToolData.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.Tool { @@ -31,6 +32,17 @@ public static ToolData Parse(string value) }; } + public static ToolData Parse(ReadOnlySpan value) + { + return new ToolData() + { + Number = OpenProtocolConvert.ToInt32(value.Slice(0, 4)), + SerialNumber = value.Slice(4, 30).ToString(), + ModelName = value.Slice(34, 30).ToString(), + ModelArticleNumber = value.Slice(64, 30).ToString() + }; + } + public static IEnumerable ParseAll(string value) { if(string.IsNullOrEmpty(value)) @@ -45,5 +57,17 @@ public static IEnumerable ParseAll(string value) yield return Parse(section); } } + + public static IEnumerable ParseAll(ReadOnlySpan value) + { + if (value.IsEmpty) + return System.Array.Empty(); + + var result = new System.Collections.Generic.List(); + const int sectionSize = 94; + for (int i = 0; i < value.Length; i += sectionSize) + result.Add(Parse(value.Slice(i, sectionSize))); + return result; + } } } diff --git a/src/OpenProtocolInterpreter/VariableDataField.cs b/src/OpenProtocolInterpreter/VariableDataField.cs index 8712e3ce..9ce1ecf3 100644 --- a/src/OpenProtocolInterpreter/VariableDataField.cs +++ b/src/OpenProtocolInterpreter/VariableDataField.cs @@ -65,6 +65,12 @@ public static VariableDataField Parse(string value) return Parse(value, length); } + public static VariableDataField Parse(ReadOnlySpan value) + { + var length = OpenProtocolConvert.ToInt32(value.Slice(5, 3)); + return Parse(value, length); + } + public static IEnumerable ParseAll(string value) { if (string.IsNullOrWhiteSpace(value)) @@ -82,6 +88,22 @@ public static IEnumerable ParseAll(string value) } } + public static IEnumerable ParseAll(ReadOnlySpan value) + { + if (value.IsWhiteSpace() || value.IsEmpty) + return System.Array.Empty(); + + var result = new System.Collections.Generic.List(); + int valueLength; + const int fixedLength = 17; + for (int i = 0; i < value.Length; i += fixedLength + valueLength) + { + valueLength = OpenProtocolConvert.ToInt32(value.Slice(i + 5, 3)); + result.Add(Parse(value.Slice(i, fixedLength + valueLength), valueLength)); + } + return result; + } + private static VariableDataField Parse(string value, int length) { return new VariableDataField() @@ -95,5 +117,18 @@ private static VariableDataField Parse(string value, int length) }; } + private static VariableDataField Parse(ReadOnlySpan value, int length) + { + return new VariableDataField() + { + ParameterId = OpenProtocolConvert.ToInt32(value.Slice(0, 5)), + Length = length, + DataType = (DataTypeDefinition)OpenProtocolConvert.ToInt32(value.Slice(8, 2)), + Unit = (DataUnitType)OpenProtocolConvert.ToInt32(value.Slice(10, 3)), + StepNumber = OpenProtocolConvert.ToInt32(value.Slice(13, 4)), + DataValue = value.Slice(17, length).ToString() + }; + } + } } diff --git a/src/OpenProtocolInterpreter/Vin/Mid0050.cs b/src/OpenProtocolInterpreter/Vin/Mid0050.cs index ddcdd338..ad20f83a 100644 --- a/src/OpenProtocolInterpreter/Vin/Mid0050.cs +++ b/src/OpenProtocolInterpreter/Vin/Mid0050.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace OpenProtocolInterpreter.Vin @@ -38,7 +39,7 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); GetField(1, DataFields.VinNumber).Size = Header.Length - 20; diff --git a/src/OpenProtocolInterpreter/Vin/Mid0052.cs b/src/OpenProtocolInterpreter/Vin/Mid0052.cs index e10244c4..c07dc2e3 100644 --- a/src/OpenProtocolInterpreter/Vin/Mid0052.cs +++ b/src/OpenProtocolInterpreter/Vin/Mid0052.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace OpenProtocolInterpreter.Vin @@ -66,7 +67,7 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(string package) + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); if (Header.Revision > 1) diff --git a/src/OpenProtocolInterpreter/_internals/SafeAccessRevisionsFields.cs b/src/OpenProtocolInterpreter/_internals/SafeAccessRevisionsFields.cs index e5a1fb83..4f8518ca 100644 --- a/src/OpenProtocolInterpreter/_internals/SafeAccessRevisionsFields.cs +++ b/src/OpenProtocolInterpreter/_internals/SafeAccessRevisionsFields.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Runtime.Serialization; namespace OpenProtocolInterpreter { @@ -42,9 +41,5 @@ public SafeAccessRevisionsFields(int capacity) : base(capacity) public SafeAccessRevisionsFields(int capacity, IEqualityComparer comparer) : base(capacity, comparer) { } - - protected SafeAccessRevisionsFields(SerializationInfo info, StreamingContext context) : base(info, context) - { - } } } diff --git a/src/OpenProtocolInterpreter/_internals/StringExtensions.cs b/src/OpenProtocolInterpreter/_internals/StringExtensions.cs index db5dadfe..05b85c11 100644 --- a/src/OpenProtocolInterpreter/_internals/StringExtensions.cs +++ b/src/OpenProtocolInterpreter/_internals/StringExtensions.cs @@ -1,4 +1,6 @@ -namespace OpenProtocolInterpreter +using System; + +namespace OpenProtocolInterpreter { public static class StringExtensions { @@ -45,5 +47,16 @@ internal static string SafeSubstring(this string value, int startIndex, int leng return value.Substring(startIndex, length); } + + internal static string SafeSubstring(this ReadOnlySpan value, int startIndex, int length) + { + if (value.IsEmpty) + return string.Empty; + + if (value.Length < startIndex + length) + return startIndex < value.Length ? value.Slice(startIndex).ToString() : string.Empty; + + return value.Slice(startIndex, length).ToString(); + } } } From 90bb4ed7b361f9d69beb32bdb23c9f8ae0aa13ef Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Thu, 2 Apr 2026 23:38:14 -0300 Subject: [PATCH 24/78] Updates on mids from revision 2.18.0 --- .../Communication/Mid0001.cs | 27 +++++++++++++--- .../Communication/Mid0002.cs | 31 +++++++++++++++--- src/OpenProtocolInterpreter/Enums/ToolType.cs | 5 ++- .../OpenProtocolConvert.cs | 32 +++++++++++++------ .../Tightening/Mid0065.cs | 30 +++++++++++++++-- src/OpenProtocolInterpreter/Tool/Mid0045.cs | 22 ++++++++++++- 6 files changed, 124 insertions(+), 23 deletions(-) diff --git a/src/OpenProtocolInterpreter/Communication/Mid0001.cs b/src/OpenProtocolInterpreter/Communication/Mid0001.cs index 5cb16c3f..0ae9e41e 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0001.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0001.cs @@ -19,6 +19,16 @@ public bool OptionalKeepAlive get => GetField(7, DataFields.UseKeepAlive).GetValue(OpenProtocolConvert.ToBoolean); set => GetField(7, DataFields.UseKeepAlive).SetValue(OpenProtocolConvert.ToString, value); } + public bool OptionalToolLockAtDisconnection + { + get => GetField(8, DataFields.OptionalToolLockAtDisconnection).GetValue(OpenProtocolConvert.ToBoolean); + set => GetField(8, DataFields.OptionalToolLockAtDisconnection).SetValue(OpenProtocolConvert.ToString, value); + } + public decimal OptionalEarlyLock + { + get => GetField(8, DataFields.OptionalEarlyLock).GetValue(x => OpenProtocolConvert.ToTruncatedDecimal(x, 1)); + set => GetField(8, DataFields.OptionalEarlyLock).SetValue((paddingChar, size, orientation, v) => OpenProtocolConvert.TruncatedDecimalToString(paddingChar, size, orientation, v, 1), value); + } public Mid0001() : this(DEFAULT_REVISION) { @@ -44,9 +54,16 @@ protected override Dictionary> RegisterDatafields() { { 7, new List() - { - DataField.Boolean(DataFields.UseKeepAlive, 20) - } + { + DataField.Boolean(DataFields.UseKeepAlive, 20) + } + }, + { + 8, new List() + { + DataField.Boolean(DataFields.OptionalToolLockAtDisconnection, 24), + DataField.Number(DataFields.OptionalEarlyLock, 26, 4) + } } }; } @@ -54,7 +71,9 @@ protected override Dictionary> RegisterDatafields() protected enum DataFields { //Rev 7 - UseKeepAlive + UseKeepAlive, + OptionalToolLockAtDisconnection, + OptionalEarlyLock } } } diff --git a/src/OpenProtocolInterpreter/Communication/Mid0002.cs b/src/OpenProtocolInterpreter/Communication/Mid0002.cs index d0d81c68..8c867aca 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0002.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0002.cs @@ -110,7 +110,7 @@ public bool LinkingHandlingSupport /// Station ID for PF6000 /// Cell ID for PF4000 /// - public long StationCellId + public long StationCellId { get => GetField(6, DataFields.StationCellId).GetValue(OpenProtocolConvert.ToInt64); set => GetField(6, DataFields.StationCellId).SetValue(OpenProtocolConvert.ToString, value); @@ -137,12 +137,23 @@ public string ClientId /// False = Use Keep alive (Keep alive is mandatory) /// True = Ignore Keep alive (Keep alive is optional) /// - public bool OptionalKeepAlive + public bool OptionalKeepAlive { get => GetField(7, DataFields.OptionalKeepAlive).GetValue(OpenProtocolConvert.ToBoolean); set => GetField(7, DataFields.OptionalKeepAlive).SetValue(OpenProtocolConvert.ToString, value); } + public bool OptionalToolLockAtDisconnection + { + get => GetField(8, DataFields.OptionalToolLockAtDisconnection).GetValue(OpenProtocolConvert.ToBoolean); + set => GetField(8, DataFields.OptionalToolLockAtDisconnection).SetValue(OpenProtocolConvert.ToString, value); + } + public decimal OptionalEarlyLock + { + get => GetField(8, DataFields.OptionalEarlyLock).GetValue(x => OpenProtocolConvert.ToTruncatedDecimal(x, 1)); + set => GetField(8, DataFields.OptionalEarlyLock).SetValue((paddingChar, size, orientation, v) => OpenProtocolConvert.TruncatedDecimalToString(paddingChar, size, orientation, v, 1), value); + } + public Mid0002() : this(DEFAULT_REVISION) { @@ -155,11 +166,11 @@ public Mid0002(Header header) : base(header) public Mid0002(int revision) : this(new Header() { - Mid= MID, + Mid = MID, Revision = revision }) { - + } protected override Dictionary> RegisterDatafields() @@ -217,6 +228,13 @@ protected override Dictionary> RegisterDatafields() { DataField.Boolean(DataFields.OptionalKeepAlive, 221) } + }, + { + 8, new List() + { + DataField.Boolean(DataFields.OptionalToolLockAtDisconnection, 224), + DataField.Number(DataFields.OptionalEarlyLock, 227, 4) + } } }; } @@ -246,7 +264,10 @@ protected enum DataFields StationCellName, ClientId, //Rev 7 - OptionalKeepAlive + OptionalKeepAlive, + //Rev 8 + OptionalToolLockAtDisconnection, + OptionalEarlyLock } } } diff --git a/src/OpenProtocolInterpreter/Enums/ToolType.cs b/src/OpenProtocolInterpreter/Enums/ToolType.cs index b0d722c2..e8d84056 100644 --- a/src/OpenProtocolInterpreter/Enums/ToolType.cs +++ b/src/OpenProtocolInterpreter/Enums/ToolType.cs @@ -38,6 +38,9 @@ public enum ToolType ITP = 30, QSHIELD_C = 31, DeltaWrench = 32, - STRWrench = 33 + STRWrench = 33, + XPBM = 34, + ExBC = 35, + ExD = 36 } } diff --git a/src/OpenProtocolInterpreter/OpenProtocolConvert.cs b/src/OpenProtocolInterpreter/OpenProtocolConvert.cs index c87cde19..11258a10 100644 --- a/src/OpenProtocolInterpreter/OpenProtocolConvert.cs +++ b/src/OpenProtocolInterpreter/OpenProtocolConvert.cs @@ -76,8 +76,12 @@ public static decimal ToDecimal(string value) } public static string TruncatedDecimalToString(decimal value) + => TruncatedDecimalToString(value, 2); + + public static string TruncatedDecimalToString(decimal value, int decimalDigits) { - int convertedValue = (int)(Math.Round(value, 2) * 100m); + var decimals = 10m * decimalDigits; + int convertedValue = (int)(Math.Round(value, 2) * decimals); return convertedValue.ToString(); } @@ -87,16 +91,26 @@ public static string TruncatedDecimalToString(char paddingChar, int size, Paddin return TruncatePadded(paddingChar, size, orientation, str); } - public static decimal ToTruncatedDecimal(string value) + public static string TruncatedDecimalToString(char paddingChar, int size, PaddingOrientation orientation, decimal value, int decimalDigits) { - int intValue = ToInt32(value); - return intValue / 100m; + var str = TruncatedDecimalToString(value, decimalDigits); + return TruncatePadded(paddingChar, size, orientation, str); } + public static decimal ToTruncatedDecimal(string value) + => ToTruncatedDecimal(value, 2); + + public static decimal ToTruncatedDecimal(string value, int decimalDigits) + => ToTruncatedDecimal(value.AsSpan(), 2); + public static decimal ToTruncatedDecimal(ReadOnlySpan value) + => ToTruncatedDecimal(value, 2); + + public static decimal ToTruncatedDecimal(ReadOnlySpan value, int decimalDigits) { + var decimals = 10m * decimalDigits; int intValue = ToInt32(value); - return intValue / 100m; + return intValue / decimals; } public static string ToString(int value) @@ -120,11 +134,11 @@ public static int ToInt32(string value) public static int ToInt32(ReadOnlySpan value) { #if NETSTANDARD2_0 - int.TryParse(value.ToString(), out int convertedValue); + return ToInt32(value.ToString()); #else int.TryParse(value, out int convertedValue); -#endif return convertedValue; +#endif } public static string ToString(long value) @@ -142,11 +156,11 @@ public static long ToInt64(string value) public static long ToInt64(ReadOnlySpan value) { #if NETSTANDARD2_0 - long.TryParse(value.ToString(), out long convertedValue); + return ToInt64(value.ToString()); #else long.TryParse(value, out long convertedValue); -#endif return convertedValue; +#endif } public static string ToString(IEnumerable value) diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0065.cs b/src/OpenProtocolInterpreter/Tightening/Mid0065.cs index 6e9582ef..fd0b4f47 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0065.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0065.cs @@ -298,6 +298,8 @@ public int ClickAngle set => GetField(11, DataFields.ClickAngle).SetValue(OpenProtocolConvert.ToString, value); } + public List StageResults { get; set; } + public Mid0065() : this(DEFAULT_REVISION) { @@ -373,8 +375,17 @@ protected override void ProcessDataFields(ReadOnlySpan package) } else { - int processUntil = Header.Revision; - for (int revision = 2; revision <= processUntil; revision++) + int processUntilRevision = Header.Revision; + if (Header.Revision == 998) + { + processUntilRevision = 6; + var stageResultField = GetField(998, DataFields.StageResult); + stageResultField.Size = Header.Length - stageResultField.Index - 2; + ProcessDataFields(998, package); + StageResults = StageResult.ParseAll(stageResultField.Value).ToList(); + } + + for (int revision = 2; revision <= processUntilRevision; revision++) ProcessDataFields(revision, package); var strategyOptionsField = GetField(2, DataFields.StrategyOptions); @@ -518,6 +529,15 @@ protected override Dictionary> RegisterDatafields() DataField.Number(DataFields.ClickTorque, 497, 6), DataField.Number(DataFields.ClickAngle, 505, 5), } + }, + { + //Revision 998 is a continuation of Revision 6. + 998, new List() + { + DataField.Number(DataFields.NumberOfStagesInMultistage, 340, 2), + DataField.Number(DataFields.NumberOfStageResults, 344, 2), + new(DataFields.StageResult, 348, 11) + } } }; } @@ -592,7 +612,11 @@ protected enum DataFields RundownAngleComplete, //Rev 11 ClickTorque, - ClickAngle + ClickAngle, + //Rev 998 (Go over rev 7) + NumberOfStagesInMultistage, + NumberOfStageResults, + StageResult } } } diff --git a/src/OpenProtocolInterpreter/Tool/Mid0045.cs b/src/OpenProtocolInterpreter/Tool/Mid0045.cs index 7cca1a85..2e494ec9 100644 --- a/src/OpenProtocolInterpreter/Tool/Mid0045.cs +++ b/src/OpenProtocolInterpreter/Tool/Mid0045.cs @@ -35,6 +35,17 @@ public int ChannelNumber set => GetField(2, DataFields.ChannelNumber).SetValue(OpenProtocolConvert.ToString, value); } + public decimal ExtendedCalibrationValue + { + get => GetField(3, DataFields.ExtendedCalibrationValue).GetValue(x => OpenProtocolConvert.ToTruncatedDecimal(x, 4)); + set => GetField(3, DataFields.ExtendedCalibrationValue).SetValue((paddingChar, size, orientation, v) => OpenProtocolConvert.TruncatedDecimalToString(paddingChar, size, orientation, v, 4), value); + } + public int TransducerNumber + { + get => GetField(3, DataFields.TransducerNumber).GetValue(OpenProtocolConvert.ToInt32); + set => GetField(3, DataFields.TransducerNumber).SetValue(OpenProtocolConvert.ToString, value); + } + public Mid0045() : this(DEFAULT_REVISION) { } @@ -67,6 +78,13 @@ protected override Dictionary> RegisterDatafields() { DataField.Number(DataFields.ChannelNumber, 31, 2), } + }, + { + 3, new List() + { + DataField.Number(DataFields.ExtendedCalibrationValue, 35, 10), + DataField.Number(DataFields.TransducerNumber, 47, 1) + } } }; } @@ -75,7 +93,9 @@ protected enum DataFields { CalibrationValueUnit, CalibrationValue, - ChannelNumber + ChannelNumber, + ExtendedCalibrationValue, + TransducerNumber } } } From 073c201c311b4ea2f0e9e4f1a09c6ab407edbfa8 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Fri, 3 Apr 2026 00:02:17 -0300 Subject: [PATCH 25/78] updates on mids from revision 2.20.X --- src/OpenProtocolInterpreter/Job/Mid0035.cs | 17 +++++++++- .../ParameterSet/Mid0015.cs | 21 +++++++++++- .../Tightening/Mid0061.cs | 22 +++++++++++++ .../Tightening/Mid0065.cs | 33 ++++++++++++++++++- 4 files changed, 90 insertions(+), 3 deletions(-) diff --git a/src/OpenProtocolInterpreter/Job/Mid0035.cs b/src/OpenProtocolInterpreter/Job/Mid0035.cs index ebbdb03f..a390d6ee 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0035.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0035.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Dynamic; namespace OpenProtocolInterpreter.Job { @@ -97,6 +98,11 @@ public string IdentifierResultPart4 get => GetField(5, DataFields.IdentifierResultPart4).Value; set => GetField(5, DataFields.IdentifierResultPart4).SetValue(value); } + public string JointId + { + get => GetField(6, DataFields.JointId).Value; + set => GetField(6, DataFields.JointId).SetValue(value); + } public Mid0035() : this(DEFAULT_REVISION) { @@ -168,7 +174,14 @@ protected override Dictionary> RegisterDatafields() DataField.String(DataFields.IdentifierResultPart3, 144, 25), DataField.String(DataFields.IdentifierResultPart4, 171, 25), } + }, + { + 6, new List() + { + DataField.String(DataFields.JointId, 198, 25) + } } + }; } @@ -213,7 +226,9 @@ protected enum DataFields VinNumber, IdentifierResultPart2, IdentifierResultPart3, - IdentifierResultPart4 + IdentifierResultPart4, + //Rev 6 + JointId } } diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid0015.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid0015.cs index e980ed50..4ecfaa5b 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid0015.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid0015.cs @@ -84,6 +84,16 @@ public decimal StartFinalAngle get => GetField(2, DataFields.StartFinalAngle).GetValue(OpenProtocolConvert.ToTruncatedDecimal); set => GetField(2, DataFields.StartFinalAngle).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); } + public int SelectedIdentifierNumber + { + get => GetField(3, DataFields.SelectedIdentifierNumber).GetValue(OpenProtocolConvert.ToInt32); + set => GetField(3, DataFields.SelectedIdentifierNumber).SetValue(OpenProtocolConvert.ToString, value); + } + public string JointId + { + get => GetField(3, DataFields.JointId).Value; + set => GetField(3, DataFields.JointId).SetValue(value); + } public Mid0015() : this(DEFAULT_REVISION) { @@ -156,6 +166,13 @@ protected override Dictionary> RegisterDatafields() DataField.Number(DataFields.FirstTarget, 125, 6), DataField.Number(DataFields.StartFinalAngle, 133, 6) } + }, + { + 3, new List() + { + DataField.Number(DataFields.SelectedIdentifierNumber, 141, 4), + DataField.String(DataFields.JointId, 147, 25) + } } }; } @@ -175,7 +192,9 @@ protected enum DataFields AngleMax, FinalAngleTarget, FirstTarget, - StartFinalAngle + StartFinalAngle, + SelectedIdentifierNumber, + JointId } } } diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0061.cs b/src/OpenProtocolInterpreter/Tightening/Mid0061.cs index d50d56ce..bd0aedf8 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0061.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0061.cs @@ -393,6 +393,17 @@ public int ClickAngle get => GetField(11, DataFields.ClickAngle).GetValue(OpenProtocolConvert.ToInt32); set => GetField(11, DataFields.ClickAngle).SetValue(OpenProtocolConvert.ToString, value); } + //Rev 12 + public int SelectedIdentifierNumber + { + get => GetField(12, DataFields.SelectedIdentifierNumber).GetValue(OpenProtocolConvert.ToInt32); + set => GetField(12, DataFields.SelectedIdentifierNumber).SetValue(OpenProtocolConvert.ToString, value); + } + public string JointId + { + get => GetField(12, DataFields.JointId).Value; + set => GetField(12, DataFields.JointId).SetValue(value); + } //Rev 998 addition public int NumberOfStagesInMultistage { @@ -406,6 +417,7 @@ public int NumberOfStageResults } public List StageResults { get; set; } + public Mid0061() : this(DEFAULT_REVISION) { @@ -701,6 +713,13 @@ protected override Dictionary> RegisterDatafields() DataField.Number(DataFields.ClickAngle, 670, 5), } }, + { + 12, new List() + { + DataField.Number(DataFields.SelectedIdentifierNumber, 677, 4), + DataField.String(DataFields.JointId, 683, 25) + } + }, { 998, new List() { @@ -835,6 +854,9 @@ protected enum DataFields //Rev 11 ClickTorque, ClickAngle, + //Rev 12 + SelectedIdentifierNumber, + JointId, //Rev 998 (Go over rev 7) NumberOfStagesInMultistage, NumberOfStageResults, diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0065.cs b/src/OpenProtocolInterpreter/Tightening/Mid0065.cs index fd0b4f47..34fb1afc 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0065.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0065.cs @@ -297,7 +297,28 @@ public int ClickAngle get => GetField(11, DataFields.ClickAngle).GetValue(OpenProtocolConvert.ToInt32); set => GetField(11, DataFields.ClickAngle).SetValue(OpenProtocolConvert.ToString, value); } - + //Rev 12 + public int SelectedIdentifierNumber + { + get => GetField(12, DataFields.SelectedIdentifierNumber).GetValue(OpenProtocolConvert.ToInt32); + set => GetField(12, DataFields.SelectedIdentifierNumber).SetValue(OpenProtocolConvert.ToString, value); + } + public string JointId + { + get => GetField(12, DataFields.JointId).Value; + set => GetField(12, DataFields.JointId).SetValue(value); + } + //Rev 998 addition + public int NumberOfStagesInMultistage + { + get => GetField(998, DataFields.NumberOfStagesInMultistage).GetValue(OpenProtocolConvert.ToInt32); + set => GetField(998, DataFields.NumberOfStagesInMultistage).SetValue(OpenProtocolConvert.ToString, value); + } + public int NumberOfStageResults + { + get => GetField(998, DataFields.NumberOfStageResults).GetValue(OpenProtocolConvert.ToInt32); + set => GetField(998, DataFields.NumberOfStageResults).SetValue(OpenProtocolConvert.ToString, value); + } public List StageResults { get; set; } public Mid0065() : this(DEFAULT_REVISION) @@ -530,6 +551,13 @@ protected override Dictionary> RegisterDatafields() DataField.Number(DataFields.ClickAngle, 505, 5), } }, + { + 12, new List() + { + DataField.Number(DataFields.SelectedIdentifierNumber, 512, 4), + DataField.String(DataFields.JointId, 518, 25) + } + }, { //Revision 998 is a continuation of Revision 6. 998, new List() @@ -613,6 +641,9 @@ protected enum DataFields //Rev 11 ClickTorque, ClickAngle, + //Rev 12 + SelectedIdentifierNumber, + JointId, //Rev 998 (Go over rev 7) NumberOfStagesInMultistage, NumberOfStageResults, From 65c9576ac9309042cb4784519715691009da8bb6 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Tue, 21 Apr 2026 11:41:16 -0300 Subject: [PATCH 26/78] Added BenchmarkDotNet --- .gitignore | 1 + src/MIDTesters.Core/MIDTesters.Core.csproj | 1 + src/MIDTesters.Core/MIDsTests.cs | 84 +++++++++++----------- src/MIDTesters.Core/Program.cs | 4 ++ 4 files changed, 49 insertions(+), 41 deletions(-) create mode 100644 src/MIDTesters.Core/Program.cs diff --git a/.gitignore b/.gitignore index 38077a5a..92156c41 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ publishprofiles/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* +BenchmarkDotNet.Artifacts/ # NUNIT *.VisualState.xml diff --git a/src/MIDTesters.Core/MIDTesters.Core.csproj b/src/MIDTesters.Core/MIDTesters.Core.csproj index f985dc5a..293c01c7 100644 --- a/src/MIDTesters.Core/MIDTesters.Core.csproj +++ b/src/MIDTesters.Core/MIDTesters.Core.csproj @@ -7,6 +7,7 @@ + diff --git a/src/MIDTesters.Core/MIDsTests.cs b/src/MIDTesters.Core/MIDsTests.cs index b3092e6d..b51b91a1 100644 --- a/src/MIDTesters.Core/MIDsTests.cs +++ b/src/MIDTesters.Core/MIDsTests.cs @@ -1,26 +1,48 @@ -using OpenProtocolInterpreter; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Running; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.Communication; using OpenProtocolInterpreter.MotorTuning; using OpenProtocolInterpreter.Tightening; using System; -using System.Diagnostics; namespace MIDTesters { - //[Microsoft.VisualStudio.TestTools.UnitTesting.TestClass] + [MemoryDiagnoser] public class MIDsTests { - //[Microsoft.VisualStudio.TestTools.UnitTesting.TestMethod] - public void TestProcessingTime() + private const string Mid61Raw = + "02310061001 010001020103airbag7 04KPOL3456JKLO897 " + + "05000600307000008000009010011112000840130014001400120015000739160000017099991800000" + + "1900000202001-06-02:09:54:09212001-05-29:12:34:3322123345675 "; + + private MidInterpreter _customMidInterpreter = null!; + private MidInterpreter _allMidInterpreter = null!; + + [GlobalSetup] + public void Setup() { - long total = 0; - string mid61 = "02310061001 010001020103airbag7 04KPOL3456JKLO897 " + - "05000600307000008000009010011112000840130014001400120015000739160000017099991800000" + - "1900000202001-06-02:09:54:09212001-05-29:12:34:3322123345675 "; - //CustomMids - var watch = Stopwatch.StartNew(); - var myTEmplate = new MidInterpreter() + _customMidInterpreter = new MidInterpreter() .UseAllMessages(new Type[] + { + typeof(Mid0001), + typeof(Mid0002), + typeof(Mid0003), + typeof(Mid0004), + typeof(Mid0061), + typeof(Mid0500), + typeof(Mid0501), + typeof(Mid0502), + typeof(Mid0503), + typeof(Mid0504) + }); + + _allMidInterpreter = new MidInterpreter().UseAllMessages(); + } + + [Benchmark(Description = "Build MidInterpreter (custom MIDs)")] + public MidInterpreter BuildCustomMidInterpreter() => + new MidInterpreter().UseAllMessages(new Type[] { typeof(Mid0001), typeof(Mid0002), @@ -33,37 +55,17 @@ public void TestProcessingTime() typeof(Mid0503), typeof(Mid0504) }); - watch.Stop(); - Debug.WriteLine("[CustomMIDs] Elapsed time to construct MidInterpreter: " + new TimeSpan(watch.ElapsedTicks)); - for (int i = 0; i < 1000000; i++) - { - - watch.Restart(); - var myMid106 = myTEmplate.Parse(mid61); - watch.Stop(); - total += watch.ElapsedTicks; - } - Debug.WriteLine($"[CustomMIDs] Total Elapsed: " + new TimeSpan(total)); - Debug.WriteLine($"[CustomMIDs] Average Elapsed Time: " + new TimeSpan(total / 1000000)); + [Benchmark(Description = "Build MidInterpreter (all MIDs)")] + public MidInterpreter BuildAllMidInterpreter() => + new MidInterpreter().UseAllMessages(); - //All MIDs - watch.Start(); - myTEmplate = new MidInterpreter().UseAllMessages(); - watch.Stop(); - Debug.WriteLine("[AllMIDs] Elapsed time to construct MidInterpreter: " + new TimeSpan(watch.ElapsedTicks)); + [Benchmark(Description = "Parse Mid0061 (custom MIDs)")] + public Mid0061 ParseMid61CustomInterpreter() => + _customMidInterpreter.Parse(Mid61Raw); - total = 0; - for (int i = 0; i < 1000000; i++) - { - watch.Restart(); - var myMid500 = myTEmplate.Parse(mid61); - watch.Stop(); - total += watch.ElapsedTicks; - } - - Debug.WriteLine($"[AllMIDs] Total Elapsed: " + new TimeSpan(total)); - Debug.WriteLine($"[AllMIDs] Average Elapsed Time: " + new TimeSpan(total / 1000000)); - } + [Benchmark(Description = "Parse Mid0061 (all MIDs)", Baseline = true)] + public Mid0061 ParseMid61AllInterpreter() => + _allMidInterpreter.Parse(Mid61Raw); } } diff --git a/src/MIDTesters.Core/Program.cs b/src/MIDTesters.Core/Program.cs new file mode 100644 index 00000000..a4caf239 --- /dev/null +++ b/src/MIDTesters.Core/Program.cs @@ -0,0 +1,4 @@ +using BenchmarkDotNet.Running; +using MIDTesters; + +BenchmarkRunner.Run(); From aaa4f8b513fde98caa3a10601d8a127d60618cd0 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Tue, 21 Apr 2026 11:58:33 -0300 Subject: [PATCH 27/78] performance tests --- src/MIDTesters.Core/MIDsTests.cs | 34 ++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/MIDTesters.Core/MIDsTests.cs b/src/MIDTesters.Core/MIDsTests.cs index b51b91a1..cd724e1b 100644 --- a/src/MIDTesters.Core/MIDsTests.cs +++ b/src/MIDTesters.Core/MIDsTests.cs @@ -1,5 +1,6 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; +using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter; using OpenProtocolInterpreter.Communication; using OpenProtocolInterpreter.MotorTuning; @@ -40,7 +41,7 @@ public void Setup() _allMidInterpreter = new MidInterpreter().UseAllMessages(); } - [Benchmark(Description = "Build MidInterpreter (custom MIDs)")] + //[Benchmark(Description = "Build MidInterpreter (custom MIDs)")] public MidInterpreter BuildCustomMidInterpreter() => new MidInterpreter().UseAllMessages(new Type[] { @@ -56,16 +57,41 @@ public MidInterpreter BuildCustomMidInterpreter() => typeof(Mid0504) }); - [Benchmark(Description = "Build MidInterpreter (all MIDs)")] + //[Benchmark(Description = "Build MidInterpreter (all MIDs)")] public MidInterpreter BuildAllMidInterpreter() => new MidInterpreter().UseAllMessages(); - [Benchmark(Description = "Parse Mid0061 (custom MIDs)")] + //[Benchmark(Description = "Parse Mid0061 (custom MIDs)")] public Mid0061 ParseMid61CustomInterpreter() => _customMidInterpreter.Parse(Mid61Raw); - [Benchmark(Description = "Parse Mid0061 (all MIDs)", Baseline = true)] + //[Benchmark(Description = "Parse Mid0061 (all MIDs)", Baseline = true)] public Mid0061 ParseMid61AllInterpreter() => _allMidInterpreter.Parse(Mid61Raw); + + [Benchmark(Description = "Mid performance")] + public Mid MidPerformance() + { + var value = "02240002007 010001020103Airbag1 04ACT05OpenProtocolVersion06Version 19.0.0.0 07Version 01.0.0.0 08RBUType 09Serial 100021100212013114429496729515Station Or Cell Name 16A171"; + var mid = _allMidInterpreter.Parse(value); + Assert.AreEqual(1, mid.CellId); + Assert.AreEqual(1, mid.ChannelId); + Assert.AreEqual("Airbag1", mid.ControllerName.TrimEnd()); + Assert.AreEqual("ACT", mid.SupplierCode); + Assert.AreEqual("OpenProtocolVersion", mid.OpenProtocolVersion); + Assert.AreEqual("Version 19.0.0.0", mid.ControllerSoftwareVersion.TrimEnd()); + Assert.AreEqual("Version 01.0.0.0", mid.ToolSoftwareVersion.TrimEnd()); + Assert.AreEqual("RBUType", mid.RBUType.TrimEnd()); + Assert.AreEqual("Serial", mid.ControllerSerialNumber.TrimEnd()); + Assert.AreEqual(SystemType.PowerMacs4000, mid.SystemType); + Assert.AreEqual(SystemSubType.SystemRunningPresses, mid.SystemSubType); + Assert.IsFalse(mid.SequenceNumberSupport); + Assert.IsTrue(mid.LinkingHandlingSupport); + Assert.AreEqual(4294967295L, mid.StationCellId); + Assert.AreEqual("Station Or Cell Name", mid.StationCellName.TrimEnd()); + Assert.AreEqual("A", mid.ClientId); + Assert.IsTrue(mid.OptionalKeepAlive); + return mid; + } } } From 98b2614ac7b1c3adb854a151fa4a7d95313a25b1 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Tue, 30 Jun 2026 18:04:00 -0300 Subject: [PATCH 28/78] wip --- .../Communication/Mid0001.cs | 65 ++--- .../Communication/Mid0002.cs | 266 +++++++----------- src/OpenProtocolInterpreter/DataField.cs | 203 +++++++++++-- .../DataFieldDefinitionAttribute.cs | 124 ++++++++ src/OpenProtocolInterpreter/MID.cs | 70 ++++- 5 files changed, 503 insertions(+), 225 deletions(-) create mode 100644 src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs diff --git a/src/OpenProtocolInterpreter/Communication/Mid0001.cs b/src/OpenProtocolInterpreter/Communication/Mid0001.cs index 0ae9e41e..d1fbae32 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0001.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0001.cs @@ -14,21 +14,14 @@ public class Mid0001 : Mid, ICommunication, IIntegrator, IAnswerableBy, public IEnumerable DocumentedPossibleErrors => new Error[] { Error.ClientAlreadyConnected, Error.MidRevisionUnsupported }; - public bool OptionalKeepAlive - { - get => GetField(7, DataFields.UseKeepAlive).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(7, DataFields.UseKeepAlive).SetValue(OpenProtocolConvert.ToString, value); - } - public bool OptionalToolLockAtDisconnection - { - get => GetField(8, DataFields.OptionalToolLockAtDisconnection).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(8, DataFields.OptionalToolLockAtDisconnection).SetValue(OpenProtocolConvert.ToString, value); - } - public decimal OptionalEarlyLock - { - get => GetField(8, DataFields.OptionalEarlyLock).GetValue(x => OpenProtocolConvert.ToTruncatedDecimal(x, 1)); - set => GetField(8, DataFields.OptionalEarlyLock).SetValue((paddingChar, size, orientation, v) => OpenProtocolConvert.TruncatedDecimalToString(paddingChar, size, orientation, v, 1), value); - } + [BooleanDataFieldDefinition(id: 0, revision: 7)] + public bool OptionalKeepAlive { get; set; } + + [BooleanDataFieldDefinition(id: 1, revision: 8)] + public bool OptionalToolLockAtDisconnection { get; set; } + + [DecimalDataFieldDefinition(id: 2, revision: 8, Size = 4)] + public decimal OptionalEarlyLock { get; set; } public Mid0001() : this(DEFAULT_REVISION) { @@ -45,28 +38,30 @@ public Mid0001(int revision) : this(new Header() Revision = revision }) { - } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 7, new List() - { - DataField.Boolean(DataFields.UseKeepAlive, 20) - } - }, - { - 8, new List() - { - DataField.Boolean(DataFields.OptionalToolLockAtDisconnection, 24), - DataField.Number(DataFields.OptionalEarlyLock, 26, 4) - } - } - }; - } + // protected override Dictionary> RegisterDatafields() + // { + // var fields = new Dictionary>(); + // if (Header.Revision >= 7) + // { + // fields.Add(7, + // [ + // DataField.Boolean(DataFields.UseKeepAlive, 20).Bind(this, nameof(OptionalKeepAlive)) + // ]); + // } + + // if (Header.Revision >= 8) + // { + // fields.Add(8, + // [ + // DataField.Boolean(DataFields.OptionalToolLockAtDisconnection, 24).Bind(this, nameof(OptionalToolLockAtDisconnection)), + // DataField.Decimal(DataFields.OptionalEarlyLock, 26, 4).Bind(this, nameof(OptionalEarlyLock)) + // ]); + // } + + // return fields; + // } protected enum DataFields { diff --git a/src/OpenProtocolInterpreter/Communication/Mid0002.cs b/src/OpenProtocolInterpreter/Communication/Mid0002.cs index 8c867aca..f867b59c 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0002.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0002.cs @@ -16,68 +16,39 @@ public class Mid0002 : Mid, ICommunication, IController { public const int MID = 2; - public int CellId - { - get => GetField(1, DataFields.CellId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.CellId).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(id: 0, revision: 1, Size = 4)] + public int CellId { get; set; } - public int ChannelId - { - get => GetField(1, DataFields.ChannelId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.ChannelId).SetValue(OpenProtocolConvert.ToString, value); - } - public string ControllerName - { - get => GetField(1, DataFields.ControllerName).Value; - set => GetField(1, DataFields.ControllerName).SetValue(value); - } + [Int32DataFieldDefinition(id: 1, revision: 1, Size = 2)] + public int ChannelId { get; set; } + + [StringDataFieldDefinition(id: 2, revision: 1, Size = 25)] + public string ControllerName { get; set; } //Rev 2 - public string SupplierCode - { - get => GetField(2, DataFields.SupplierCode).Value; - set => GetField(2, DataFields.SupplierCode).SetValue(value); - } + [StringDataFieldDefinition(id: 3, revision: 2, Size = 3)] + public string SupplierCode { get; set; } //Rev 3 - public string OpenProtocolVersion - { - get => GetField(3, DataFields.OpenProtocolVersion).Value; - set => GetField(3, DataFields.OpenProtocolVersion).SetValue(value); - } + [StringDataFieldDefinition(id: 4, revision: 3, Size = 19)] + public string OpenProtocolVersion { get; set; } - public string ControllerSoftwareVersion - { - get => GetField(3, DataFields.ControllerSoftwareVersion).Value; - set => GetField(3, DataFields.ControllerSoftwareVersion).SetValue(value); - } + [StringDataFieldDefinition(id: 5, revision: 3, Size = 19)] + public string ControllerSoftwareVersion { get; set; } - public string ToolSoftwareVersion - { - get => GetField(3, DataFields.ToolSoftwareVersion).Value; - set => GetField(3, DataFields.ToolSoftwareVersion).SetValue(value); - } + [StringDataFieldDefinition(id: 6, revision: 3, Size = 19)] + public string ToolSoftwareVersion { get; set; } //Rev 4 - public string RBUType - { - get => GetField(4, DataFields.RBUType).Value; - set => GetField(4, DataFields.RBUType).SetValue(value); - } + [StringDataFieldDefinition(id: 7, revision: 4, Size = 24)] + public string RBUType { get; set; } - public string ControllerSerialNumber - { - get => GetField(4, DataFields.ControllerSerialNumber).Value; - set => GetField(4, DataFields.ControllerSerialNumber).SetValue(value); - } + [StringDataFieldDefinition(id: 8, revision: 4, Size = 10)] + public string ControllerSerialNumber { get; set; } //Rev 5 - public SystemType SystemType - { - get => (SystemType)GetField(5, DataFields.SystemType).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(5, DataFields.SystemType).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(id: 9, revision: 5, Size = 3)] + public SystemType SystemType { get; set; } /// /// If no subtype exists it will be set to 000 @@ -87,72 +58,47 @@ public SystemType SystemType /// 001 = a normal tightening system /// 002 = a system running presses instead of spindles. /// - public SystemSubType SystemSubType - { - get => (SystemSubType)GetField(5, DataFields.SystemSubtype).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(5, DataFields.SystemSubtype).SetValue(OpenProtocolConvert.ToString, value); - } + + [Int32DataFieldDefinition(id: 10, revision: 5, Size = 3)] + public SystemSubType SystemSubType { get; set; } //Rev 6 - public bool SequenceNumberSupport - { - get => GetField(6, DataFields.SequenceNumberSupport).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(6, DataFields.SequenceNumberSupport).SetValue(OpenProtocolConvert.ToString, value); - } + [BooleanDataFieldDefinition(id: 11, revision: 6)] + public bool SequenceNumberSupport { get; set; } - public bool LinkingHandlingSupport - { - get => GetField(6, DataFields.LinkingHandlingSupport).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(6, DataFields.LinkingHandlingSupport).SetValue(OpenProtocolConvert.ToString, value); - } + [BooleanDataFieldDefinition(id: 12, revision: 6)] + public bool LinkingHandlingSupport { get; set; } /// /// Station ID for PF6000 /// Cell ID for PF4000 /// - public long StationCellId - { - get => GetField(6, DataFields.StationCellId).GetValue(OpenProtocolConvert.ToInt64); - set => GetField(6, DataFields.StationCellId).SetValue(OpenProtocolConvert.ToString, value); - } + [Int64DataFieldDefinition(id: 13, revision: 6, Size = 10)] + public long StationCellId { get; set; } /// /// Station ID for PF6000 /// Cell ID for PF4000 /// - public string StationCellName - { - get => GetField(6, DataFields.StationCellName).Value; - set => GetField(6, DataFields.StationCellName).SetValue(value); - } + [StringDataFieldDefinition(id: 14, revision: 6, Size = 25)] + public string StationCellName { get; set; } - public string ClientId - { - get => GetField(6, DataFields.ClientId).Value; - set => GetField(6, DataFields.ClientId).SetValue(value); - } + [StringDataFieldDefinition(id: 15, revision: 6, Size = 1)] + public string ClientId { get; set; } //Rev 7 /// /// False = Use Keep alive (Keep alive is mandatory) /// True = Ignore Keep alive (Keep alive is optional) /// - public bool OptionalKeepAlive - { - get => GetField(7, DataFields.OptionalKeepAlive).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(7, DataFields.OptionalKeepAlive).SetValue(OpenProtocolConvert.ToString, value); - } + [BooleanDataFieldDefinition(id: 16, revision: 7)] + public bool OptionalKeepAlive { get; set; } - public bool OptionalToolLockAtDisconnection - { - get => GetField(8, DataFields.OptionalToolLockAtDisconnection).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(8, DataFields.OptionalToolLockAtDisconnection).SetValue(OpenProtocolConvert.ToString, value); - } - public decimal OptionalEarlyLock - { - get => GetField(8, DataFields.OptionalEarlyLock).GetValue(x => OpenProtocolConvert.ToTruncatedDecimal(x, 1)); - set => GetField(8, DataFields.OptionalEarlyLock).SetValue((paddingChar, size, orientation, v) => OpenProtocolConvert.TruncatedDecimalToString(paddingChar, size, orientation, v, 1), value); - } + [BooleanDataFieldDefinition(id: 17, revision: 8)] + public bool OptionalToolLockAtDisconnection { get; set; } + + [DecimalDataFieldDefinition(id: 18, revision: 8, Size = 1)] + public decimal OptionalEarlyLock { get; set; } public Mid0002() : this(DEFAULT_REVISION) { @@ -173,71 +119,71 @@ public Mid0002(int revision) : this(new Header() } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.CellId, 20, 4), - DataField.Number(DataFields.ChannelId, 26, 2), - DataField.String(DataFields.ControllerName, 30, 25) - } - }, - { - 2, new List() - { - DataField.String(DataFields.SupplierCode, 57, 3) - } - }, - { - 3, new List() - { - DataField.String(DataFields.OpenProtocolVersion, 62, 19), - DataField.String(DataFields.ControllerSoftwareVersion, 83, 19), - DataField.String(DataFields.ToolSoftwareVersion, 104, 19) - } - }, - { - 4, new List() - { - DataField.String(DataFields.RBUType, 125, 24), - DataField.String(DataFields.ControllerSerialNumber, 151, 10) - } - }, - { - 5, new List() - { - DataField.Number(DataFields.SystemType, 163, 3), - DataField.Number(DataFields.SystemSubtype, 168, 3) - } - }, - { - 6, new List() - { - DataField.Boolean(DataFields.SequenceNumberSupport, 173), - DataField.Boolean(DataFields.LinkingHandlingSupport, 176), - DataField.Number(DataFields.StationCellId, 179, 10), - DataField.String(DataFields.StationCellName, 191, 25), - DataField.String(DataFields.ClientId, 218, 1) - } - }, - { - 7, new List() - { - DataField.Boolean(DataFields.OptionalKeepAlive, 221) - } - }, - { - 8, new List() - { - DataField.Boolean(DataFields.OptionalToolLockAtDisconnection, 224), - DataField.Number(DataFields.OptionalEarlyLock, 227, 4) - } - } - }; - } + // protected override Dictionary> RegisterDatafields() + // { + // return new Dictionary>() + // { + // { + // 1, new List() + // { + // DataField.Number(DataFields.CellId, 20, 4), + // DataField.Number(DataFields.ChannelId, 26, 2), + // DataField.String(DataFields.ControllerName, 30, 25) + // } + // }, + // { + // 2, new List() + // { + // DataField.String(DataFields.SupplierCode, 57, 3) + // } + // }, + // { + // 3, new List() + // { + // DataField.String(DataFields.OpenProtocolVersion, 62, 19), + // DataField.String(DataFields.ControllerSoftwareVersion, 83, 19), + // DataField.String(DataFields.ToolSoftwareVersion, 104, 19) + // } + // }, + // { + // 4, new List() + // { + // DataField.String(DataFields.RBUType, 125, 24), + // DataField.String(DataFields.ControllerSerialNumber, 151, 10) + // } + // }, + // { + // 5, new List() + // { + // DataField.Number(DataFields.SystemType, 163, 3), + // DataField.Number(DataFields.SystemSubtype, 168, 3) + // } + // }, + // { + // 6, new List() + // { + // DataField.Boolean(DataFields.SequenceNumberSupport, 173), + // DataField.Boolean(DataFields.LinkingHandlingSupport, 176), + // DataField.Number(DataFields.StationCellId, 179, 10), + // DataField.String(DataFields.StationCellName, 191, 25), + // DataField.String(DataFields.ClientId, 218, 1) + // } + // }, + // { + // 7, new List() + // { + // DataField.Boolean(DataFields.OptionalKeepAlive, 221) + // } + // }, + // { + // 8, new List() + // { + // DataField.Boolean(DataFields.OptionalToolLockAtDisconnection, 224), + // DataField.Number(DataFields.OptionalEarlyLock, 227, 4) + // } + // } + // }; + // } protected enum DataFields { diff --git a/src/OpenProtocolInterpreter/DataField.cs b/src/OpenProtocolInterpreter/DataField.cs index b660a073..e8a26c75 100644 --- a/src/OpenProtocolInterpreter/DataField.cs +++ b/src/OpenProtocolInterpreter/DataField.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; namespace OpenProtocolInterpreter { @@ -8,8 +9,8 @@ namespace OpenProtocolInterpreter /// public class DataField { - private readonly char _paddingChar; - private readonly PaddingOrientation _paddingOrientation; + protected readonly char PaddingChar; + protected readonly PaddingOrientation PaddingOrientation; private object CachedValue; internal static DataField Default = new(-1, -1, -1); @@ -26,7 +27,7 @@ public class DataField public DataField(int field, int index, int size, bool hasPrefix = true) : this(field, index, size, ' ', PaddingOrientation.RightPadded, hasPrefix) { - + } public DataField(Enum field, int index, int size, bool hasPrefix = true) @@ -43,8 +44,8 @@ public DataField(Enum field, int index, int size, char paddingChar, PaddingOrien public DataField(int field, int index, int size, char paddingChar, PaddingOrientation paddingOrientation = PaddingOrientation.RightPadded, bool hasPrefix = true) { - _paddingChar = paddingChar; - _paddingOrientation = paddingOrientation; + PaddingChar = paddingChar; + PaddingOrientation = paddingOrientation; HasPrefix = hasPrefix; Field = field; Index = index; @@ -74,44 +75,98 @@ public virtual T GetValue(Func converter) public virtual void SetValue(Func converter, T value) { CachedValue = null; - Value = converter(_paddingChar, Size, _paddingOrientation, value); + Value = converter(PaddingChar, Size, PaddingOrientation, value); Size = Value.Length; } public virtual void SetRawValue(Func converter, T value) { CachedValue = null; - RawValue = converter(_paddingChar, Size, _paddingOrientation, value); + RawValue = converter(PaddingChar, Size, PaddingOrientation, value); Size = RawValue.Length; } public virtual void SetValue(string value) { - CachedValue = null; SetValue(OpenProtocolConvert.TruncatePadded, value); } public virtual void SetValue(ReadOnlySpan value) => SetValue(value.ToString()); - public static DataField String(int field, int index, int size, bool hasPrefix = true) - => new(field, index, size, ' ', PaddingOrientation.RightPadded, hasPrefix); - public static DataField String(int field, int index, int size, PaddingOrientation paddingOrientation, bool hasPrefix = true) - => new(field, index, size, ' ', paddingOrientation, hasPrefix); - public static DataField String(Enum field, int index, int size, bool hasPrefix = true) - => new(field, index, size, ' ', PaddingOrientation.RightPadded, hasPrefix); - public static DataField String(Enum field, int index, int size, PaddingOrientation paddingOrientation, bool hasPrefix = true) - => new(field, index, size, ' ', paddingOrientation, hasPrefix); - - public static DataField Boolean(int field, int index, bool hasPrefix = true) - => new(field, index, 1, hasPrefix); - public static DataField Boolean(Enum field, int index, bool hasPrefix = true) - => new(field, index, 1, hasPrefix); - - public static DataField Timestamp(int field, int index, bool hasPrefix = true) - => new(field, index, 19, hasPrefix); - public static DataField Timestamp(Enum field, int index, bool hasPrefix = true) - => new(field, index, 19, hasPrefix); + public static DataField String(int field, int index, int size, bool hasPrefix = true) + => String(field, index, size, PaddingOrientation.RightPadded, hasPrefix); + public static DataField String(int field, int index, int size, PaddingOrientation paddingOrientation, bool hasPrefix = true) + { + return new DataField(field, index, size, ' ', paddingOrientation, hasPrefix) + { + DefaultConverter = (paddingChar, s, o, v) => OpenProtocolConvert.TruncatePadded(paddingChar, s, o, v?.ToString()), + DefaultParser = s => s + }; + } + + public static DataField String(Enum field, int index, int size, bool hasPrefix = true) + => String(field, index, size, PaddingOrientation.RightPadded, hasPrefix); + public static DataField String(Enum field, int index, int size, PaddingOrientation paddingOrientation, bool hasPrefix = true) + => String(field.GetHashCode(), index, size, paddingOrientation, hasPrefix); + + public static DataField Boolean(int field, int index, bool hasPrefix = true) + { + return new DataField(field, index, 1, hasPrefix) + { + DefaultConverter = OpenProtocolConvert.ToString, + DefaultParser = OpenProtocolConvert.ToBoolean + }; + } + + public static DataField Boolean(Enum field, int index, bool hasPrefix = true) + => Boolean(field.GetHashCode(), index, hasPrefix); + + public static DataField Timestamp(int field, int index, bool hasPrefix = true) + { + return new DataField(field, index, 19, hasPrefix) + { + DefaultConverter = OpenProtocolConvert.ToString, + DefaultParser = OpenProtocolConvert.ToDateTime + }; + } + public static DataField Timestamp(Enum field, int index, bool hasPrefix = true) + => Timestamp(field.GetHashCode(), index, hasPrefix); + + public static DataField Decimal(int field, int index, int size, bool hasPrefix = true) + { + return new DataField(field, index, size, '0', PaddingOrientation.LeftPadded, hasPrefix) + { + DefaultConverter = OpenProtocolConvert.ToString, + DefaultParser = OpenProtocolConvert.ToDecimal + }; + } + public static DataField Decimal(Enum field, int index, int size, bool hasPrefix = true) + => Decimal(field.GetHashCode(), index, size, hasPrefix); + + public static DataField Int32(int field, int index, int size, bool hasPrefix = true) + { + return new DataField(field, index, size, '0', PaddingOrientation.LeftPadded, hasPrefix) + { + DefaultConverter = OpenProtocolConvert.ToString, + DefaultParser = OpenProtocolConvert.ToInt32 + }; + } + + public static DataField Int32(Enum field, int index, int size, bool hasPrefix = true) + => Int32(field.GetHashCode(), index, size, hasPrefix); + + public static DataField Int64(int field, int index, int size, bool hasPrefix = true) + { + return new DataField(field, index, size, '0', PaddingOrientation.LeftPadded, hasPrefix) + { + DefaultConverter = OpenProtocolConvert.ToString, + DefaultParser = OpenProtocolConvert.ToInt64 + }; + } + + public static DataField Int64(Enum field, int index, int size, bool hasPrefix = true) + => Int64(field.GetHashCode(), index, size, hasPrefix); public static DataField Number(int field, int index, int size, bool hasPrefix = true) => new(field, index, size, '0', PaddingOrientation.LeftPadded, hasPrefix); @@ -132,4 +187,100 @@ public static DataField Volatile(Enum field, bool hasPrefix = true) private bool IsNotTypeOf() => !CachedValue.GetType().Equals(typeof(T)); } + + public class DataField : DataField + { + private Mid _owner; + private PropertyInfo _backingProperty; + protected internal Func DefaultConverter; + protected internal Func DefaultParser; + + public DataField(int field, int index, int size, bool hasPrefix = true) : base(field, index, size, hasPrefix) + { + } + + public DataField(Enum field, int index, int size, bool hasPrefix = true) : base(field, index, size, hasPrefix) + { + } + + public DataField(Enum field, int index, int size, char paddingChar, PaddingOrientation paddingOrientation = PaddingOrientation.RightPadded, bool hasPrefix = true) : base(field, index, size, paddingChar, paddingOrientation, hasPrefix) + { + } + + public DataField(int field, int index, int size, char paddingChar, PaddingOrientation paddingOrientation = PaddingOrientation.RightPadded, bool hasPrefix = true) : base(field, index, size, paddingChar, paddingOrientation, hasPrefix) + { + } + + public override void SetValue(string value) + { + base.SetValue(value); + var parsedValue = DefaultParser(value); + SetBackingPropertyValueIfBound(parsedValue); + } + + private void SetBackingPropertyValueIfBound(object value) + { + if (_backingProperty != null && _owner != null) + _backingProperty.SetValue(_owner, value); + } + + protected internal DataField Bind(Mid owner, string propertyName) + => Bind(owner, owner.GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance)); + + protected internal DataField Bind(Mid owner, PropertyInfo propertyInfo) + { + _owner = owner; + _backingProperty = propertyInfo; + return this; + } + } + + public struct DataFieldDefinition + { + public int Field { get; set; } + public int Revision { get; set; } + public int Index { get; set; } + public int Size { get; set; } + public char PaddingChar { get; set; } = ' '; + public PaddingOrientation PaddingOrientation { get; set; } = PaddingOrientation.RightPadded; + public bool HasPrefix { get; set; } = true; + public PropertyInfo BoundedPropertyInfo { get; private set; } + + public DataFieldDefinition() + { + } + + public DataFieldDefinition Bind(PropertyInfo propertyInfo) + { + BoundedPropertyInfo = propertyInfo; + return this; + } + + public override readonly int GetHashCode() + { + return HashCode.Combine(Field, Revision, Index, Size, PaddingChar, PaddingOrientation, HasPrefix); + } + } + + internal readonly record struct DataFieldMetadata + { + public int Index { get; init; } + public DataFieldDefinitionAttribute Attribute { get; init; } + public PropertyInfo Property { get; init; } + + public DataFieldMetadata(DataFieldDefinition definition) + { + + } + + public DataFieldMetadata(int index, DataFieldDefinitionAttribute attribute, PropertyInfo property) + { + Index = index; + Attribute = attribute; + Property = property; + } + + public DataField CreateAndBind(Mid mid) + => Attribute.CreateAndBind(mid, Property, Index); + } } diff --git a/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs b/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs new file mode 100644 index 00000000..4d8f7936 --- /dev/null +++ b/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs @@ -0,0 +1,124 @@ +using System; +using System.Reflection; +namespace OpenProtocolInterpreter +{ + /// + /// Defines a data field specification in a MID. + /// + /// This attribute is used to decorate properties in a MID class to define how they should be parsed and formatted in the MID message. + /// + /// + public class DataFieldDefinitionAttribute : Attribute + { + public int Field { get; set; } + public int Revision { get; set; } + public int Index { get; set; } + public int Size { get; set; } + public char PaddingChar { get; set; } = ' '; + public PaddingOrientation PaddingOrientation { get; set; } = PaddingOrientation.RightPadded; + public bool HasPrefix { get; set; } = true; + public DataFieldDefinitionAttribute(int revision) + { + Revision = revision; + } + public DataFieldDefinitionAttribute(int id, int revision) : this(revision) + { + if(id > 99 || id < 0) + { + throw new ArgumentOutOfRangeException(nameof(id), "Data field id must be between 0 and 99"); + } + Field = id; + } + internal virtual DataField CreateAndBind(Mid mid, PropertyInfo propertyInfo) + => CreateAndBind(mid, propertyInfo, Index); + internal virtual DataField CreateAndBind(Mid mid, PropertyInfo propertyInfo, int index) + { + return new DataField(Field, index, Size, PaddingChar, PaddingOrientation, HasPrefix); + } + } + public class BooleanDataFieldDefinitionAttribute : DataFieldDefinitionAttribute + { + public BooleanDataFieldDefinitionAttribute(int revision) : base(revision) + { + Size = 1; + } + public BooleanDataFieldDefinitionAttribute(int id, int revision) : base(id, revision) + { + Size = 1; + } + internal override DataField CreateAndBind(Mid mid, PropertyInfo propertyInfo, int index) + { + return DataField.Boolean(Field, index, HasPrefix) + .Bind(mid, propertyInfo); + } + } + public class StringDataFieldDefinitionAttribute : DataFieldDefinitionAttribute + { + public StringDataFieldDefinitionAttribute(int revision) : base(revision) + { + } + public StringDataFieldDefinitionAttribute(int id, int revision) : base(id, revision) + { + + } + internal override DataField CreateAndBind(Mid mid, PropertyInfo propertyInfo, int index) + { + return DataField.String(Field, index, Size, PaddingOrientation, HasPrefix) + .Bind(mid, propertyInfo); + } + } + public class Int32DataFieldDefinitionAttribute : DataFieldDefinitionAttribute + { + public Int32DataFieldDefinitionAttribute(int revision) : base(revision) + { + PaddingChar = '0'; + PaddingOrientation = PaddingOrientation.LeftPadded; + } + public Int32DataFieldDefinitionAttribute(int id, int revision) : base(id, revision) + { + PaddingChar = '0'; + PaddingOrientation = PaddingOrientation.LeftPadded; + } + internal override DataField CreateAndBind(Mid mid, PropertyInfo propertyInfo, int index) + { + return DataField.Int32(Field, index, Size, HasPrefix) + .Bind(mid, propertyInfo); + } + } + public class Int64DataFieldDefinitionAttribute : DataFieldDefinitionAttribute + { + public Int64DataFieldDefinitionAttribute(int revision) : base(revision) + { + PaddingChar = '0'; + PaddingOrientation = PaddingOrientation.LeftPadded; + } + public Int64DataFieldDefinitionAttribute(int id, int revision) : base(id, revision) + { + PaddingChar = '0'; + PaddingOrientation = PaddingOrientation.LeftPadded; + } + internal override DataField CreateAndBind(Mid mid, PropertyInfo propertyInfo, int index) + { + return DataField.Int64(Field, index, Size, HasPrefix) + .Bind(mid, propertyInfo); + } + } + public class DecimalDataFieldDefinitionAttribute : DataFieldDefinitionAttribute + { + public DecimalDataFieldDefinitionAttribute(int revision) : base(revision) + { + PaddingChar = '0'; + PaddingOrientation = PaddingOrientation.LeftPadded; + } + public DecimalDataFieldDefinitionAttribute(int id, int revision) : base(id, revision) + { + PaddingChar = '0'; + PaddingOrientation = PaddingOrientation.LeftPadded; + } + internal override DataField CreateAndBind(Mid mid, PropertyInfo propertyInfo, int index) + { + return DataField.Decimal(Field, index, Size, HasPrefix) + .Bind(mid, propertyInfo); + } + } +} diff --git a/src/OpenProtocolInterpreter/MID.cs b/src/OpenProtocolInterpreter/MID.cs index d1abec32..9578d85c 100644 --- a/src/OpenProtocolInterpreter/MID.cs +++ b/src/OpenProtocolInterpreter/MID.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Text; namespace OpenProtocolInterpreter @@ -10,15 +12,22 @@ namespace OpenProtocolInterpreter /// public abstract class Mid { + private static readonly ConcurrentDictionary _metadataCache = new(); + + private Lazy>> _lazyFields; protected const int DEFAULT_REVISION = 1; - protected Dictionary> RevisionsByFields { get; } + protected Dictionary> RevisionsByFields => _lazyFields.Value; + + /// + /// Header of the MID message containing standardized fields. + /// public Header Header { get; set; } public Mid(Header header) { Header = header; - RevisionsByFields = new SafeAccessRevisionsFields(RegisterDatafields()); + _lazyFields = new Lazy>>(() => new SafeAccessRevisionsFields(RegisterDatafields())); } public Mid(int mid, int revision, bool noAckFlag = false) : this(new Header() @@ -96,11 +105,65 @@ protected virtual string Pack(List dataFields, ref int prefixIndex) return builder.ToString(); } - protected virtual Dictionary> RegisterDatafields() => []; + protected virtual Dictionary> RegisterDatafieldsDefinitions() + { + + } + /// + /// Registers the data fields for the MID, ordered by their revision and field id. This method is called lazily when parsing or packing a MID to ensure revision defined at header. + /// Each data field should be decorated with a to define its specifications. + /// The field id in the attribute is used to order the fields in the same revision. + /// Mids with custom fields due to different revisions fields should override this method to handle them appropriately. + /// + /// A dictionary where the key is the revision number and the value is a list of data fields for that revision. + protected virtual Dictionary> RegisterDatafields() + { + var metadata = _metadataCache.GetOrAdd(GetType(), static type => + { + var result = new List(); + var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .OrderBy(x => x.GetCustomAttribute()?.Field ?? x.MetadataToken); + int fieldIndex = 20; + foreach (var prop in properties) + { + var attr = prop.GetCustomAttribute(); + if (attr == null) + continue; + + result.Add(new DataFieldMetadata(fieldIndex, attr, prop)); + fieldIndex += attr.Size + (attr.HasPrefix ? 2 : 0); + } + return result.ToArray(); + }); + + var fields = new Dictionary>(); + var currentRevision = Header.Revision; + foreach (var m in metadata) + { + if (currentRevision >= m.Attribute.Revision) + { + if (!fields.TryGetValue(m.Attribute.Revision, out var revisionFields)) + fields.Add(m.Attribute.Revision, revisionFields = new List()); + revisionFields.Add(m.Attribute.CreateAndBind(this, m.Property, m.Index)); + } + } + return fields; + } + + /// + /// Processes the header of the MID message and fills the property. This method is called before processing the data fields. + /// + /// The MID message as a string. + /// The parsed object. protected virtual Header ProcessHeader(string package) => ProcessHeader(package.AsSpan()); + /// + /// Processes the header of the MID message and fills the property. This method is called before processing the data fields. + /// + /// The MID message as a read-only span of characters. + /// The parsed object. protected virtual Header ProcessHeader(ReadOnlySpan package) { if (package.Length < 20) @@ -198,7 +261,6 @@ protected ReadOnlySpan GetValue(DataField field, ReadOnlySpan packag } } - protected byte[] GetValue(DataField field, byte[] package) { try From fad2fb56a18f8ed330f434c9a15737db9a7d1c56 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Thu, 2 Jul 2026 23:59:48 -0300 Subject: [PATCH 29/78] Updated communication mids with datafield attributes and changed mid 8 and 9 to sub/unsub mid to be an int (UI in documentation) --- .../Communication/TestMid0008.cs | 4 +- .../Communication/TestMid0009.cs | 4 +- .../Communication/Mid0001.cs | 33 ++----- .../Communication/Mid0002.cs | 86 +++---------------- .../Communication/Mid0004.cs | 48 +++-------- .../Communication/Mid0005.cs | 24 ++---- .../Communication/Mid0006.cs | 73 +++++++--------- .../Communication/Mid0008.cs | 65 ++++++-------- .../Communication/Mid0009.cs | 62 ++++++------- src/OpenProtocolInterpreter/DataField.cs | 43 ++++------ .../DataFieldDefinitionAttribute.cs | 38 ++++---- src/OpenProtocolInterpreter/MID.cs | 19 ++-- .../_internals/DataFieldMetadata.cs | 28 ++++++ 13 files changed, 203 insertions(+), 324 deletions(-) create mode 100644 src/OpenProtocolInterpreter/_internals/DataFieldMetadata.cs diff --git a/src/MIDTesters.Core/Communication/TestMid0008.cs b/src/MIDTesters.Core/Communication/TestMid0008.cs index a9e7dd79..c5377821 100644 --- a/src/MIDTesters.Core/Communication/TestMid0008.cs +++ b/src/MIDTesters.Core/Communication/TestMid0008.cs @@ -14,7 +14,7 @@ public void Mid0008Revision1() string pack = @"00430008 002200214lengthequals14"; var mid = _midInterpreter.Parse(pack); - Assert.AreEqual("0022", mid.SubscriptionMid); + Assert.AreEqual(22, mid.SubscriptionMid); Assert.AreEqual(2, mid.WantedRevision); Assert.AreEqual(14, mid.ExtraDataLength); Assert.AreEqual("lengthequals14", mid.ExtraData); @@ -29,7 +29,7 @@ public void Mid0008ByteRevision1() byte[] bytes = GetAsciiBytes(pack); var mid = _midInterpreter.Parse(bytes); - Assert.AreEqual("0022", mid.SubscriptionMid); + Assert.AreEqual(22, mid.SubscriptionMid); Assert.AreEqual(2, mid.WantedRevision); Assert.AreEqual(14, mid.ExtraDataLength); Assert.AreEqual("lengthequals14", mid.ExtraData); diff --git a/src/MIDTesters.Core/Communication/TestMid0009.cs b/src/MIDTesters.Core/Communication/TestMid0009.cs index cbfd542c..c9fb760b 100644 --- a/src/MIDTesters.Core/Communication/TestMid0009.cs +++ b/src/MIDTesters.Core/Communication/TestMid0009.cs @@ -14,7 +14,7 @@ public void Mid0009Revision1() string pack = @"00430009 002200214lengthequals14"; var mid = _midInterpreter.Parse(pack); - Assert.AreEqual("0022", mid.UnsubscriptionMid); + Assert.AreEqual(22, mid.UnsubscriptionMid); Assert.AreEqual(2, mid.ExtraDataRevision); Assert.AreEqual(14, mid.ExtraDataLength); Assert.AreEqual("lengthequals14", mid.ExtraData); @@ -29,7 +29,7 @@ public void Mid0009ByteRevision1() byte[] bytes = GetAsciiBytes(pack); var mid = _midInterpreter.Parse(bytes); - Assert.AreEqual("0022", mid.UnsubscriptionMid); + Assert.AreEqual(22, mid.UnsubscriptionMid); Assert.AreEqual(2, mid.ExtraDataRevision); Assert.AreEqual(14, mid.ExtraDataLength); Assert.AreEqual("lengthequals14", mid.ExtraData); diff --git a/src/OpenProtocolInterpreter/Communication/Mid0001.cs b/src/OpenProtocolInterpreter/Communication/Mid0001.cs index d1fbae32..0efc5c8b 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0001.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0001.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.Communication { @@ -14,13 +15,13 @@ public class Mid0001 : Mid, ICommunication, IIntegrator, IAnswerableBy, public IEnumerable DocumentedPossibleErrors => new Error[] { Error.ClientAlreadyConnected, Error.MidRevisionUnsupported }; - [BooleanDataFieldDefinition(id: 0, revision: 7)] + [BooleanDataFieldDefinition(field: 0, revision: 7)] public bool OptionalKeepAlive { get; set; } - [BooleanDataFieldDefinition(id: 1, revision: 8)] + [BooleanDataFieldDefinition(field: 1, revision: 8)] public bool OptionalToolLockAtDisconnection { get; set; } - [DecimalDataFieldDefinition(id: 2, revision: 8, Size = 4)] + [DecimalDataFieldDefinition(field: 2, revision: 8, Size = 4)] public decimal OptionalEarlyLock { get; set; } public Mid0001() : this(DEFAULT_REVISION) @@ -40,29 +41,7 @@ public Mid0001(int revision) : this(new Header() { } - // protected override Dictionary> RegisterDatafields() - // { - // var fields = new Dictionary>(); - // if (Header.Revision >= 7) - // { - // fields.Add(7, - // [ - // DataField.Boolean(DataFields.UseKeepAlive, 20).Bind(this, nameof(OptionalKeepAlive)) - // ]); - // } - - // if (Header.Revision >= 8) - // { - // fields.Add(8, - // [ - // DataField.Boolean(DataFields.OptionalToolLockAtDisconnection, 24).Bind(this, nameof(OptionalToolLockAtDisconnection)), - // DataField.Decimal(DataFields.OptionalEarlyLock, 26, 4).Bind(this, nameof(OptionalEarlyLock)) - // ]); - // } - - // return fields; - // } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { //Rev 7 diff --git a/src/OpenProtocolInterpreter/Communication/Mid0002.cs b/src/OpenProtocolInterpreter/Communication/Mid0002.cs index f867b59c..16fbf775 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0002.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0002.cs @@ -1,11 +1,12 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.Communication { /// /// Application Communication start acknowledge /// - /// When accepting the communication start the controller sends as reply, + /// When accepting the communication start the controller sends as reply, /// a Communication start acknowledge. This message contains some basic information about the /// controller, such as cell ID, channel ID, and name. /// @@ -46,7 +47,7 @@ public class Mid0002 : Mid, ICommunication, IController [StringDataFieldDefinition(id: 8, revision: 4, Size = 10)] public string ControllerSerialNumber { get; set; } - //Rev 5 + //Rev 5 [Int32DataFieldDefinition(id: 9, revision: 5, Size = 3)] public SystemType SystemType { get; set; } @@ -63,10 +64,10 @@ public class Mid0002 : Mid, ICommunication, IController public SystemSubType SystemSubType { get; set; } //Rev 6 - [BooleanDataFieldDefinition(id: 11, revision: 6)] + [BooleanDataFieldDefinition(field: 11, revision: 6)] public bool SequenceNumberSupport { get; set; } - [BooleanDataFieldDefinition(id: 12, revision: 6)] + [BooleanDataFieldDefinition(field: 12, revision: 6)] public bool LinkingHandlingSupport { get; set; } /// @@ -88,16 +89,16 @@ public class Mid0002 : Mid, ICommunication, IController //Rev 7 /// - /// False = Use Keep alive (Keep alive is mandatory) + /// False = Use Keep alive (Keep alive is mandatory) /// True = Ignore Keep alive (Keep alive is optional) /// - [BooleanDataFieldDefinition(id: 16, revision: 7)] + [BooleanDataFieldDefinition(field: 16, revision: 7)] public bool OptionalKeepAlive { get; set; } - [BooleanDataFieldDefinition(id: 17, revision: 8)] + [BooleanDataFieldDefinition(field: 17, revision: 8)] public bool OptionalToolLockAtDisconnection { get; set; } - [DecimalDataFieldDefinition(id: 18, revision: 8, Size = 1)] + [DecimalDataFieldDefinition(field: 18, revision: 8, Size = 1)] public decimal OptionalEarlyLock { get; set; } public Mid0002() : this(DEFAULT_REVISION) @@ -119,72 +120,7 @@ public Mid0002(int revision) : this(new Header() } - // protected override Dictionary> RegisterDatafields() - // { - // return new Dictionary>() - // { - // { - // 1, new List() - // { - // DataField.Number(DataFields.CellId, 20, 4), - // DataField.Number(DataFields.ChannelId, 26, 2), - // DataField.String(DataFields.ControllerName, 30, 25) - // } - // }, - // { - // 2, new List() - // { - // DataField.String(DataFields.SupplierCode, 57, 3) - // } - // }, - // { - // 3, new List() - // { - // DataField.String(DataFields.OpenProtocolVersion, 62, 19), - // DataField.String(DataFields.ControllerSoftwareVersion, 83, 19), - // DataField.String(DataFields.ToolSoftwareVersion, 104, 19) - // } - // }, - // { - // 4, new List() - // { - // DataField.String(DataFields.RBUType, 125, 24), - // DataField.String(DataFields.ControllerSerialNumber, 151, 10) - // } - // }, - // { - // 5, new List() - // { - // DataField.Number(DataFields.SystemType, 163, 3), - // DataField.Number(DataFields.SystemSubtype, 168, 3) - // } - // }, - // { - // 6, new List() - // { - // DataField.Boolean(DataFields.SequenceNumberSupport, 173), - // DataField.Boolean(DataFields.LinkingHandlingSupport, 176), - // DataField.Number(DataFields.StationCellId, 179, 10), - // DataField.String(DataFields.StationCellName, 191, 25), - // DataField.String(DataFields.ClientId, 218, 1) - // } - // }, - // { - // 7, new List() - // { - // DataField.Boolean(DataFields.OptionalKeepAlive, 221) - // } - // }, - // { - // 8, new List() - // { - // DataField.Boolean(DataFields.OptionalToolLockAtDisconnection, 224), - // DataField.Number(DataFields.OptionalEarlyLock, 227, 4) - // } - // } - // }; - // } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { //Rev 1 diff --git a/src/OpenProtocolInterpreter/Communication/Mid0004.cs b/src/OpenProtocolInterpreter/Communication/Mid0004.cs index 793e9ef3..b397d9ba 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0004.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0004.cs @@ -6,14 +6,14 @@ namespace OpenProtocolInterpreter.Communication /// /// Application Communication negative acknowledge /// - /// This message is used by the controller when a request, command or subscription for any reason has - /// not been performed. + /// This message is used by the controller when a request, command or subscription for any reason has + /// not been performed. /// The data field contains the message ID of the message request that failed as well as an error code. /// It can also be used by the integrator to acknowledge received subscribed data/events upload and will /// then do all the special subscription data acknowledges obsolete. /// /// - /// When using the communication acknowledgement of MID 0007 and together with sequence + /// When using the communication acknowledgement of MID 0007 and together with sequence /// numbering this is an application level message only. /// /// Message sent by: Controller @@ -23,16 +23,11 @@ public class Mid0004 : Mid, ICommunication, IController { public const int MID = 4; - public int FailedMid - { - get => GetField(1, DataFields.Mid).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.Mid).SetValue(OpenProtocolConvert.ToString, value); - } - public Error ErrorCode - { - get => (Error)GetField(1, DataFields.ErrorCode).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.ErrorCode).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(id: 0, revision: 1, Size = 4, HasPrefix = false)] + public int FailedMid { get; set; } + + [Int32DataFieldDefinition(id: 1, revision: 1, Size = 2, HasPrefix = false)] + public Error ErrorCode { get; set; } public Mid0004() : this(DEFAULT_REVISION) { @@ -58,35 +53,16 @@ public override string Pack() return base.Pack(); } - public override Mid Parse(ReadOnlySpan package) + protected override void ProcessDataFields(int revision, ReadOnlySpan package) { - Header = ProcessHeader(package); HandleRevision(); - ProcessDataFields(package); - return this; + base.ProcessDataFields(revision, package); } private void HandleRevision() - { - var errorCodeField = GetField(1, DataFields.ErrorCode); - errorCodeField.Size = Header.Revision > 1 ? 3 : 2; - } - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.Mid, 20, 4, false), - DataField.Number(DataFields.ErrorCode, 24, 2, false) - } - } - }; - } - + => GetField(revision: 1, field: 1).Size = Header.Revision > 1 ? 3 : 2; + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { Mid, diff --git a/src/OpenProtocolInterpreter/Communication/Mid0005.cs b/src/OpenProtocolInterpreter/Communication/Mid0005.cs index 994aee2e..a40a5d6c 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0005.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0005.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.Communication { @@ -26,11 +27,8 @@ public class Mid0005 : Mid, ICommunication, IController { public const int MID = 5; - public int MidAccepted - { - get => GetField(1, DataFields.MidAccepted).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.MidAccepted).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(id: 0, revision: 1, Size = 4, HasPrefix = false)] + public int MidAccepted { get; set; } public Mid0005() : this(DEFAULT_REVISION) { @@ -50,19 +48,7 @@ public Mid0005(int revision) : this(new Header() } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.MidAccepted, 20, 4, false) - } - } - }; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { MidAccepted diff --git a/src/OpenProtocolInterpreter/Communication/Mid0006.cs b/src/OpenProtocolInterpreter/Communication/Mid0006.cs index 7cb5ef76..cb54d89e 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0006.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0006.cs @@ -8,9 +8,9 @@ namespace OpenProtocolInterpreter.Communication /// /// Do a request for data. This message is used for ALL request handling. /// When used it substitutes the use of all MID special request messages. - /// + /// /// - /// NOTE! The Header Revision field is the revision of the itself NOT + /// NOTE! The Header Revision field is the revision of the itself NOT /// the revision of the data MID that is wanted to be uploaded. /// /// Message sent by: Integrator @@ -20,64 +20,53 @@ public class Mid0006 : Mid, ICommunication, IIntegrator { public const int MID = 6; - public int RequestedMid - { - get => GetField(1, DataFields.RequestedMid).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.RequestedMid).SetValue(OpenProtocolConvert.ToString, value); - } - public int WantedRevision - { - get => GetField(1, DataFields.WantedRevision).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.WantedRevision).SetValue(OpenProtocolConvert.ToString, value); - } - public int ExtraDataLength - { - get => GetField(1, DataFields.ExtraDataLength).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.ExtraDataLength).SetValue(OpenProtocolConvert.ToString, value); - } - public string ExtraData - { - get => GetField(1, DataFields.ExtraData).Value; - set => GetField(1, DataFields.ExtraData).SetValue(value); - } + [Int32DataFieldDefinition(id: 0, revision: 1, Size = 4, HasPrefix = false)] + public int RequestedMid { get; set; } + + [Int32DataFieldDefinition(id: 1, revision: 1, Size = 3, HasPrefix = false)] + public int WantedRevision { get; set; } + + [Int32DataFieldDefinition(id: 2, revision: 1, Size = 2, HasPrefix = false)] + public int ExtraDataLength { get; set; } + + [StringDataFieldDefinition(id: 3, revision: 1, Size = 0, HasPrefix = false)] + public string ExtraData { get; set; } public Mid0006() : this(new Header() { - Mid = MID, + Mid = MID, Revision = DEFAULT_REVISION }) { - + } public Mid0006(Header header) : base(header) { } - public override Mid Parse(ReadOnlySpan package) + public override string Pack() { - Header = ProcessHeader(package); - GetField(1, DataFields.ExtraData).Size = Header.Length - 29; - ProcessDataFields(package); - return this; + ExtraDataLength = ExtraData?.Length ?? 0; + HandleExtraDataFieldSize(); + return base.Pack(); } - protected override Dictionary> RegisterDatafields() + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - return new Dictionary>() + base.ProcessDataField(dataField, package); + if (dataField.Field == 2) { - { - 1, new List() - { - DataField.Number(DataFields.RequestedMid, 20, 4, false), - DataField.Number(DataFields.WantedRevision, 24, 3, false), - DataField.Number(DataFields.ExtraDataLength, 27, 2, false), - DataField.Volatile(DataFields.ExtraData, 29, false) - } - } - }; + HandleExtraDataFieldSize(); + } + } + + private void HandleExtraDataFieldSize() + { + GetField(revision: 1, field: 3).Size = ExtraDataLength; } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { RequestedMid, @@ -86,4 +75,4 @@ protected enum DataFields ExtraData } } -} \ No newline at end of file +} diff --git a/src/OpenProtocolInterpreter/Communication/Mid0008.cs b/src/OpenProtocolInterpreter/Communication/Mid0008.cs index 4746975d..315cb3a5 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0008.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0008.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.Versioning; namespace OpenProtocolInterpreter.Communication { @@ -15,7 +16,7 @@ namespace OpenProtocolInterpreter.Communication /// /// Message sent by: Integrator /// - /// Answer: Command accepted with the MID subscribed for or Command error, + /// Answer: Command accepted with the MID subscribed for or Command error, /// MID revision unsupported or Invalid data code and the MID subscribed for /// /// @@ -23,26 +24,14 @@ public class Mid0008 : Mid, ICommunication, IIntegrator { public const int MID = 8; - public string SubscriptionMid - { - get => GetField(1, DataFields.SubscriptionMid).Value; - set => GetField(1, DataFields.SubscriptionMid).SetValue(value); - } - public int WantedRevision - { - get => GetField(1, DataFields.WantedRevision).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.WantedRevision).SetValue(OpenProtocolConvert.ToString, value); - } - public int ExtraDataLength - { - get => GetField(1, DataFields.ExtraDataLength).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.ExtraDataLength).SetValue(OpenProtocolConvert.ToString, value); - } - public string ExtraData - { - get => GetField(1, DataFields.ExtraData).Value; - set => GetField(1, DataFields.ExtraData).SetValue(value); - } + [Int32DataFieldDefinition(id: 0, revision: 1, Size = 4, HasPrefix = false)] + public int SubscriptionMid { get; set; } + [Int32DataFieldDefinition(id: 1, revision: 1, Size = 3, HasPrefix = false)] + public int WantedRevision { get; set; } + [Int32DataFieldDefinition(id: 2, revision: 1, Size = 2, HasPrefix = false)] + public int ExtraDataLength { get; set; } + [StringDataFieldDefinition(id: 3, revision: 1, Size = 0, HasPrefix = false)] + public string ExtraData { get; set; } public Mid0008() : this(new Header() { @@ -50,37 +39,35 @@ public Mid0008() : this(new Header() Revision = DEFAULT_REVISION }) { - + } public Mid0008(Header header) : base(header) { } - public override Mid Parse(ReadOnlySpan package) + public override string Pack() { - Header = ProcessHeader(package); - GetField(1, DataFields.ExtraData).Size = Header.Length - 29; - ProcessDataFields(package); - return this; + ExtraDataLength = ExtraData?.Length ?? 0; + HandleExtraDataFieldSize(); + return base.Pack(); } - protected override Dictionary> RegisterDatafields() + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - return new Dictionary>() + base.ProcessDataField(dataField, package); + if (dataField.Field == 2) { - { - 1, new List() - { - DataField.Number(DataFields.SubscriptionMid, 20, 4, false), - DataField.Number(DataFields.WantedRevision, 24, 3, false), - DataField.Number(DataFields.ExtraDataLength, 27, 2, false), - DataField.Volatile(DataFields.ExtraData, 29, false) - } - } - }; + HandleExtraDataFieldSize(); + } + } + + private void HandleExtraDataFieldSize() + { + GetField(revision: 1, field: 3).Size = ExtraDataLength; } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { SubscriptionMid, diff --git a/src/OpenProtocolInterpreter/Communication/Mid0009.cs b/src/OpenProtocolInterpreter/Communication/Mid0009.cs index 577e9991..633937a0 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0009.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0009.cs @@ -15,7 +15,7 @@ namespace OpenProtocolInterpreter.Communication /// /// Message sent by: Integrator /// - /// Answer: Command accepted with the MID subscribed for or Command error, + /// Answer: Command accepted with the MID subscribed for or Command error, /// MID revision unsupported or Invalid data code and the MID subscribed for /// /// @@ -23,26 +23,14 @@ public class Mid0009 : Mid, ICommunication, IIntegrator { public const int MID = 9; - public string UnsubscriptionMid - { - get => GetField(1, DataFields.UnsubscriptionMid).Value; - set => GetField(1, DataFields.UnsubscriptionMid).SetValue(value); - } - public int ExtraDataRevision - { - get => GetField(1, DataFields.ExtraDataRevision).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.ExtraDataRevision).SetValue(OpenProtocolConvert.ToString, value); - } - public int ExtraDataLength - { - get => GetField(1, DataFields.ExtraDataLength).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.ExtraDataLength).SetValue(OpenProtocolConvert.ToString, value); - } - public string ExtraData - { - get => GetField(1, DataFields.ExtraData).Value; - set => GetField(1, DataFields.ExtraData).SetValue(value); - } + [Int32DataFieldDefinition(id: 0, revision: 1, Size = 4, HasPrefix = false)] + public int UnsubscriptionMid { get; set; } + [Int32DataFieldDefinition(id: 1, revision: 1, Size = 3, HasPrefix = false)] + public int ExtraDataRevision { get; set; } + [Int32DataFieldDefinition(id: 2, revision: 1, Size = 2, HasPrefix = false)] + public int ExtraDataLength { get; set; } + [StringDataFieldDefinition(id: 3, revision: 1, Size = 0, HasPrefix = false)] + public string ExtraData { get; set; } public Mid0009() : this(new Header() { @@ -57,30 +45,28 @@ public Mid0009(Header header) : base(header) { } - public override Mid Parse(ReadOnlySpan package) + public override string Pack() { - Header = ProcessHeader(package); - GetField(1, DataFields.ExtraData).Size = Header.Length - 29; - ProcessDataFields(package); - return this; + ExtraDataLength = ExtraData?.Length ?? 0; + HandleExtraDataFieldSize(); + return base.Pack(); } - protected override Dictionary> RegisterDatafields() + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - return new Dictionary>() + base.ProcessDataField(dataField, package); + if (dataField.Field == 2) { - { - 1, new List() - { - DataField.Number(DataFields.UnsubscriptionMid, 20, 4, false), - DataField.Number(DataFields.ExtraDataRevision, 24, 3, false), - DataField.Number(DataFields.ExtraDataLength, 27, 2, false), - DataField.Volatile(DataFields.ExtraData, 29, false) - } - } - }; + HandleExtraDataFieldSize(); + } + } + + private void HandleExtraDataFieldSize() + { + GetField(revision: 1, field: 3).Size = ExtraDataLength; } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { UnsubscriptionMid, diff --git a/src/OpenProtocolInterpreter/DataField.cs b/src/OpenProtocolInterpreter/DataField.cs index e8a26c75..be544586 100644 --- a/src/OpenProtocolInterpreter/DataField.cs +++ b/src/OpenProtocolInterpreter/DataField.cs @@ -4,7 +4,7 @@ namespace OpenProtocolInterpreter { /// - /// Represents a single and raw Data Field in before being abstracted + /// Represents a single and raw Data Field in before being abstracted /// to a typed field inside a mid entity /// public class DataField @@ -188,7 +188,7 @@ public static DataField Volatile(Enum field, bool hasPrefix = true) private bool IsNotTypeOf() => !CachedValue.GetType().Equals(typeof(T)); } - public class DataField : DataField + public class DataField : DataField, IBackedPropertyDataField { private Mid _owner; private PropertyInfo _backingProperty; @@ -211,6 +211,15 @@ public DataField(int field, int index, int size, char paddingChar, PaddingOrient { } + public void SyncWithBackingProperty() + { + if (_backingProperty.GetValue(_owner) is T propValue) + { + var value = DefaultConverter(PaddingChar, Size, PaddingOrientation, propValue); + base.SetValue(value); + } + } + public override void SetValue(string value) { base.SetValue(value); @@ -231,10 +240,16 @@ protected internal DataField Bind(Mid owner, PropertyInfo propertyInfo) { _owner = owner; _backingProperty = propertyInfo; + return this; } } + public interface IBackedPropertyDataField + { + void SyncWithBackingProperty(); + } + public struct DataFieldDefinition { public int Field { get; set; } @@ -256,31 +271,11 @@ public DataFieldDefinition Bind(PropertyInfo propertyInfo) return this; } +#if NET6_0_OR_GREATER public override readonly int GetHashCode() { return HashCode.Combine(Field, Revision, Index, Size, PaddingChar, PaddingOrientation, HasPrefix); } - } - - internal readonly record struct DataFieldMetadata - { - public int Index { get; init; } - public DataFieldDefinitionAttribute Attribute { get; init; } - public PropertyInfo Property { get; init; } - - public DataFieldMetadata(DataFieldDefinition definition) - { - - } - - public DataFieldMetadata(int index, DataFieldDefinitionAttribute attribute, PropertyInfo property) - { - Index = index; - Attribute = attribute; - Property = property; - } - - public DataField CreateAndBind(Mid mid) - => Attribute.CreateAndBind(mid, Property, Index); +#endif } } diff --git a/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs b/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs index 4d8f7936..20924c9a 100644 --- a/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs +++ b/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs @@ -1,9 +1,10 @@ using System; +using System.Collections.Concurrent; using System.Reflection; namespace OpenProtocolInterpreter { /// - /// Defines a data field specification in a MID. + /// Defines a data field specification in a MID. /// /// This attribute is used to decorate properties in a MID class to define how they should be parsed and formatted in the MID message. /// @@ -17,21 +18,28 @@ public class DataFieldDefinitionAttribute : Attribute public char PaddingChar { get; set; } = ' '; public PaddingOrientation PaddingOrientation { get; set; } = PaddingOrientation.RightPadded; public bool HasPrefix { get; set; } = true; + public DataFieldDefinitionAttribute(int revision) { Revision = revision; } - public DataFieldDefinitionAttribute(int id, int revision) : this(revision) + public DataFieldDefinitionAttribute(int field, int revision) : this(revision) { - if(id > 99 || id < 0) + if (field > 99 || field < 0) { - throw new ArgumentOutOfRangeException(nameof(id), "Data field id must be between 0 and 99"); + throw new ArgumentOutOfRangeException(nameof(field), "Data field id must be between 0 and 99"); } - Field = id; + Field = field; } - internal virtual DataField CreateAndBind(Mid mid, PropertyInfo propertyInfo) + + internal DataField CreateAndBind(Mid mid, PropertyInfo propertyInfo) => CreateAndBind(mid, propertyInfo, Index); - internal virtual DataField CreateAndBind(Mid mid, PropertyInfo propertyInfo, int index) + internal DataField CreateAndBind(Mid mid, PropertyInfo propertyInfo, int index) + { + return Build(mid, propertyInfo, index); + } + + internal virtual DataField Build(Mid mid, PropertyInfo propertyInfo, int index) { return new DataField(Field, index, Size, PaddingChar, PaddingOrientation, HasPrefix); } @@ -42,11 +50,11 @@ public BooleanDataFieldDefinitionAttribute(int revision) : base(revision) { Size = 1; } - public BooleanDataFieldDefinitionAttribute(int id, int revision) : base(id, revision) + public BooleanDataFieldDefinitionAttribute(int field, int revision) : base(field, revision) { Size = 1; } - internal override DataField CreateAndBind(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) { return DataField.Boolean(Field, index, HasPrefix) .Bind(mid, propertyInfo); @@ -59,9 +67,9 @@ public StringDataFieldDefinitionAttribute(int revision) : base(revision) } public StringDataFieldDefinitionAttribute(int id, int revision) : base(id, revision) { - + } - internal override DataField CreateAndBind(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) { return DataField.String(Field, index, Size, PaddingOrientation, HasPrefix) .Bind(mid, propertyInfo); @@ -79,7 +87,7 @@ public Int32DataFieldDefinitionAttribute(int id, int revision) : base(id, revisi PaddingChar = '0'; PaddingOrientation = PaddingOrientation.LeftPadded; } - internal override DataField CreateAndBind(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) { return DataField.Int32(Field, index, Size, HasPrefix) .Bind(mid, propertyInfo); @@ -97,7 +105,7 @@ public Int64DataFieldDefinitionAttribute(int id, int revision) : base(id, revisi PaddingChar = '0'; PaddingOrientation = PaddingOrientation.LeftPadded; } - internal override DataField CreateAndBind(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) { return DataField.Int64(Field, index, Size, HasPrefix) .Bind(mid, propertyInfo); @@ -110,12 +118,12 @@ public DecimalDataFieldDefinitionAttribute(int revision) : base(revision) PaddingChar = '0'; PaddingOrientation = PaddingOrientation.LeftPadded; } - public DecimalDataFieldDefinitionAttribute(int id, int revision) : base(id, revision) + public DecimalDataFieldDefinitionAttribute(int field, int revision) : base(field, revision) { PaddingChar = '0'; PaddingOrientation = PaddingOrientation.LeftPadded; } - internal override DataField CreateAndBind(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) { return DataField.Decimal(Field, index, Size, HasPrefix) .Bind(mid, propertyInfo); diff --git a/src/OpenProtocolInterpreter/MID.cs b/src/OpenProtocolInterpreter/MID.cs index 9578d85c..1ffd88fa 100644 --- a/src/OpenProtocolInterpreter/MID.cs +++ b/src/OpenProtocolInterpreter/MID.cs @@ -14,7 +14,7 @@ public abstract class Mid { private static readonly ConcurrentDictionary _metadataCache = new(); - private Lazy>> _lazyFields; + private readonly Lazy>> _lazyFields; protected const int DEFAULT_REVISION = 1; protected Dictionary> RevisionsByFields => _lazyFields.Value; @@ -93,6 +93,9 @@ protected virtual string Pack(List dataFields, ref int prefixIndex) var builder = new StringBuilder(); foreach (var dataField in dataFields) { + if (dataField is IBackedPropertyDataField backedPropertyDataField) + backedPropertyDataField.SyncWithBackingProperty(); + if (dataField.HasPrefix) { builder.Append(prefixIndex.ToString("D2")); @@ -107,7 +110,7 @@ protected virtual string Pack(List dataFields, ref int prefixIndex) protected virtual Dictionary> RegisterDatafieldsDefinitions() { - + return new Dictionary>(); } /// @@ -145,7 +148,7 @@ protected virtual Dictionary> RegisterDatafields() { if (!fields.TryGetValue(m.Attribute.Revision, out var revisionFields)) fields.Add(m.Attribute.Revision, revisionFields = new List()); - revisionFields.Add(m.Attribute.CreateAndBind(this, m.Property, m.Index)); + revisionFields.Add(m.CreateAndBind(this)); } } return fields; @@ -210,7 +213,7 @@ protected virtual void ProcessDataFields(ReadOnlySpan package) if (!RevisionsByFields.Any()) return; - int revision = Header.Revision > 0 ? Header.Revision : 1; + int revision = Header.StandardizedRevision; for (int i = 1; i <= revision; i++) { ProcessDataFields(i, package); @@ -234,9 +237,15 @@ protected virtual void ProcessDataFields(List dataFields, string pack protected virtual void ProcessDataFields(List dataFields, ReadOnlySpan package) { foreach (var dataField in dataFields) - dataField.SetValue(GetValue(dataField, package)); + ProcessDataField(dataField, package); } + protected virtual void ProcessDataField(DataField dataField, string package) + => dataField.SetValue(GetValue(dataField, package)); + + protected virtual void ProcessDataField(DataField dataField, ReadOnlySpan package) + => dataField.SetValue(GetValue(dataField, package)); + protected string GetValue(DataField field, string package) { try diff --git a/src/OpenProtocolInterpreter/_internals/DataFieldMetadata.cs b/src/OpenProtocolInterpreter/_internals/DataFieldMetadata.cs new file mode 100644 index 00000000..672517a8 --- /dev/null +++ b/src/OpenProtocolInterpreter/_internals/DataFieldMetadata.cs @@ -0,0 +1,28 @@ +using System.Reflection; + +namespace OpenProtocolInterpreter +{ + internal struct DataFieldMetadata + { + public int Index { get; set; } + public DataFieldDefinitionAttribute Attribute { get; set; } + public PropertyInfo Property { get; set; } + + public DataFieldMetadata(DataFieldDefinition definition) + { + + } + + public DataFieldMetadata(int index, DataFieldDefinitionAttribute attribute, PropertyInfo property) + { + Index = index; + Attribute = attribute; + Property = property; + } + + public DataField CreateAndBind(Mid mid) + { + return Attribute.CreateAndBind(mid, Property, Index); + } + } +} From 154ee95c5d9211a7553c2ebc31b7f896f4f2b319 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Fri, 3 Jul 2026 00:00:05 -0300 Subject: [PATCH 30/78] update to slnx --- .vscode/settings.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 6efb6c15..651bdec5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,13 +1,13 @@ { - "dotnet.defaultSolution": "OpenProtocolInterpreter.sln", - "omnisharp.useModernNet": true, - "omnisharp.enableRoslynAnalyzers": true, - "omnisharp.enableEditorConfigSupport": true, - "omnisharp.enableDecompilationSupport": true, - "dotnet.completion.showCompletionItemsFromUnimportedNamespaces": true, - "chat.tools.terminal.autoApprove": { - "dotnet build": true, - "dotnet test": true - }, - "dotnet.preferCSharpExtension": true + "dotnet.defaultSolution": "OpenProtocolInterpreter.slnx", + "omnisharp.useModernNet": true, + "omnisharp.enableRoslynAnalyzers": true, + "omnisharp.enableEditorConfigSupport": true, + "omnisharp.enableDecompilationSupport": true, + "dotnet.completion.showCompletionItemsFromUnimportedNamespaces": true, + "chat.tools.terminal.autoApprove": { + "dotnet build": true, + "dotnet test": true + }, + "dotnet.preferCSharpExtension": false } From 5233911d72772a20c9e51e5f24f82d556b43f703 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Fri, 3 Jul 2026 01:00:40 -0300 Subject: [PATCH 31/78] updated alarm mids --- src/MIDTesters.Core/Alarm/TestMid1000.cs | 37 ++++++++- src/OpenProtocolInterpreter/Alarm/Mid0071.cs | 82 +++++-------------- src/OpenProtocolInterpreter/Alarm/Mid0074.cs | 32 +++----- src/OpenProtocolInterpreter/Alarm/Mid0076.cs | 80 +++++------------- src/OpenProtocolInterpreter/Alarm/Mid1000.cs | 51 ++++-------- .../Communication/Mid0002.cs | 28 +++---- .../Communication/Mid0004.cs | 8 +- .../Communication/Mid0005.cs | 2 +- .../Communication/Mid0006.cs | 8 +- .../Communication/Mid0008.cs | 8 +- .../Communication/Mid0009.cs | 8 +- .../DataFieldDefinitionAttribute.cs | 45 +++++++++- src/OpenProtocolInterpreter/MID.cs | 10 +-- .../OpenProtocolConvert.cs | 5 +- .../VariableDataField.cs | 34 ++++---- 15 files changed, 203 insertions(+), 235 deletions(-) diff --git a/src/MIDTesters.Core/Alarm/TestMid1000.cs b/src/MIDTesters.Core/Alarm/TestMid1000.cs index 85a1459a..92131448 100644 --- a/src/MIDTesters.Core/Alarm/TestMid1000.cs +++ b/src/MIDTesters.Core/Alarm/TestMid1000.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.Alarm; using System; @@ -19,6 +20,23 @@ public void Mid1000Revision1() Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.Time); Assert.AreEqual(2, mid.NumberOfDataFields); Assert.AreEqual(2, mid.AlarmDataFields.Count); + + var alarmText = mid.AlarmDataFields[0]; + Assert.AreEqual(1700, alarmText.ParameterId); + Assert.AreEqual(9, alarmText.Length); + Assert.AreEqual(DataTypeDefinition.String, alarmText.DataType); + Assert.AreEqual(DataUnitType.NoUnit, alarmText.Unit); + Assert.AreEqual(0, alarmText.StepNumber); + Assert.AreEqual("ALARMTEXT", alarmText.DataValue); + + var alarmFlag = mid.AlarmDataFields[1]; + Assert.AreEqual(1701, alarmFlag.ParameterId); + Assert.AreEqual(1, alarmFlag.Length); + Assert.AreEqual(DataTypeDefinition.UnsignedInteger, alarmFlag.DataType); + Assert.AreEqual(DataUnitType.NoUnit, alarmFlag.Unit); + Assert.AreEqual(0, alarmFlag.StepNumber); + Assert.AreEqual("1", alarmFlag.DataValue); + AssertEqualPackages(pack, mid); } @@ -26,7 +44,7 @@ public void Mid1000Revision1() [TestCategory("Revision 1"), TestCategory("ByteArray")] public void Mid1000ByteRevision1() { - string pack = "00911000001 ABCDE2017-12-01:20:12:4500201700009040000000ALARMTEXT017010010100000001"; + string pack = "00911000001 ABCDE2017-12-01:20:12:4500201700009040000000TEXTALARM017010010100000002"; byte[] bytes = GetAsciiBytes(pack); var mid = _midInterpreter.Parse(bytes); @@ -34,6 +52,23 @@ public void Mid1000ByteRevision1() Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.Time); Assert.AreEqual(2, mid.NumberOfDataFields); Assert.AreEqual(2, mid.AlarmDataFields.Count); + + var alarmText = mid.AlarmDataFields[0]; + Assert.AreEqual(1700, alarmText.ParameterId); + Assert.AreEqual(9, alarmText.Length); + Assert.AreEqual(DataTypeDefinition.String, alarmText.DataType); + Assert.AreEqual(DataUnitType.NoUnit, alarmText.Unit); + Assert.AreEqual(0, alarmText.StepNumber); + Assert.AreEqual("TEXTALARM", alarmText.DataValue); + + var alarmFlag = mid.AlarmDataFields[1]; + Assert.AreEqual(1701, alarmFlag.ParameterId); + Assert.AreEqual(1, alarmFlag.Length); + Assert.AreEqual(DataTypeDefinition.UnsignedInteger, alarmFlag.DataType); + Assert.AreEqual(DataUnitType.NoUnit, alarmFlag.Unit); + Assert.AreEqual(0, alarmFlag.StepNumber); + Assert.AreEqual("2", alarmFlag.DataValue); + AssertEqualPackages(bytes, mid); } } diff --git a/src/OpenProtocolInterpreter/Alarm/Mid0071.cs b/src/OpenProtocolInterpreter/Alarm/Mid0071.cs index 988b67ff..48fb9df1 100644 --- a/src/OpenProtocolInterpreter/Alarm/Mid0071.cs +++ b/src/OpenProtocolInterpreter/Alarm/Mid0071.cs @@ -13,32 +13,18 @@ public class Mid0071 : Mid, IAlarm, IController, IAcknowledgeable { public const int MID = 71; - public string ErrorCode - { - get => GetField(1, DataFields.ErrorCode).Value; - set => GetField(1, DataFields.ErrorCode).SetValue(value); - } - public bool ControllerReadyStatus - { - get => GetField(1, DataFields.ControllerReadyStatus).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.ControllerReadyStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public bool ToolReadyStatus - { - get => GetField(1, DataFields.ToolReadyStatus).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.ToolReadyStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public DateTime Time - { - get => GetField(1, DataFields.Time).GetValue(OpenProtocolConvert.ToDateTime); - set => GetField(1, DataFields.Time).SetValue(OpenProtocolConvert.ToString, value); - } - //Rev 2 - public string AlarmText - { - get => GetField(2, DataFields.AlarmText).Value; - set => GetField(2, DataFields.AlarmText).SetValue(value); - } + [StringDataFieldDefinition(field: 0, revision: 1, Size = 4, PaddingOrientation = PaddingOrientation.LeftPadded)] + public string ErrorCode { get; set; } + + [BooleanDataFieldDefinition(field: 1, revision: 1)] + public bool ControllerReadyStatus { get; set; } + [BooleanDataFieldDefinition(field: 2, revision: 1)] + public bool ToolReadyStatus { get; set; } + [TimestampDataFieldDefinition(field: 3, revision: 1)] + public DateTime Time { get; set; } + + [StringDataFieldDefinition(field: 4, revision: 2, Index = 54, Size = 50)] //Always has index 54 due to error code field size change + public string AlarmText { get; set; } public Mid0071() : this(DEFAULT_REVISION) { @@ -59,57 +45,33 @@ public Mid0071(int revision) : this(new Header() } - public override Mid Parse(ReadOnlySpan package) + public override string Pack() { - Header = ProcessHeader(package); HandleRevision(); - ProcessDataFields(package); - return this; + return base.Pack(); } - protected override Dictionary> RegisterDatafields() + protected override void ProcessDataFields(ReadOnlySpan package) { - return new Dictionary>() - { - { - 1, new List() - { - DataField.String(DataFields.ErrorCode, 20, 4, PaddingOrientation.LeftPadded), - DataField.Boolean(DataFields.ControllerReadyStatus, 26), - DataField.Boolean(DataFields.ToolReadyStatus, 29), - DataField.Timestamp(DataFields.Time, 32) - } - }, - { - 2, new List() - { - DataField.String(DataFields.AlarmText, 54, 50), - } - } - }; + HandleRevision(); + base.ProcessDataFields(package); } private void HandleRevision() { - var errorCodeField = GetField(1, (int)DataFields.ErrorCode); - if (Header.Revision > 1) - { - errorCodeField.Size = 5; - } - else - { - errorCodeField.Size = 4; - } + var errorCodeField = GetField(revision: 1, field: 0); + errorCodeField.Size = Header.Revision > 1 ? 5 : 4; int index = errorCodeField.Index + errorCodeField.Size; - for (int i = DataFields.ControllerReadyStatus.GetHashCode(); i < RevisionsByFields[1].Count; i++) + for (int fieldIndex = errorCodeField.Field + 1; fieldIndex < RevisionsByFields[1].Count; fieldIndex++) { - var field = GetField(1, i); + var field = GetField(revision: 1, field: fieldIndex); field.Index = 2 + index; index = field.Index + field.Size; } } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { ErrorCode, diff --git a/src/OpenProtocolInterpreter/Alarm/Mid0074.cs b/src/OpenProtocolInterpreter/Alarm/Mid0074.cs index d25b5bd3..bf342f3c 100644 --- a/src/OpenProtocolInterpreter/Alarm/Mid0074.cs +++ b/src/OpenProtocolInterpreter/Alarm/Mid0074.cs @@ -3,7 +3,6 @@ namespace OpenProtocolInterpreter.Alarm { - /// /// Alarm acknowledged on controller /// The message is sent by the controller to inform the integrator that the current alarm has been acknowledged. @@ -14,11 +13,8 @@ public class Mid0074 : Mid, IAlarm, IController, IAcknowledgeable { public const int MID = 74; - public string ErrorCode - { - get => GetField(1, DataFields.ErrorCode).Value; - set => GetField(1, DataFields.ErrorCode).SetValue(value); - } + [StringDataFieldDefinition(field: 0, revision: 1, Size = 4, HasPrefix = false, PaddingOrientation = PaddingOrientation.LeftPadded)] + public string ErrorCode { get; set; } public Mid0074() : this(DEFAULT_REVISION) { @@ -37,31 +33,23 @@ public Mid0074(int revision) : base(MID, revision) public override string Pack() { - RevisionsByFields[1][(int)DataFields.ErrorCode].Size = Header.Revision == 1 ? 4 : 5; + HandleRevision(); return base.Pack(); } - public override Mid Parse(ReadOnlySpan package) + protected override void ProcessDataFields(ReadOnlySpan package) { - Header = ProcessHeader(package); - RevisionsByFields[1][(int)DataFields.ErrorCode].Size = Header.Revision == 1 ? 4 : 5; - ProcessDataFields(package); - return this; + HandleRevision(); + base.ProcessDataFields(package); } - protected override Dictionary> RegisterDatafields() + private void HandleRevision() { - return new Dictionary>() - { - { - 1, new List() - { - DataField.String(DataFields.ErrorCode, 20, 4, PaddingOrientation.LeftPadded, false) - } - } - }; + var errorCodeField = GetField(revision: 1, field: 0); + errorCodeField.Size = Header.Revision > 1 ? 5 : 4; } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { ErrorCode diff --git a/src/OpenProtocolInterpreter/Alarm/Mid0076.cs b/src/OpenProtocolInterpreter/Alarm/Mid0076.cs index a63de368..f07dd8f1 100644 --- a/src/OpenProtocolInterpreter/Alarm/Mid0076.cs +++ b/src/OpenProtocolInterpreter/Alarm/Mid0076.cs @@ -5,7 +5,7 @@ namespace OpenProtocolInterpreter.Alarm { /// /// Alarm status - /// The alarm status is sent after an accepted subscription of the controller alarms. + /// The alarm status is sent after an accepted subscription of the controller alarms. /// This message is used to inform the integrator that an alarm is active on the controller at subscription time. /// Message sent by: Controller /// Answer: Alarm status acknowledge @@ -14,36 +14,18 @@ public class Mid0076 : Mid, IAlarm, IController, IAcknowledgeable { public const int MID = 76; - public bool AlarmStatus - { - get => GetField(1, DataFields.AlarmStatus).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.AlarmStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public string ErrorCode - { - get => GetField(1, DataFields.ErrorCode).Value; - set => GetField(1, DataFields.ErrorCode).SetValue(value); - } - public bool ControllerReadyStatus - { - get => GetField(1, DataFields.ControllerReadyStatus).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.ControllerReadyStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public bool ToolReadyStatus - { - get => GetField(1, DataFields.ToolReadyStatus).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.ToolReadyStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public DateTime Time - { - get => GetField(1, DataFields.Time).GetValue(OpenProtocolConvert.ToDateTime); - set => GetField(1, DataFields.Time).SetValue(OpenProtocolConvert.ToString, value); - } - public ToolHealth ToolHealth - { - get => (ToolHealth)GetField(3, DataFields.ToolHealth).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(3, DataFields.ToolHealth).SetValue(OpenProtocolConvert.ToString, value); - } + [BooleanDataFieldDefinition(field: 0, revision: 1)] + public bool AlarmStatus { get; set; } + [StringDataFieldDefinition(field: 1, revision: 1, Size = 4, PaddingOrientation = PaddingOrientation.LeftPadded)] + public string ErrorCode { get; set; } + [BooleanDataFieldDefinition(field: 2, revision: 1)] + public bool ControllerReadyStatus { get; set; } + [BooleanDataFieldDefinition(field: 3, revision: 1)] + public bool ToolReadyStatus { get; set; } + [TimestampDataFieldDefinition(field: 4, revision: 1)] + public DateTime Time { get; set; } + [Int32DataFieldDefinition(field: 5, revision: 3, Index = 57, Size = 1)] + public ToolHealth ToolHealth { get; set; } public Mid0076() : this(DEFAULT_REVISION) { @@ -64,51 +46,33 @@ public Mid0076(int revision) : this(new Header() } - public override Mid Parse(ReadOnlySpan package) + public override string Pack() { - Header = ProcessHeader(package); HandleRevision(); - ProcessDataFields(package); - return this; + return base.Pack(); } - protected override Dictionary> RegisterDatafields() + protected override void ProcessDataFields(ReadOnlySpan package) { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Boolean(DataFields.AlarmStatus, 20), - DataField.String(DataFields.ErrorCode, 23, 4, PaddingOrientation.LeftPadded), - DataField.Boolean(DataFields.ControllerReadyStatus, 29), - DataField.Boolean(DataFields.ToolReadyStatus, 32), - DataField.Timestamp(DataFields.Time, 35) - } - }, - { - 3, new List() - { - DataField.Number(DataFields.ToolHealth, 57, 1), - } - } - }; + HandleRevision(); + base.ProcessDataFields(package); } private void HandleRevision() { - var errorCodeField = GetField(1, DataFields.ErrorCode); + var errorCodeField = GetField(revision: 1, field: 1); errorCodeField.Size = Header.Revision > 1 ? 5 : 4; int index = errorCodeField.Index + errorCodeField.Size; - for (int i = (int)DataFields.ControllerReadyStatus; i < RevisionsByFields[1].Count; i++) + for (int fieldIndex = errorCodeField.Field + 1; fieldIndex < RevisionsByFields[1].Count; fieldIndex++) { - var field = GetField(1, i); + var field = GetField(revision: 1, field: fieldIndex); field.Index = 2 + index; index = field.Index + field.Size; } } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { AlarmStatus, diff --git a/src/OpenProtocolInterpreter/Alarm/Mid1000.cs b/src/OpenProtocolInterpreter/Alarm/Mid1000.cs index 5a0461f4..eaaf9046 100644 --- a/src/OpenProtocolInterpreter/Alarm/Mid1000.cs +++ b/src/OpenProtocolInterpreter/Alarm/Mid1000.cs @@ -15,17 +15,13 @@ public class Mid1000 : Mid, IAlarm, IController, IAcknowledgeable { public const int MID = 1000; - public string AlarmCode - { - get => GetField(1, DataFields.AlarmCode).Value; - set => GetField(1, DataFields.AlarmCode).SetValue(value); - } - public DateTime Time - { - get => GetField(1, DataFields.Time).GetValue(OpenProtocolConvert.ToDateTime); - set => GetField(1, DataFields.Time).SetValue(OpenProtocolConvert.ToString, value); - } - public int NumberOfDataFields => AlarmDataFields.Count; + [StringDataFieldDefinition(field: 0, revision: 1, Size = 5, HasPrefix = false, PaddingOrientation = PaddingOrientation.LeftPadded)] + public string AlarmCode { get; set; } + [TimestampDataFieldDefinition(field: 1, revision: 1, HasPrefix = false)] + public DateTime Time { get; set; } + [Int32DataFieldDefinition(field: 2, revision: 1, Size = 3, HasPrefix = false)] + public int NumberOfDataFields { get; set; } + [VariableDataFieldCollectionDefinition(field: 3, revision: 1, HasPrefix = false)] public List AlarmDataFields { get; set; } public Mid1000() : this(DEFAULT_REVISION) @@ -44,42 +40,25 @@ public Mid1000(int revision) : this(new Header() Revision = revision }) { - + } public override string Pack() { - GetField(1, DataFields.NumberOfDataFields).SetValue(OpenProtocolConvert.ToString, AlarmDataFields.Count); - GetField(1, DataFields.EachAlarmDataField).SetValue(OpenProtocolConvert.ToString(AlarmDataFields)); + NumberOfDataFields = AlarmDataFields?.Count ?? 0; //Enforce list size even if modified return base.Pack(); } - public override Mid Parse(ReadOnlySpan package) - { - Header = ProcessHeader(package); - var dataFieldsField = GetField(1, DataFields.EachAlarmDataField); - dataFieldsField.Size = Header.Length - dataFieldsField.Index; - ProcessDataFields(package); - AlarmDataFields = VariableDataField.ParseAll(dataFieldsField.Value).ToList(); - return this; - } - - protected override Dictionary> RegisterDatafields() + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - return new Dictionary>() + if (dataField.Field == 3) //AlarmDataFields { - { - 1, new List() - { - DataField.String(DataFields.AlarmCode, 20, 5, PaddingOrientation.RightPadded, false), - DataField.Timestamp(DataFields.Time, 25, false), - DataField.Number(DataFields.NumberOfDataFields, 44, 3, false), - DataField.Volatile(DataFields.EachAlarmDataField, 47, false) - } - } - }; + dataField.Size = Header.Length - dataField.Index; + } + base.ProcessDataField(dataField, package); } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { AlarmCode, diff --git a/src/OpenProtocolInterpreter/Communication/Mid0002.cs b/src/OpenProtocolInterpreter/Communication/Mid0002.cs index 16fbf775..19ed67b9 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0002.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0002.cs @@ -17,38 +17,38 @@ public class Mid0002 : Mid, ICommunication, IController { public const int MID = 2; - [Int32DataFieldDefinition(id: 0, revision: 1, Size = 4)] + [Int32DataFieldDefinition(field: 0, revision: 1, Size = 4)] public int CellId { get; set; } - [Int32DataFieldDefinition(id: 1, revision: 1, Size = 2)] + [Int32DataFieldDefinition(field: 1, revision: 1, Size = 2)] public int ChannelId { get; set; } - [StringDataFieldDefinition(id: 2, revision: 1, Size = 25)] + [StringDataFieldDefinition(field: 2, revision: 1, Size = 25)] public string ControllerName { get; set; } //Rev 2 - [StringDataFieldDefinition(id: 3, revision: 2, Size = 3)] + [StringDataFieldDefinition(field: 3, revision: 2, Size = 3)] public string SupplierCode { get; set; } //Rev 3 - [StringDataFieldDefinition(id: 4, revision: 3, Size = 19)] + [StringDataFieldDefinition(field: 4, revision: 3, Size = 19)] public string OpenProtocolVersion { get; set; } - [StringDataFieldDefinition(id: 5, revision: 3, Size = 19)] + [StringDataFieldDefinition(field: 5, revision: 3, Size = 19)] public string ControllerSoftwareVersion { get; set; } - [StringDataFieldDefinition(id: 6, revision: 3, Size = 19)] + [StringDataFieldDefinition(field: 6, revision: 3, Size = 19)] public string ToolSoftwareVersion { get; set; } //Rev 4 - [StringDataFieldDefinition(id: 7, revision: 4, Size = 24)] + [StringDataFieldDefinition(field: 7, revision: 4, Size = 24)] public string RBUType { get; set; } - [StringDataFieldDefinition(id: 8, revision: 4, Size = 10)] + [StringDataFieldDefinition(field: 8, revision: 4, Size = 10)] public string ControllerSerialNumber { get; set; } //Rev 5 - [Int32DataFieldDefinition(id: 9, revision: 5, Size = 3)] + [Int32DataFieldDefinition(field: 9, revision: 5, Size = 3)] public SystemType SystemType { get; set; } /// @@ -60,7 +60,7 @@ public class Mid0002 : Mid, ICommunication, IController /// 002 = a system running presses instead of spindles. /// - [Int32DataFieldDefinition(id: 10, revision: 5, Size = 3)] + [Int32DataFieldDefinition(field: 10, revision: 5, Size = 3)] public SystemSubType SystemSubType { get; set; } //Rev 6 @@ -74,17 +74,17 @@ public class Mid0002 : Mid, ICommunication, IController /// Station ID for PF6000 /// Cell ID for PF4000 /// - [Int64DataFieldDefinition(id: 13, revision: 6, Size = 10)] + [Int64DataFieldDefinition(field: 13, revision: 6, Size = 10)] public long StationCellId { get; set; } /// /// Station ID for PF6000 /// Cell ID for PF4000 /// - [StringDataFieldDefinition(id: 14, revision: 6, Size = 25)] + [StringDataFieldDefinition(field: 14, revision: 6, Size = 25)] public string StationCellName { get; set; } - [StringDataFieldDefinition(id: 15, revision: 6, Size = 1)] + [StringDataFieldDefinition(field: 15, revision: 6, Size = 1)] public string ClientId { get; set; } //Rev 7 diff --git a/src/OpenProtocolInterpreter/Communication/Mid0004.cs b/src/OpenProtocolInterpreter/Communication/Mid0004.cs index b397d9ba..fe82d1a2 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0004.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0004.cs @@ -23,10 +23,10 @@ public class Mid0004 : Mid, ICommunication, IController { public const int MID = 4; - [Int32DataFieldDefinition(id: 0, revision: 1, Size = 4, HasPrefix = false)] + [Int32DataFieldDefinition(field: 0, revision: 1, Size = 4, HasPrefix = false)] public int FailedMid { get; set; } - [Int32DataFieldDefinition(id: 1, revision: 1, Size = 2, HasPrefix = false)] + [Int32DataFieldDefinition(field: 1, revision: 1, Size = 2, HasPrefix = false)] public Error ErrorCode { get; set; } public Mid0004() : this(DEFAULT_REVISION) @@ -53,10 +53,10 @@ public override string Pack() return base.Pack(); } - protected override void ProcessDataFields(int revision, ReadOnlySpan package) + protected override void ProcessDataFields(ReadOnlySpan package) { HandleRevision(); - base.ProcessDataFields(revision, package); + base.ProcessDataFields(package); } private void HandleRevision() diff --git a/src/OpenProtocolInterpreter/Communication/Mid0005.cs b/src/OpenProtocolInterpreter/Communication/Mid0005.cs index a40a5d6c..522cd2d3 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0005.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0005.cs @@ -27,7 +27,7 @@ public class Mid0005 : Mid, ICommunication, IController { public const int MID = 5; - [Int32DataFieldDefinition(id: 0, revision: 1, Size = 4, HasPrefix = false)] + [Int32DataFieldDefinition(field: 0, revision: 1, Size = 4, HasPrefix = false)] public int MidAccepted { get; set; } public Mid0005() : this(DEFAULT_REVISION) diff --git a/src/OpenProtocolInterpreter/Communication/Mid0006.cs b/src/OpenProtocolInterpreter/Communication/Mid0006.cs index cb54d89e..b2bb8a7c 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0006.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0006.cs @@ -20,16 +20,16 @@ public class Mid0006 : Mid, ICommunication, IIntegrator { public const int MID = 6; - [Int32DataFieldDefinition(id: 0, revision: 1, Size = 4, HasPrefix = false)] + [Int32DataFieldDefinition(field: 0, revision: 1, Size = 4, HasPrefix = false)] public int RequestedMid { get; set; } - [Int32DataFieldDefinition(id: 1, revision: 1, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(field: 1, revision: 1, Size = 3, HasPrefix = false)] public int WantedRevision { get; set; } - [Int32DataFieldDefinition(id: 2, revision: 1, Size = 2, HasPrefix = false)] + [Int32DataFieldDefinition(field: 2, revision: 1, Size = 2, HasPrefix = false)] public int ExtraDataLength { get; set; } - [StringDataFieldDefinition(id: 3, revision: 1, Size = 0, HasPrefix = false)] + [StringDataFieldDefinition(field: 3, revision: 1, Size = 0, HasPrefix = false)] public string ExtraData { get; set; } public Mid0006() : this(new Header() diff --git a/src/OpenProtocolInterpreter/Communication/Mid0008.cs b/src/OpenProtocolInterpreter/Communication/Mid0008.cs index 315cb3a5..16d63531 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0008.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0008.cs @@ -24,13 +24,13 @@ public class Mid0008 : Mid, ICommunication, IIntegrator { public const int MID = 8; - [Int32DataFieldDefinition(id: 0, revision: 1, Size = 4, HasPrefix = false)] + [Int32DataFieldDefinition(field: 0, revision: 1, Size = 4, HasPrefix = false)] public int SubscriptionMid { get; set; } - [Int32DataFieldDefinition(id: 1, revision: 1, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(field: 1, revision: 1, Size = 3, HasPrefix = false)] public int WantedRevision { get; set; } - [Int32DataFieldDefinition(id: 2, revision: 1, Size = 2, HasPrefix = false)] + [Int32DataFieldDefinition(field: 2, revision: 1, Size = 2, HasPrefix = false)] public int ExtraDataLength { get; set; } - [StringDataFieldDefinition(id: 3, revision: 1, Size = 0, HasPrefix = false)] + [StringDataFieldDefinition(field: 3, revision: 1, Size = 0, HasPrefix = false)] public string ExtraData { get; set; } public Mid0008() : this(new Header() diff --git a/src/OpenProtocolInterpreter/Communication/Mid0009.cs b/src/OpenProtocolInterpreter/Communication/Mid0009.cs index 633937a0..75370389 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0009.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0009.cs @@ -23,13 +23,13 @@ public class Mid0009 : Mid, ICommunication, IIntegrator { public const int MID = 9; - [Int32DataFieldDefinition(id: 0, revision: 1, Size = 4, HasPrefix = false)] + [Int32DataFieldDefinition(field: 0, revision: 1, Size = 4, HasPrefix = false)] public int UnsubscriptionMid { get; set; } - [Int32DataFieldDefinition(id: 1, revision: 1, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(field: 1, revision: 1, Size = 3, HasPrefix = false)] public int ExtraDataRevision { get; set; } - [Int32DataFieldDefinition(id: 2, revision: 1, Size = 2, HasPrefix = false)] + [Int32DataFieldDefinition(field: 2, revision: 1, Size = 2, HasPrefix = false)] public int ExtraDataLength { get; set; } - [StringDataFieldDefinition(id: 3, revision: 1, Size = 0, HasPrefix = false)] + [StringDataFieldDefinition(field: 3, revision: 1, Size = 0, HasPrefix = false)] public string ExtraData { get; set; } public Mid0009() : this(new Header() diff --git a/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs b/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs index 20924c9a..4cad902f 100644 --- a/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs +++ b/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Reflection; namespace OpenProtocolInterpreter { @@ -65,7 +66,7 @@ public class StringDataFieldDefinitionAttribute : DataFieldDefinitionAttribute public StringDataFieldDefinitionAttribute(int revision) : base(revision) { } - public StringDataFieldDefinitionAttribute(int id, int revision) : base(id, revision) + public StringDataFieldDefinitionAttribute(int field, int revision) : base(field, revision) { } @@ -82,7 +83,7 @@ public Int32DataFieldDefinitionAttribute(int revision) : base(revision) PaddingChar = '0'; PaddingOrientation = PaddingOrientation.LeftPadded; } - public Int32DataFieldDefinitionAttribute(int id, int revision) : base(id, revision) + public Int32DataFieldDefinitionAttribute(int field, int revision) : base(field, revision) { PaddingChar = '0'; PaddingOrientation = PaddingOrientation.LeftPadded; @@ -100,7 +101,7 @@ public Int64DataFieldDefinitionAttribute(int revision) : base(revision) PaddingChar = '0'; PaddingOrientation = PaddingOrientation.LeftPadded; } - public Int64DataFieldDefinitionAttribute(int id, int revision) : base(id, revision) + public Int64DataFieldDefinitionAttribute(int field, int revision) : base(field, revision) { PaddingChar = '0'; PaddingOrientation = PaddingOrientation.LeftPadded; @@ -129,4 +130,42 @@ internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) .Bind(mid, propertyInfo); } } + + public class TimestampDataFieldDefinitionAttribute : DataFieldDefinitionAttribute + { + public TimestampDataFieldDefinitionAttribute(int revision) : base(revision) + { + Size = 19; + } + public TimestampDataFieldDefinitionAttribute(int field, int revision) : base(field, revision) + { + Size = 19; + } + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + { + return DataField.Timestamp(Field, index, HasPrefix) + .Bind(mid, propertyInfo); + } + } + + public class VariableDataFieldCollectionDefinitionAttribute : DataFieldDefinitionAttribute + { + public VariableDataFieldCollectionDefinitionAttribute(int revision) : base(revision) + { + + } + public VariableDataFieldCollectionDefinitionAttribute(int field, int revision) : base(field, revision) + { + + } + + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + { + return new DataField>(Field, index, Size, HasPrefix) + { + DefaultConverter = OpenProtocolConvert.ToString, + DefaultParser = VariableDataField.ParseAll + }.Bind(mid, propertyInfo); + } + } } diff --git a/src/OpenProtocolInterpreter/MID.cs b/src/OpenProtocolInterpreter/MID.cs index 1ffd88fa..304ed6f1 100644 --- a/src/OpenProtocolInterpreter/MID.cs +++ b/src/OpenProtocolInterpreter/MID.cs @@ -134,6 +134,7 @@ protected virtual Dictionary> RegisterDatafields() if (attr == null) continue; + fieldIndex = attr.Index > 0 ? attr.Index : fieldIndex; //enforced index if defined in attribute result.Add(new DataFieldMetadata(fieldIndex, attr, prop)); fieldIndex += attr.Size + (attr.HasPrefix ? 2 : 0); } @@ -144,12 +145,9 @@ protected virtual Dictionary> RegisterDatafields() var currentRevision = Header.Revision; foreach (var m in metadata) { - if (currentRevision >= m.Attribute.Revision) - { - if (!fields.TryGetValue(m.Attribute.Revision, out var revisionFields)) - fields.Add(m.Attribute.Revision, revisionFields = new List()); - revisionFields.Add(m.CreateAndBind(this)); - } + if (!fields.TryGetValue(m.Attribute.Revision, out var revisionFields)) + fields.Add(m.Attribute.Revision, revisionFields = new List()); + revisionFields.Add(m.CreateAndBind(this)); } return fields; } diff --git a/src/OpenProtocolInterpreter/OpenProtocolConvert.cs b/src/OpenProtocolInterpreter/OpenProtocolConvert.cs index 11258a10..d22cfbe1 100644 --- a/src/OpenProtocolInterpreter/OpenProtocolConvert.cs +++ b/src/OpenProtocolInterpreter/OpenProtocolConvert.cs @@ -163,7 +163,10 @@ public static long ToInt64(ReadOnlySpan value) #endif } - public static string ToString(IEnumerable value) + public static string ToString(char paddingChar, int size, PaddingOrientation orientation, List value) + => ToString(value); + + public static string ToString(List value) { var builder = new StringBuilder(); foreach (var v in value) diff --git a/src/OpenProtocolInterpreter/VariableDataField.cs b/src/OpenProtocolInterpreter/VariableDataField.cs index 9ce1ecf3..21c90593 100644 --- a/src/OpenProtocolInterpreter/VariableDataField.cs +++ b/src/OpenProtocolInterpreter/VariableDataField.cs @@ -31,7 +31,7 @@ public object Convert() case DataTypeDefinition.Timestamp: return DateTime.TryParseExact(DataValue, "yyyy-MM-dd:HH:mm:ss", null, DateTimeStyles.None, out DateTime dateTime) ? dateTime : DataValue; - + case DataTypeDefinition.Boolean: return OpenProtocolConvert.ToBoolean(DataValue); @@ -53,7 +53,7 @@ public string Pack() { return OpenProtocolConvert.ToString('0', 5, PaddingOrientation.LeftPadded, ParameterId) + OpenProtocolConvert.ToString('0', 3, PaddingOrientation.LeftPadded, Length) + - OpenProtocolConvert.ToString('0', 2, PaddingOrientation.LeftPadded, (int)DataType) + + OpenProtocolConvert.ToString('0', 2, PaddingOrientation.LeftPadded, (int)DataType) + OpenProtocolConvert.ToString('0', 3, PaddingOrientation.LeftPadded, (int)Unit) + OpenProtocolConvert.ToString('0', 4, PaddingOrientation.LeftPadded, StepNumber) + OpenProtocolConvert.TruncatePadded(' ', Length, PaddingOrientation.RightPadded, DataValue); @@ -71,29 +71,29 @@ public static VariableDataField Parse(ReadOnlySpan value) return Parse(value, length); } - public static IEnumerable ParseAll(string value) + public static List ParseAll(string value) { - if (string.IsNullOrWhiteSpace(value)) - { - yield break; - } - - int valueLength; - const int fixedLength = 17; - for (int i = 0; i < value.Length; i += fixedLength + valueLength) + var list = new List(); + if (!string.IsNullOrWhiteSpace(value)) { - valueLength = OpenProtocolConvert.ToInt32(value.Substring(i + 5, 3)); - var section = value.Substring(i, fixedLength + valueLength); - yield return Parse(section, valueLength); + int valueLength; + const int fixedLength = 17; + for (int i = 0; i < value.Length; i += fixedLength + valueLength) + { + valueLength = OpenProtocolConvert.ToInt32(value.Substring(i + 5, 3)); + var section = value.Substring(i, fixedLength + valueLength); + list.Add(Parse(section, valueLength)); + } } + return list; } - public static IEnumerable ParseAll(ReadOnlySpan value) + public static List ParseAll(ReadOnlySpan value) { if (value.IsWhiteSpace() || value.IsEmpty) - return System.Array.Empty(); + return new List(); - var result = new System.Collections.Generic.List(); + var result = new List(); int valueLength; const int fixedLength = 17; for (int i = 0; i < value.Length; i += fixedLength + valueLength) From 721c96b6b334a998f020b433cf768561dfe954ce Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 5 Jul 2026 03:13:45 -0300 Subject: [PATCH 32/78] Updated application selector to use datafield attributes --- .../ApplicationSelector/Mid0251.cs | 96 ++++++++++--------- .../ApplicationSelector/Mid0254.cs | 66 +++---------- .../ApplicationSelector/Mid0255.cs | 65 ++----------- .../DataFieldDefinitionAttribute.cs | 40 ++++++++ 4 files changed, 109 insertions(+), 158 deletions(-) diff --git a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0251.cs b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0251.cs index 651c0dd8..8e20d760 100644 --- a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0251.cs +++ b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0251.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Reflection; using System.Text; namespace OpenProtocolInterpreter.ApplicationSelector @@ -7,9 +8,9 @@ namespace OpenProtocolInterpreter.ApplicationSelector /// /// Selector socket info /// - /// This message is sent each time a socket is lifted or put back in position. - /// This MID contains the device ID of the selector the information is coming from, - /// the number of sockets of the selector device, and the current status of each socket + /// This message is sent each time a socket is lifted or put back in position. + /// This MID contains the device ID of the selector the information is coming from, + /// the number of sockets of the selector device, and the current status of each socket /// (lifted or not lifted). /// /// Message sent by: Controller @@ -19,16 +20,11 @@ public class Mid0251 : Mid, IApplicationSelector, IController, IAcknowledgeable< { public const int MID = 251; - public int DeviceId - { - get => GetField(1, DataFields.DeviceId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.DeviceId).SetValue(OpenProtocolConvert.ToString, value); - } - public int NumberOfSockets - { - get => GetField(1, DataFields.NumberOfSockets).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.NumberOfSockets).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(field: 0, revision: 1, Size = 2)] + public int DeviceId { get; set; } + [Int32DataFieldDefinition(field: 1, revision: 1, Size = 2)] + public int NumberOfSockets { get; set; } + [SocketStatusListDefinition(field: 2, revision: 1)] public List SocketStatus { get; set; } public Mid0251() : this(new Header() @@ -47,54 +43,60 @@ public Mid0251(Header header) : base(header) public override string Pack() { - GetField(1, DataFields.SocketStatus).Size = NumberOfSockets; - GetField(1, DataFields.SocketStatus).Value = PackSocketStatus(); + NumberOfSockets = SocketStatus.Count; + GetField(revision: 1, field: 2).Size = NumberOfSockets; return base.Pack(); } - public override Mid Parse(ReadOnlySpan package) + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - Header = ProcessHeader(package); - - GetField(1, DataFields.SocketStatus).Size = Header.Length - 30; - ProcessDataFields(package); - SocketStatus = ParseSocketStatus(GetField(1, DataFields.SocketStatus).Value); - return this; + if (dataField.Field == 2) + { + dataField.Size = NumberOfSockets; + } + base.ProcessDataField(dataField, package); } - protected virtual string PackSocketStatus() + private class SocketStatusListDefinitionAttribute : DataFieldDefinitionAttribute { - var builder = new StringBuilder(SocketStatus.Count); - foreach (var v in SocketStatus) - builder.Append(OpenProtocolConvert.ToString(v)); - - return builder.ToString(); - } + public SocketStatusListDefinitionAttribute(int revision) : base(revision) + { - protected virtual List ParseSocketStatus(string section) - { - var list = new List(); - foreach (var c in section) - list.Add(OpenProtocolConvert.ToBoolean(c.ToString())); + } + public SocketStatusListDefinitionAttribute(int field, int revision) : base(field, revision) + { - return list; - } + } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) { + return new DataField>(Field, index, Size, HasPrefix) { - 1, new List() - { - DataField.Number(DataFields.DeviceId, 20, 2), - DataField.Number(DataFields.NumberOfSockets, 24, 2), - DataField.Volatile(DataFields.SocketStatus, 28) - } - } - }; + DefaultConverter = PackSocketStatus, + DefaultParser = ParseSocketStatus + }.Bind(mid, propertyInfo); + } + + protected static string PackSocketStatus(char paddingChar, int size, PaddingOrientation orientation, List socketStatus) + { + var builder = new StringBuilder(socketStatus.Count); + foreach (var v in socketStatus) + builder.Append(OpenProtocolConvert.ToString(v)); + + return builder.ToString(); + } + + protected static List ParseSocketStatus(string section) + { + var list = new List(); + foreach (var c in section) + list.Add(OpenProtocolConvert.ToBoolean(c.ToString())); + + return list; + } } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { DeviceId, diff --git a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0254.cs b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0254.cs index 8ff3e684..8663a281 100644 --- a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0254.cs +++ b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0254.cs @@ -1,20 +1,21 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Text; namespace OpenProtocolInterpreter.ApplicationSelector { /// /// Selector control green lights - /// - /// This message controls the selector green lights. - /// The green light can be set (steady), reset (off) or flash. + /// + /// This message controls the selector green lights. + /// The green light can be set (steady), reset (off) or flash. /// A command must be sent for each one of the selector positions (1-8). /// /// - /// Note: This MID only works when the selector is put in external controlled mode and - /// this is only possible when the selector is loaded with software 1.20 or later. + /// Note: This MID only works when the selector is put in external controlled mode and + /// this is only possible when the selector is loaded with software 1.20 or later. /// /// Message sent by: Integrator /// Answer: Command accepted or Command error, Faulty IO device ID @@ -25,11 +26,10 @@ public class Mid0254 : Mid, IApplicationSelector, IIntegrator, IAcceptableComman public IEnumerable DocumentedPossibleErrors => new Error[] { Error.FaultyIODeviceId }; - public int DeviceId - { - get => GetField(1, DataFields.DeviceId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.DeviceId).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(field: 0, revision: 1, Size = 2)] + public int DeviceId { get; set; } + + [EnumCollectionDefinition(field: 1, revision: 1, Size = 8)] public List GreenLights { get; set; } public Mid0254() : this(new Header() @@ -46,51 +46,7 @@ public Mid0254(Header header) : base(header) GreenLights ??= []; } - public override string Pack() - { - GetField(1, DataFields.GreenLightCommand).Value = PackGreenLights(); - return base.Pack(); - } - - public override Mid Parse(ReadOnlySpan package) - { - base.Parse(package); - GreenLights = ParseGreenLights(GetField(1, DataFields.GreenLightCommand).Value).ToList(); - return this; - } - - protected virtual string PackGreenLights() - { - var builder = new StringBuilder(GreenLights.Count); - foreach (var e in GreenLights) - builder.Append(OpenProtocolConvert.ToString((int)e)); - - return builder.ToString(); - } - - protected virtual List ParseGreenLights(string value) - { - var list = new List(); - foreach (var c in value) - list.Add((LightCommand)OpenProtocolConvert.ToInt32(c.ToString())); - - return list; - } - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.DeviceId, 20, 2), - new(DataFields.GreenLightCommand, 24, 8) - } - } - }; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { DeviceId, diff --git a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0255.cs b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0255.cs index 88547698..a2d35d4e 100644 --- a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0255.cs +++ b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0255.cs @@ -1,19 +1,18 @@ using System; using System.Collections.Generic; -using System.Text; namespace OpenProtocolInterpreter.ApplicationSelector { /// /// Selector control red lights /// - /// This message controls the selector red lights. - /// The green light can be set (steady), reset (off) or flash. + /// This message controls the selector red lights. + /// The green light can be set (steady), reset (off) or flash. /// A command must be sent for each one of the selector positions (1-8). /// /// - /// Note: This MID only works when the selector is put in external controlled mode and - /// this is only possible when the selector is loaded with software 1.20 or later. + /// Note: This MID only works when the selector is put in external controlled mode and + /// this is only possible when the selector is loaded with software 1.20 or later. /// /// Message sent by: Integrator /// Answer: Command accepted or Command error, Faulty IO device ID @@ -24,11 +23,9 @@ public class Mid0255 : Mid, IApplicationSelector, IIntegrator, IAcceptableComman public IEnumerable DocumentedPossibleErrors => new Error[] { Error.FaultyIODeviceId }; - public int DeviceId - { - get => GetField(1, DataFields.DeviceId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.DeviceId).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(field: 0, revision: 1, Size = 2)] + public int DeviceId { get; set; } + [EnumCollectionDefinition(field: 1, revision: 1, Size = 8)] public List RedLights { get; set; } public Mid0255() : this(new Header() @@ -37,7 +34,7 @@ public Mid0255() : this(new Header() Revision = DEFAULT_REVISION }) { - + } public Mid0255(Header header) : base(header) @@ -45,51 +42,7 @@ public Mid0255(Header header) : base(header) RedLights ??= []; } - public override string Pack() - { - GetField(1, DataFields.RedLightCommand).Value = PackRedLights(); - return base.Pack(); - } - - public override Mid Parse(ReadOnlySpan package) - { - base.Parse(package); - RedLights = ParseRedLights(GetField(1, DataFields.RedLightCommand).Value); - return this; - } - - protected virtual string PackRedLights() - { - var builder = new StringBuilder(RedLights.Count); - foreach (var e in RedLights) - builder.Append(OpenProtocolConvert.ToString(e)); - - return builder.ToString(); - } - - protected virtual List ParseRedLights(string value) - { - var list = new List(); - foreach (var c in value) - list.Add((LightCommand)OpenProtocolConvert.ToInt32(c.ToString())); - - return list; - } - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.DeviceId, 20, 2), - new(DataFields.RedLightCommand, 24, 8) - } - } - }; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { DeviceId, diff --git a/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs b/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs index 4cad902f..1db38b15 100644 --- a/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs +++ b/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Reflection; +using System.Text; namespace OpenProtocolInterpreter { /// @@ -168,4 +169,43 @@ internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) }.Bind(mid, propertyInfo); } } + + public class EnumCollectionDefinitionAttribute : DataFieldDefinitionAttribute where T : Enum + { + public EnumCollectionDefinitionAttribute(int revision) : base(revision) + { + + } + public EnumCollectionDefinitionAttribute(int field, int revision) : base(field, revision) + { + + } + + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + { + return new DataField>(Field, index, Size, HasPrefix) + { + DefaultConverter = PackGreenLights, + DefaultParser = ParseGreenLights + }.Bind(mid, propertyInfo); + } + + private static string PackGreenLights(char paddingChar, int size, PaddingOrientation orientation, List greenLights) + { + var builder = new StringBuilder(greenLights.Count); + foreach (var e in greenLights) + builder.Append(OpenProtocolConvert.ToString((int)(object)e)); + + return builder.ToString(); + } + + private static List ParseGreenLights(string value) + { + var list = new List(); + foreach (var c in value) + list.Add((T)(object)OpenProtocolConvert.ToInt32(c.ToString())); + + return list; + } + } } From 671738beef08ed89707ba712048a71d5360c0859 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 5 Jul 2026 03:16:53 -0300 Subject: [PATCH 33/78] Updated ApplicationToolLocationSystem mids to use datafield attributes --- .../ApplicationToolLocationSystem/Mid0262.cs | 24 +++----------- .../ApplicationToolLocationSystem/Mid0265.cs | 33 +++++-------------- 2 files changed, 13 insertions(+), 44 deletions(-) diff --git a/src/OpenProtocolInterpreter/ApplicationToolLocationSystem/Mid0262.cs b/src/OpenProtocolInterpreter/ApplicationToolLocationSystem/Mid0262.cs index 7406f2b4..858bad37 100644 --- a/src/OpenProtocolInterpreter/ApplicationToolLocationSystem/Mid0262.cs +++ b/src/OpenProtocolInterpreter/ApplicationToolLocationSystem/Mid0262.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.ApplicationToolLocationSystem { @@ -12,11 +13,8 @@ public class Mid0262 : Mid, IApplicationToolLocationSystem, IController, IAcknow { public const int MID = 262; - public string ToolTagId - { - get => GetField(1, DataFields.ToolTagId).Value; - set => GetField(1, DataFields.ToolTagId).SetValue(value); - } + [StringDataFieldDefinition(field: 0, revision: 1, Size = 8)] + public string ToolTagId { get; set; } public Mid0262() : base(MID, DEFAULT_REVISION) { @@ -28,19 +26,7 @@ public Mid0262(Header header) : base(header) } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.String(DataFields.ToolTagId, 20, 8) - } - } - }; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { ToolTagId diff --git a/src/OpenProtocolInterpreter/ApplicationToolLocationSystem/Mid0265.cs b/src/OpenProtocolInterpreter/ApplicationToolLocationSystem/Mid0265.cs index cca95998..27c9b2ca 100644 --- a/src/OpenProtocolInterpreter/ApplicationToolLocationSystem/Mid0265.cs +++ b/src/OpenProtocolInterpreter/ApplicationToolLocationSystem/Mid0265.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.ApplicationToolLocationSystem { @@ -14,16 +15,11 @@ public class Mid0265 : Mid, IApplicationToolLocationSystem, IIntegrator, IAccept public IEnumerable DocumentedPossibleErrors => new Error[] { Error.MidRevisionUnsupported }; - public string ToolTagId - { - get => GetField(1, DataFields.ToolTagId).Value; - set => GetField(1, DataFields.ToolTagId).SetValue(value); - } - public ToolStatus ToolStatus - { - get => (ToolStatus)GetField(1, DataFields.ToolStatus).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.ToolStatus).SetValue(OpenProtocolConvert.ToString, value); - } + [StringDataFieldDefinition(field: 0, revision: 1, Size = 8)] + public string ToolTagId { get; set; } + + [Int32DataFieldDefinition(field: 1, revision: 1, Size = 2)] + public ToolStatus ToolStatus { get; set; } public Mid0265() : this(new Header() { @@ -39,20 +35,7 @@ public Mid0265(Header header) : base(header) } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.String(DataFields.ToolTagId, 20, 8), - DataField.Number(DataFields.ToolStatus, 30, 2) - } - } - }; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { ToolTagId, From 5cb4f2142c19f0f748d3a406168814440a60a469 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 5 Jul 2026 03:18:39 -0300 Subject: [PATCH 34/78] Updated AutomaticManualMode mids to use datafield attributes --- .../AutomaticManualMode/Mid0401.cs | 26 +++---------- .../AutomaticManualMode/Mid0411.cs | 38 +++++-------------- 2 files changed, 16 insertions(+), 48 deletions(-) diff --git a/src/OpenProtocolInterpreter/AutomaticManualMode/Mid0401.cs b/src/OpenProtocolInterpreter/AutomaticManualMode/Mid0401.cs index e5273e59..f37832f5 100644 --- a/src/OpenProtocolInterpreter/AutomaticManualMode/Mid0401.cs +++ b/src/OpenProtocolInterpreter/AutomaticManualMode/Mid0401.cs @@ -1,11 +1,12 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.AutomaticManualMode { /// /// Automatic/Manual mode /// - /// The operation mode in the controller has changed. + /// The operation mode in the controller has changed. /// The message includes the new operational mode of the controller. /// /// Message sent by: Controller @@ -19,11 +20,8 @@ public class Mid0401 : Mid, IAutomaticManualMode, IController, IAcknowledgeable< /// Automatic Mode = false (0) /// Manual Mode = true (1) /// - public bool ManualAutomaticMode - { - get => GetField(1, DataFields.ManualAutomaticMode).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.ManualAutomaticMode).SetValue(OpenProtocolConvert.ToString, value); - } + [BooleanDataFieldDefinition(field: 0, revision: 1, HasPrefix = false)] + public bool ManualAutomaticMode { get; set; } public Mid0401() : this(new Header() { @@ -38,19 +36,7 @@ public Mid0401(Header header) : base(header) { } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Boolean(DataFields.ManualAutomaticMode, 20, false) - } - } - }; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { ManualAutomaticMode diff --git a/src/OpenProtocolInterpreter/AutomaticManualMode/Mid0411.cs b/src/OpenProtocolInterpreter/AutomaticManualMode/Mid0411.cs index 0cf5e0e9..57008c6e 100644 --- a/src/OpenProtocolInterpreter/AutomaticManualMode/Mid0411.cs +++ b/src/OpenProtocolInterpreter/AutomaticManualMode/Mid0411.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.AutomaticManualMode { @@ -7,7 +8,7 @@ namespace OpenProtocolInterpreter.AutomaticManualMode /// /// Information about the setting of AutoDisable tightening in the controller. Also contains information about the currently running batch. /// The settings are reserved for single parameter sets with batch and are not available while running Job. - /// + /// /// /// Power Macs use: /// “OKs to disable station” is a parameter in Tools Talk PowerMACS and specifies the number of cycles with status OK or OKR that may be run while in Automatic mode before the station is automatically disabled. It is sent as two ASCII digits, a 0 means the function is not in use. @@ -32,44 +33,25 @@ public class Mid0411 : Mid, IAutomaticManualMode, IController { public const int MID = 411; - public int AutoDisableSetting - { - get => GetField(1, DataFields.AutoDisableSetting).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.AutoDisableSetting).SetValue(OpenProtocolConvert.ToString, value); - } - public int CurrentBatch - { - get => GetField(1, DataFields.CurrentBatch).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.CurrentBatch).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(field: 0, revision: 1, Size = 2, HasPrefix = false)] + public int AutoDisableSetting { get; set; } + [Int32DataFieldDefinition(field: 1, revision: 1, Size = 2, HasPrefix = false)] + public int CurrentBatch { get; set; } public Mid0411() : this(new Header() { - Mid= MID, + Mid = MID, Revision = DEFAULT_REVISION }) { - - } - public Mid0411(Header header) : base(header) - { } - protected override Dictionary> RegisterDatafields() + public Mid0411(Header header) : base(header) { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.AutoDisableSetting, 20, 2, false), - DataField.Number(DataFields.CurrentBatch, 22, 2, false) - } - } - }; } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { AutoDisableSetting, From f52f88865197480f496fa7c7c321daf8830987c6 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 5 Jul 2026 22:54:49 -0300 Subject: [PATCH 35/78] Updated IOInterface to use datafields attribute --- .../IOInterface/TestMid0215.cs | 6 +- src/OpenProtocolInterpreter/DataField.cs | 6 +- .../DataFieldDefinitionAttribute.cs | 8 +- .../IOInterface/DigitalInput.cs | 52 ++++- .../IOInterface/Mid0200.cs | 96 ++------- .../IOInterface/Mid0211.cs | 80 ++----- .../IOInterface/Mid0214.cs | 18 +- .../IOInterface/Mid0215.cs | 203 +++++++++--------- .../IOInterface/Mid0216.cs | 26 +-- .../IOInterface/Mid0217.cs | 32 +-- .../IOInterface/Mid0219.cs | 27 +-- .../IOInterface/Mid0220.cs | 32 +-- .../IOInterface/Mid0221.cs | 34 +-- .../IOInterface/Mid0223.cs | 28 +-- .../IOInterface/Mid0224.cs | 26 +-- .../IOInterface/Mid0225.cs | 31 +-- .../IOInterface/Relay.cs | 53 ++++- src/OpenProtocolInterpreter/MID.cs | 9 +- 18 files changed, 317 insertions(+), 450 deletions(-) diff --git a/src/MIDTesters.Core/IOInterface/TestMid0215.cs b/src/MIDTesters.Core/IOInterface/TestMid0215.cs index f34ba063..bce76244 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0215.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0215.cs @@ -40,14 +40,14 @@ public void Mid0215ByteRevision1() [TestCategory("Revision 2"), TestCategory("ASCII")] public void Mid0215Revision2() { - string package = "00920215002 010302070300100021003100400051006000710407050011002000310041005100610070"; + string package = @"00920215002 010302070300100021003100400051006000710407050011002000310041005100610070"; var mid = _midInterpreter.Parse(package); Assert.AreEqual(3, mid.IODeviceId); - Assert.AreEqual(7, mid.Relays.Count); - Assert.AreEqual(7, mid.DigitalInputs.Count); Assert.AreEqual(7, mid.NumberOfRelays); + Assert.AreEqual(7, mid.Relays.Count); Assert.AreEqual(7, mid.NumberOfDigitalInputs); + Assert.AreEqual(7, mid.DigitalInputs.Count); AssertEqualPackages(package, mid); } diff --git a/src/OpenProtocolInterpreter/DataField.cs b/src/OpenProtocolInterpreter/DataField.cs index be544586..3ac4a0bd 100644 --- a/src/OpenProtocolInterpreter/DataField.cs +++ b/src/OpenProtocolInterpreter/DataField.cs @@ -211,9 +211,9 @@ public DataField(int field, int index, int size, char paddingChar, PaddingOrient { } - public void SyncWithBackingProperty() + public void SyncWithBackingPropertyIfBound() { - if (_backingProperty.GetValue(_owner) is T propValue) + if (_backingProperty?.GetValue(_owner) is T propValue) { var value = DefaultConverter(PaddingChar, Size, PaddingOrientation, propValue); base.SetValue(value); @@ -247,7 +247,7 @@ protected internal DataField Bind(Mid owner, PropertyInfo propertyInfo) public interface IBackedPropertyDataField { - void SyncWithBackingProperty(); + void SyncWithBackingPropertyIfBound(); } public struct DataFieldDefinition diff --git a/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs b/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs index 1db38b15..80baca53 100644 --- a/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs +++ b/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs @@ -185,12 +185,12 @@ internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) { return new DataField>(Field, index, Size, HasPrefix) { - DefaultConverter = PackGreenLights, - DefaultParser = ParseGreenLights + DefaultConverter = PackEnums, + DefaultParser = ParseEnums }.Bind(mid, propertyInfo); } - private static string PackGreenLights(char paddingChar, int size, PaddingOrientation orientation, List greenLights) + private static string PackEnums(char paddingChar, int size, PaddingOrientation orientation, List greenLights) { var builder = new StringBuilder(greenLights.Count); foreach (var e in greenLights) @@ -199,7 +199,7 @@ private static string PackGreenLights(char paddingChar, int size, PaddingOrienta return builder.ToString(); } - private static List ParseGreenLights(string value) + private static List ParseEnums(string value) { var list = new List(); foreach (var c in value) diff --git a/src/OpenProtocolInterpreter/IOInterface/DigitalInput.cs b/src/OpenProtocolInterpreter/IOInterface/DigitalInput.cs index 069a5d54..63344836 100644 --- a/src/OpenProtocolInterpreter/IOInterface/DigitalInput.cs +++ b/src/OpenProtocolInterpreter/IOInterface/DigitalInput.cs @@ -1,5 +1,8 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; namespace OpenProtocolInterpreter.IOInterface { @@ -11,6 +14,17 @@ public class DigitalInput public DigitalInputNumber Number { get; set; } public bool Status { get; set; } + public DigitalInput() + { + + } + + public DigitalInput(DigitalInputNumber number, bool status) + { + Number = number; + Status = status; + } + public string Pack() { return OpenProtocolConvert.ToString('0', 3, PaddingOrientation.LeftPadded, (int)Number) + @@ -37,7 +51,7 @@ public static DigitalInput Parse(ReadOnlySpan section) public static IEnumerable ParseAll(string value) { - if(string.IsNullOrEmpty(value)) + if (string.IsNullOrEmpty(value)) { yield break; } @@ -53,14 +67,46 @@ public static IEnumerable ParseAll(string value) public static IEnumerable ParseAll(ReadOnlySpan value) { if (value.IsEmpty) - return System.Array.Empty(); + return Array.Empty(); - var result = new System.Collections.Generic.List(); + var result = new List(); const int sectionSize = 4; for (int i = 0; i < value.Length; i += sectionSize) result.Add(Parse(value.Slice(i, sectionSize))); return result; } + } + + public class DigitalInputCollectionDefinitionAttribute : DataFieldDefinitionAttribute + { + public DigitalInputCollectionDefinitionAttribute(int revision) : base(revision) + { + + } + public DigitalInputCollectionDefinitionAttribute(int field, int revision) : base(field, revision) + { + + } + + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + { + return new DataField>(Field, index, Size, HasPrefix) + { + DefaultConverter = PackDigitalInputs, + DefaultParser = ParseDigitalInputs + }.Bind(mid, propertyInfo); + } + + private static string PackDigitalInputs(char paddingChar, int size, PaddingOrientation orientation, List digitalInputs) + { + var builder = new StringBuilder(digitalInputs.Count * 4); + foreach (var digitalInput in digitalInputs) + builder.Append(digitalInput.Pack()); + + return builder.ToString(); + } + private static List ParseDigitalInputs(string value) + => DigitalInput.ParseAll(value).ToList(); } } diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0200.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0200.cs index 252a4489..a77d1485 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0200.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0200.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.IOInterface { @@ -15,56 +16,26 @@ public class Mid0200 : Mid, IIOInterface, IIntegrator, IAcceptableCommand { public const int MID = 200; - public RelayStatus StatusRelayOne - { - get => (RelayStatus)GetField(1, DataFields.StatusRelay1).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.StatusRelay1).SetValue(OpenProtocolConvert.ToString, value); - } - public RelayStatus StatusRelayTwo - { - get => (RelayStatus)GetField(1, DataFields.StatusRelay2).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.StatusRelay2).SetValue(OpenProtocolConvert.ToString, value); - } - public RelayStatus StatusRelayThree - { - get => (RelayStatus)GetField(1, DataFields.StatusRelay3).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.StatusRelay3).SetValue(OpenProtocolConvert.ToString, value); - } - public RelayStatus StatusRelayFour - { - get => (RelayStatus)GetField(1, DataFields.StatusRelay4).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.StatusRelay4).SetValue(OpenProtocolConvert.ToString, value); - } - public RelayStatus StatusRelayFive - { - get => (RelayStatus)GetField(1, DataFields.StatusRelay5).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.StatusRelay5).SetValue(OpenProtocolConvert.ToString, value); - } - public RelayStatus StatusRelaySix - { - get => (RelayStatus)GetField(1, DataFields.StatusRelay6).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.StatusRelay6).SetValue(OpenProtocolConvert.ToString, value); - } - public RelayStatus StatusRelaySeven - { - get => (RelayStatus)GetField(1, DataFields.StatusRelay7).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.StatusRelay7).SetValue(OpenProtocolConvert.ToString, value); - } - public RelayStatus StatusRelayEight - { - get => (RelayStatus)GetField(1, DataFields.StatusRelay8).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.StatusRelay8).SetValue(OpenProtocolConvert.ToString, value); - } - public RelayStatus StatusRelayNine - { - get => (RelayStatus)GetField(1, DataFields.StatusRelay9).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.StatusRelay9).SetValue(OpenProtocolConvert.ToString, value); - } - public RelayStatus StatusRelayTen - { - get => (RelayStatus)GetField(1, DataFields.StatusRelay10).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.StatusRelay10).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(field: 0, revision: 1, Size = 1, HasPrefix = false)] + public RelayStatus StatusRelayOne { get; set; } + [Int32DataFieldDefinition(field: 1, revision: 1, Size = 1, HasPrefix = false)] + public RelayStatus StatusRelayTwo { get; set; } + [Int32DataFieldDefinition(field: 2, revision: 1, Size = 1, HasPrefix = false)] + public RelayStatus StatusRelayThree { get; set; } + [Int32DataFieldDefinition(field: 3, revision: 1, Size = 1, HasPrefix = false)] + public RelayStatus StatusRelayFour { get; set; } + [Int32DataFieldDefinition(field: 4, revision: 1, Size = 1, HasPrefix = false)] + public RelayStatus StatusRelayFive { get; set; } + [Int32DataFieldDefinition(field: 5, revision: 1, Size = 1, HasPrefix = false)] + public RelayStatus StatusRelaySix { get; set; } + [Int32DataFieldDefinition(field: 6, revision: 1, Size = 1, HasPrefix = false)] + public RelayStatus StatusRelaySeven { get; set; } + [Int32DataFieldDefinition(field: 7, revision: 1, Size = 1, HasPrefix = false)] + public RelayStatus StatusRelayEight { get; set; } + [Int32DataFieldDefinition(field: 8, revision: 1, Size = 1, HasPrefix = false)] + public RelayStatus StatusRelayNine { get; set; } + [Int32DataFieldDefinition(field: 9, revision: 1, Size = 1, HasPrefix = false)] + public RelayStatus StatusRelayTen { get; set; } public Mid0200() : this(new Header() { @@ -79,28 +50,7 @@ public Mid0200(Header header) : base(header) { } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.StatusRelay1, 20, 1, false), - DataField.Number(DataFields.StatusRelay2, 21, 1, false), - DataField.Number(DataFields.StatusRelay3, 22, 1, false), - DataField.Number(DataFields.StatusRelay4, 23, 1, false), - DataField.Number(DataFields.StatusRelay5, 24, 1, false), - DataField.Number(DataFields.StatusRelay6, 25, 1, false), - DataField.Number(DataFields.StatusRelay7, 26, 1, false), - DataField.Number(DataFields.StatusRelay8, 27, 1, false), - DataField.Number(DataFields.StatusRelay9, 28, 1, false), - DataField.Number(DataFields.StatusRelay10, 29, 1, false) - } - } - }; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { StatusRelay1, diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0211.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0211.cs index 22eb08c4..aa1c175f 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0211.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0211.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.IOInterface { @@ -15,46 +16,22 @@ public class Mid0211 : Mid, IIOInterface, IController, IAcknowledgeable { public const int MID = 211; - public bool StatusDigInOne - { - get => GetField(1, DataFields.StatusDigIn1).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.StatusDigIn1).SetValue(OpenProtocolConvert.ToString, value); - } - public bool StatusDigInTwo - { - get => GetField(1, DataFields.StatusDigIn2).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.StatusDigIn2).SetValue(OpenProtocolConvert.ToString, value); - } - public bool StatusDigInThree - { - get => GetField(1, DataFields.StatusDigIn3).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.StatusDigIn3).SetValue(OpenProtocolConvert.ToString, value); - } - public bool StatusDigInFour - { - get => GetField(1, DataFields.StatusDigIn4).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.StatusDigIn4).SetValue(OpenProtocolConvert.ToString, value); - } - public bool StatusDigInFive - { - get => GetField(1, DataFields.StatusDigIn5).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.StatusDigIn5).SetValue(OpenProtocolConvert.ToString, value); - } - public bool StatusDigInSix - { - get => GetField(1, DataFields.StatusDigIn6).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.StatusDigIn6).SetValue(OpenProtocolConvert.ToString, value); - } - public bool StatusDigInSeven - { - get => GetField(1, DataFields.StatusDigIn7).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.StatusDigIn7).SetValue(OpenProtocolConvert.ToString, value); - } - public bool StatusDigInEight - { - get => GetField(1, DataFields.StatusDigIn8).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.StatusDigIn8).SetValue(OpenProtocolConvert.ToString, value); - } + [BooleanDataFieldDefinition(field: 0, revision: 1, HasPrefix = false)] + public bool StatusDigInOne { get; set; } + [BooleanDataFieldDefinition(field: 1, revision: 1, HasPrefix = false)] + public bool StatusDigInTwo { get; set; } + [BooleanDataFieldDefinition(field: 2, revision: 1, HasPrefix = false)] + public bool StatusDigInThree { get; set; } + [BooleanDataFieldDefinition(field: 3, revision: 1, HasPrefix = false)] + public bool StatusDigInFour { get; set; } + [BooleanDataFieldDefinition(field: 4, revision: 1, HasPrefix = false)] + public bool StatusDigInFive { get; set; } + [BooleanDataFieldDefinition(field: 5, revision: 1, HasPrefix = false)] + public bool StatusDigInSix { get; set; } + [BooleanDataFieldDefinition(field: 6, revision: 1, HasPrefix = false)] + public bool StatusDigInSeven { get; set; } + [BooleanDataFieldDefinition(field: 7, revision: 1, HasPrefix = false)] + public bool StatusDigInEight { get; set; } public Mid0211() : this(new Header() { @@ -69,26 +46,7 @@ public Mid0211(Header header) : base(header) { } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Boolean(DataFields.StatusDigIn1, 20, false), - DataField.Boolean(DataFields.StatusDigIn2, 21, false), - DataField.Boolean(DataFields.StatusDigIn3, 22, false), - DataField.Boolean(DataFields.StatusDigIn4, 23, false), - DataField.Boolean(DataFields.StatusDigIn5, 24, false), - DataField.Boolean(DataFields.StatusDigIn6, 25, false), - DataField.Boolean(DataFields.StatusDigIn7, 26, false), - DataField.Boolean(DataFields.StatusDigIn8, 27, false) - } - } - }; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { StatusDigIn1, diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0214.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0214.cs index c7c80ec1..949aca33 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0214.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0214.cs @@ -5,9 +5,9 @@ namespace OpenProtocolInterpreter.IOInterface /// /// IO device status request /// - /// Request for the status of the relays and digital inputs at a device, e.g. an I/O expander. + /// Request for the status of the relays and digital inputs at a device, e.g. an I/O expander. /// The device is specified by a device number. - /// + /// /// Message sent by: Integrator /// /// Answer: IO device status or @@ -20,6 +20,7 @@ public class Mid0214 : Mid, IIOInterface, IIntegrator, IAnswerableBy, I public IEnumerable DocumentedPossibleErrors => new Error[] { Error.FaultyIODeviceId, Error.IODeviceNotConnected }; + [Int32DataFieldDefinition(field: 0, revision: 1, Size = 2, HasPrefix = false)] public int DeviceNumber { get => GetField(1, DataFields.DeviceNumber).GetValue(OpenProtocolConvert.ToInt32); @@ -42,19 +43,6 @@ public Mid0214(int revision) : this(new Header() { } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.DeviceNumber, 20, 2, false) - } - } - }; - } - protected enum DataFields { DeviceNumber diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs index faea8fc8..12286f41 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Text; +using System.Threading.Tasks.Dataflow; namespace OpenProtocolInterpreter.IOInterface { @@ -23,24 +25,18 @@ public class Mid0215 : Mid, IIOInterface, IController { public const int MID = 215; - public int IODeviceId - { - get => GetField(Header.StandardizedRevision, DataFields.IODeviceId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(Header.StandardizedRevision, DataFields.IODeviceId).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(field: 1, revision: 1, Size = 2)] + public int IODeviceId { get; set; } + [RelayCollectionDefinition(field: 2, revision: 1, Size = 4 * 8)] public List Relays { get; set; } + [DigitalInputCollectionDefinition(field: 3, revision: 1, Size = 4 * 8)] public List DigitalInputs { get; set; } - //rev 2 - public int NumberOfRelays - { - get => GetField(2, DataFields.NumberOfRelays).GetValue(OpenProtocolConvert.ToInt32); - private set => GetField(2, DataFields.NumberOfRelays).SetValue(OpenProtocolConvert.ToString, value); - } - public int NumberOfDigitalInputs - { - get => GetField(2, DataFields.NumberOfDigitalInputs).GetValue(OpenProtocolConvert.ToInt32); - private set => GetField(2, DataFields.NumberOfDigitalInputs).SetValue(OpenProtocolConvert.ToString, value); - } + + //At revision 2 number of relays/digital inputs comes before their lists + [Int32DataFieldDefinition(field: 4, revision: 2, Index = 24, Size = 2)] + public int NumberOfRelays { get; set; } + [Int32DataFieldDefinition(field: 5, revision: 2, Index = 0, Size = 2)] + public int NumberOfDigitalInputs { get; set; } public Mid0215() : this(DEFAULT_REVISION) { @@ -62,126 +58,131 @@ public Mid0215(int revision) : this(new Header() } - protected override string BuildHeader() - { - if (RevisionsByFields.Any()) - { - Header.Length = 20; - var revision = Header.StandardizedRevision; - foreach (var dataField in RevisionsByFields[revision]) - Header.Length += (dataField.HasPrefix ? 2 : 0) + dataField.Size; - } - return Header.ToString(); - } - public override string Pack() { + HandleRevision(); if (Header.Revision > 1) { NumberOfRelays = Relays.Count; NumberOfDigitalInputs = DigitalInputs.Count; - var relayListField = GetField(2, DataFields.RelayList); - relayListField.Size = NumberOfRelays * 4; - relayListField.Value = PackRelays(); - GetField(2, DataFields.NumberOfDigitalInputs).Index = relayListField.Index + relayListField.Size; + GetField(revision: 1, field: 2).Size = NumberOfRelays * 4; + GetField(revision: 1, field: 3).Size = NumberOfDigitalInputs * 4; - GetField(2, DataFields.DigitalInputList).Index = GetField(2, DataFields.NumberOfDigitalInputs).Index + 2; - GetField(2, DataFields.DigitalInputList).Size = NumberOfDigitalInputs * 4; - GetField(2, DataFields.DigitalInputList).Value = PackDigitalInputs(); - } - else - { - GetField(1, DataFields.RelayList).Value = PackRelays(); - GetField(1, DataFields.DigitalInputList).Value = PackDigitalInputs(); - } + var builder = new StringBuilder(BuildHeader()); + int prefixIndex = 1; - var builder = new StringBuilder(BuildHeader()); - int prefixIndex = 1; - foreach (var field in RevisionsByFields[Header.StandardizedRevision]) - { - builder.Append(string.Concat(prefixIndex.ToString("D2"), field.Value)); - prefixIndex++; + var fields = OrderedDataFieldsByRevision().ToList(); + builder.Append(base.Pack(fields, ref prefixIndex)); + return builder.ToString(); } - return builder.ToString(); + + return base.Pack(); } - public override Mid Parse(ReadOnlySpan package) + protected override void ProcessDataFields(ReadOnlySpan package) { - Header = ProcessHeader(package); - - var revision = Header.StandardizedRevision; + HandleRevision(); + var fields = OrderedDataFieldsByRevision(); + var enumerator = fields.GetEnumerator(); - var relayListField = GetField(revision, DataFields.RelayList); - var digitalListField = GetField(revision, DataFields.DigitalInputList); - if (revision > 1) + var index = 0; + while (enumerator.MoveNext()) { - int numberOfRelays = OpenProtocolConvert.ToInt32(GetValue(GetField(2, DataFields.NumberOfRelays), package).ToString()); - relayListField.Size = numberOfRelays * 4; - - var numberOfDigitalInputsField = GetField(2, DataFields.NumberOfDigitalInputs); - numberOfDigitalInputsField.Index = relayListField.Index + 2 + relayListField.Size; + index++; + var field = enumerator.Current; + base.ProcessDataField(field, package); + if (Header.StandardizedRevision <= 1) + continue; - digitalListField.Index = numberOfDigitalInputsField.Index + 2 + numberOfDigitalInputsField.Size; - digitalListField.Size = Header.Length - 2 - digitalListField.Index; + if (index == 2) + { + var relays = GetField(revision: 1, field: 2); + relays.Index = field.Index + field.TotalSize; + relays.Size = NumberOfRelays * 4; + } + else if (index == 3) + { + var numberOfDigitalInputs = GetField(revision: 2, field: 5); + numberOfDigitalInputs.Index = field.Index + field.TotalSize; + } + else if (index == 4) + { + var digitalInputs = GetField(revision: 1, field: 3); + digitalInputs.Index = field.Index + field.TotalSize; + digitalInputs.Size = NumberOfDigitalInputs * 4; + } } - - ProcessDataFields(package); - - Relays = Relay.ParseAll(relayListField.Value).ToList(); - DigitalInputs = DigitalInput.ParseAll(digitalListField.Value).ToList(); - return this; } - protected virtual string PackRelays() + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - var value = new StringBuilder(); - foreach (var relay in Relays) - value.Append(relay.Pack()); + base.ProcessDataField(dataField, package); + if (Header.StandardizedRevision <= 1) + return; + + if (dataField.Field == 2) + { + var relays = GetField(revision: 1, field: 2); + relays.Index = dataField.Index + dataField.TotalSize; + relays.Size = NumberOfRelays * 4; - return value.ToString(); + var numberOfDigitalInputs = GetField(revision: 2, field: 4); + numberOfDigitalInputs.Index = relays.Index + relays.TotalSize; + } + else if (dataField.Field == 4) + { + var digitalInputs = GetField(revision: 1, field: 3); + digitalInputs.Index = dataField.Index + dataField.TotalSize; + digitalInputs.Size = NumberOfDigitalInputs * 4; + } } - protected virtual string PackDigitalInputs() + private void HandleRevision() { - var value = new StringBuilder(); - foreach (var digitalInput in DigitalInputs) - value.Append(digitalInput.Pack()); - - return value.ToString(); + if (Header.StandardizedRevision == 1) + { + EnsureEightRelaysAndDigitalInputs(); + } } - protected override Dictionary> RegisterDatafields() + private IEnumerable OrderedDataFieldsByRevision() { - return new Dictionary>() + var revision = Header.StandardizedRevision; + if (revision == 1) { - { - 1, new List() - { - DataField.Number(DataFields.IODeviceId, 20, 2), - new(DataFields.RelayList, 24, 32), - new(DataFields.DigitalInputList, 58, 32) - } - }, - { - 2, new List() - { - DataField.Number(DataFields.IODeviceId, 20, 2), - DataField.Number(DataFields.NumberOfRelays, 24, 2), - DataField.Volatile(DataFields.RelayList, 28), - new(DataFields.NumberOfDigitalInputs, 0, 2, '0', PaddingOrientation.LeftPadded), - DataField.Volatile(DataFields.DigitalInputList) - } - } - }; + yield return GetField(revision: 1, field: 1); + yield return GetField(revision: 1, field: 2); + yield return GetField(revision: 1, field: 3); + } + else + { + yield return GetField(revision: 1, field: 1); + yield return GetField(revision: 2, field: 4); + yield return GetField(revision: 1, field: 2); + yield return GetField(revision: 2, field: 5); + yield return GetField(revision: 1, field: 3); + } + } + + private void EnsureEightRelaysAndDigitalInputs() + { + GetField(revision: 1, field: 2).Size = 4 * 8; + for (int i = Relays.Count; i < 8; i++) + Relays.Add(new Relay(RelayNumber.Off, false)); + + GetField(revision: 1, field: 3).Size = 4 * 8; + for (int i = DigitalInputs.Count; i < 8; i++) + DigitalInputs.Add(new DigitalInput(DigitalInputNumber.Off, false)); } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { IODeviceId, RelayList, DigitalInputList, - //rev2 + //rev2 NumberOfRelays, NumberOfDigitalInputs } diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0216.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0216.cs index 786b8c04..1251a2c5 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0216.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0216.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.IOInterface { @@ -11,7 +12,7 @@ namespace OpenProtocolInterpreter.IOInterface /// status to the subscriber. /// can only subscribe for one single relay function at a time, but still, Open Protocol supports /// keeping several relay function subscriptions simultaneously. - /// + /// /// Message sent by: Integrator /// Answer: Command accepted or Command error, The relay function subscription already exists /// @@ -21,11 +22,8 @@ public class Mid0216 : Mid, IIOInterface, IIntegrator, ISubscription, IAcceptabl public IEnumerable DocumentedPossibleErrors => new Error[] { Error.RelayFunctionSubscriptionAlreadyExists }; - public RelayNumber RelayNumber - { - get => (RelayNumber)GetField(1, DataFields.RelayNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.RelayNumber).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(field: 1, revision: 1, Size = 3, HasPrefix = false)] + public RelayNumber RelayNumber { get; set; } public Mid0216() : this(false) { @@ -43,22 +41,10 @@ public Mid0216(bool noAckFlag = false) : this(new Header() NoAckFlag = noAckFlag }) { - - } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.RelayNumber, 20, 3, false) - } - } - }; } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { RelayNumber diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0217.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0217.cs index ef8b8d1e..a626bc1f 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0217.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0217.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.IOInterface { @@ -17,16 +18,10 @@ public class Mid0217 : Mid, IIOInterface, IController, IAcknowledgeable { public const int MID = 217; - public RelayNumber RelayNumber - { - get => (RelayNumber)GetField(1, DataFields.RelayNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.RelayNumber).SetValue(OpenProtocolConvert.ToString, value); - } - public bool RelayStatus - { - get => GetField(1, DataFields.RelayStatus).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.RelayStatus).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(field: 1, revision: 1, Size = 3)] + public RelayNumber RelayNumber { get; set; } + [BooleanDataFieldDefinition(field: 2, revision: 1)] + public bool RelayStatus { get; set; } public Mid0217() : this(new Header() { @@ -41,20 +36,7 @@ public Mid0217(Header header) : base(header) { } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.RelayNumber, 20, 3), - DataField.Boolean(DataFields.RelayStatus, 25) - } - } - }; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { RelayNumber, diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0219.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0219.cs index e096f11c..c2d9eafd 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0219.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0219.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; namespace OpenProtocolInterpreter.IOInterface { @@ -6,7 +8,7 @@ namespace OpenProtocolInterpreter.IOInterface /// Relay function unsubscribe /// /// Unsubscribe for a single relay function. The data field consists of three ASCII digits, - /// the relay number, which corresponds to the specific relay function. The relay numbers can be + /// the relay number, which corresponds to the specific relay function. The relay numbers can be /// found in Table 101. /// /// Message sent by: Integrator @@ -18,11 +20,8 @@ public class Mid0219 : Mid, IIOInterface, IIntegrator, IUnsubscription, IAccepta public IEnumerable DocumentedPossibleErrors => new Error[] { Error.RelayFunctionSubscriptionDoesntExists }; - public RelayNumber RelayNumber - { - get => (RelayNumber)GetField(1, DataFields.RelayNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.RelayNumber).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(field: 1, revision: 1, Size = 3, HasPrefix = false)] + public RelayNumber RelayNumber { get; set; } public Mid0219() : this(new Header() { @@ -37,19 +36,7 @@ public Mid0219(Header header) : base(header) { } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.RelayNumber, 20, 3, false) - } - } - }; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { RelayNumber diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0220.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0220.cs index 94d3cead..a90296b9 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0220.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0220.cs @@ -1,17 +1,18 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.IOInterface { /// /// Digital input function subscribe /// - /// Subscribe for one single digital input function. The data field consists of three ASCII digits, + /// Subscribe for one single digital input function. The data field consists of three ASCII digits, /// the digital input function number. The digital input function numbers can be found in Table 80 above. - /// At a subscription of a tracking event, Digital input function upload immediately returns the + /// At a subscription of a tracking event, Digital input function upload immediately returns the /// current digital input function status to the subscriber. /// /// - /// can only subscribe for one single digital input function at a time, + /// can only subscribe for one single digital input function at a time, /// but still, Open Protocol supports keeping several digital input function subscriptions simultaneously. /// /// Message sent by: Integrator @@ -21,13 +22,10 @@ public class Mid0220 : Mid, IIOInterface, IIntegrator, ISubscription, IAcceptabl { public const int MID = 220; - public IEnumerable DocumentedPossibleErrors => new Error[] { }; + public IEnumerable DocumentedPossibleErrors => new Error[] { }; - public DigitalInputNumber DigitalInputNumber - { - get => (DigitalInputNumber)GetField(1, DataFields.DigitalInputNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.DigitalInputNumber).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(field: 1, revision: 1, Size = 3, HasPrefix = false)] + public DigitalInputNumber DigitalInputNumber { get; set; } public Mid0220() : this(false) { @@ -48,19 +46,7 @@ public Mid0220(bool noAckFlag = false) : this(new Header() } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.DigitalInputNumber, 20, 3, false) - } - } - }; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { DigitalInputNumber diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0221.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0221.cs index 3d63bfe2..cd9df683 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0221.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0221.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.IOInterface { @@ -8,7 +9,7 @@ namespace OpenProtocolInterpreter.IOInterface /// Upload of one specific digital input function status. See Table 80. /// For tracking event functions, Digital input function, is sent each time the digital input /// function’s status (state) is changed. For digital input functions which are not tracking events, the - /// upload is sent only when the digital input function is set high, + /// upload is sent only when the digital input function is set high, /// i.e. the data field “Digital input function status” will always be 1 for such functions. /// /// Message sent by: Controller @@ -18,16 +19,10 @@ public class Mid0221 : Mid, IIOInterface, IController, IAcknowledgeable { public const int MID = 221; - public DigitalInputNumber DigitalInputNumber - { - get => (DigitalInputNumber)GetField(1, DataFields.DigitalInputNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.DigitalInputNumber).SetValue(OpenProtocolConvert.ToString, value); - } - public bool DigitalInputStatus - { - get => GetField(1, DataFields.DigitalInputStatus).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.DigitalInputStatus).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(field: 1, revision: 1, Size = 3)] + public DigitalInputNumber DigitalInputNumber { get; set; } + [BooleanDataFieldDefinition(field: 2, revision: 1)] + public bool DigitalInputStatus { get; set; } public Mid0221() : this(new Header() { @@ -41,20 +36,7 @@ public Mid0221(Header header) : base(header) { } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.DigitalInputNumber, 20, 3), - DataField.Boolean(DataFields.DigitalInputStatus, 25) - } - } - }; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { DigitalInputNumber, diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0223.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0223.cs index 539bb5c7..454db7fb 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0223.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0223.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.IOInterface { @@ -6,9 +7,9 @@ namespace OpenProtocolInterpreter.IOInterface /// Relay function unsubscribe /// /// Unsubscribe for a single relay function. The data field consists of three ASCII digits, - /// the relay number, which corresponds to the specific relay function. The relay numbers can be + /// the relay number, which corresponds to the specific relay function. The relay numbers can be /// found in Table 101. - /// + /// /// Message sent by: Integrator /// Answer: Command accepted or Command error, The relay function subscription does not exist /// @@ -18,11 +19,8 @@ public class Mid0223 : Mid, IIOInterface, IIntegrator, IUnsubscription, IAccepta public IEnumerable DocumentedPossibleErrors => new Error[] { Error.RelayFunctionSubscriptionDoesntExists }; - public DigitalInputNumber DigitalInputNumber - { - get => (DigitalInputNumber)GetField(1, DataFields.DigitalInputNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.DigitalInputNumber).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(field: 1, revision: 1, Size = 3, HasPrefix = false)] + public DigitalInputNumber DigitalInputNumber { get; set; } public Mid0223() : this(new Header() { @@ -37,19 +35,7 @@ public Mid0223(Header header) : base(header) { } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.DigitalInputNumber, 20, 3, false) - } - } - }; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { DigitalInputNumber diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0224.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0224.cs index e48f7f7c..a332613e 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0224.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0224.cs @@ -1,11 +1,12 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.IOInterface { /// /// Set digital input function /// - /// Set the digital input function with the digital input number. + /// Set the digital input function with the digital input number. /// The digital input function numbers are defined in Table 80. /// /// Message sent by: Integrator @@ -17,11 +18,8 @@ public class Mid0224 : Mid, IIOInterface, IIntegrator, IAcceptableCommand, IDecl public IEnumerable DocumentedPossibleErrors => new Error[] { Error.InvalidData }; - public DigitalInputNumber DigitalInputNumber - { - get => (DigitalInputNumber)GetField(1, DataFields.DigitalInputNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.DigitalInputNumber).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(field: 1, revision: 1, Size = 3, HasPrefix = false)] + public DigitalInputNumber DigitalInputNumber { get; set; } public Mid0224() : this(new Header() { @@ -36,19 +34,7 @@ public Mid0224(Header header) : base(header) { } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.DigitalInputNumber, 20, 3, false) - } - } - }; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { DigitalInputNumber diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0225.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0225.cs index c4d8c82e..ba0ba82f 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0225.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0225.cs @@ -1,16 +1,17 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.IOInterface { /// /// Reset digital input function /// - /// Reset the digital input function with the digital input number. + /// Reset the digital input function with the digital input number. /// The digital input function numbers are defined in Table 80. - /// + /// /// /// This MID will only affect the digital input functions of tracking type. - /// The digital input functions with the type flank cannot be reset (for example reset the reset + /// The digital input functions with the type flank cannot be reset (for example reset the reset /// batch digital input function will have no effect). /// /// Message sent by: Integrator @@ -22,11 +23,9 @@ public class Mid0225 : Mid, IIOInterface, IIntegrator, IAcceptableCommand, IDecl public IEnumerable DocumentedPossibleErrors => new Error[] { Error.InvalidData }; - public DigitalInputNumber DigitalInputNumber - { - get => (DigitalInputNumber)GetField(1, DataFields.DigitalInputNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.DigitalInputNumber).SetValue(OpenProtocolConvert.ToString, value); - } + + [Int32DataFieldDefinition(field: 1, revision: 1, Size = 3, HasPrefix = false)] + public DigitalInputNumber DigitalInputNumber { get; set; } public Mid0225() : this(new Header() { @@ -41,19 +40,7 @@ public Mid0225(Header header) : base(header) { } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.DigitalInputNumber, 20, 3, false) - } - } - }; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { DigitalInputNumber diff --git a/src/OpenProtocolInterpreter/IOInterface/Relay.cs b/src/OpenProtocolInterpreter/IOInterface/Relay.cs index 5d68ba48..7626eab8 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Relay.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Relay.cs @@ -1,5 +1,8 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; namespace OpenProtocolInterpreter.IOInterface { @@ -11,6 +14,17 @@ public class Relay public RelayNumber Number { get; set; } public bool Status { get; set; } + public Relay() + { + + } + + public Relay(RelayNumber number, bool status) + { + Number = number; + Status = status; + } + public string Pack() { return OpenProtocolConvert.ToString('0', 3, PaddingOrientation.LeftPadded, (int)Number) + @@ -37,7 +51,7 @@ public static Relay Parse(ReadOnlySpan section) public static IEnumerable ParseAll(string value) { - if(string.IsNullOrEmpty(value)) + if (string.IsNullOrEmpty(value)) { yield break; } @@ -53,13 +67,46 @@ public static IEnumerable ParseAll(string value) public static IEnumerable ParseAll(ReadOnlySpan value) { if (value.IsEmpty) - return System.Array.Empty(); + return Array.Empty(); - var result = new System.Collections.Generic.List(); + var result = new List(); const int sectionSize = 4; for (int i = 0; i < value.Length; i += sectionSize) result.Add(Parse(value.Slice(i, sectionSize))); return result; } } + + public class RelayCollectionDefinitionAttribute : DataFieldDefinitionAttribute + { + public RelayCollectionDefinitionAttribute(int revision) : base(revision) + { + + } + public RelayCollectionDefinitionAttribute(int field, int revision) : base(field, revision) + { + + } + + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + { + return new DataField>(Field, index, Size, HasPrefix) + { + DefaultConverter = PackRelays, + DefaultParser = ParseRelays + }.Bind(mid, propertyInfo); + } + + private static string PackRelays(char paddingChar, int size, PaddingOrientation orientation, List relays) + { + var builder = new StringBuilder(relays.Count * 4); + foreach (var relay in relays) + builder.Append(relay.Pack()); + + return builder.ToString(); + } + + private static List ParseRelays(string value) + => Relay.ParseAll(value).ToList(); + } } diff --git a/src/OpenProtocolInterpreter/MID.cs b/src/OpenProtocolInterpreter/MID.cs index 304ed6f1..75a604d2 100644 --- a/src/OpenProtocolInterpreter/MID.cs +++ b/src/OpenProtocolInterpreter/MID.cs @@ -52,7 +52,7 @@ protected virtual string BuildHeader() if (RevisionsByFields.TryGetValue(i, out var dataFields)) { foreach (var dataField in dataFields) - Header.Length += (dataField.HasPrefix ? 2 : 0) + dataField.Size; + Header.Length += dataField.TotalSize; } } } @@ -94,7 +94,7 @@ protected virtual string Pack(List dataFields, ref int prefixIndex) foreach (var dataField in dataFields) { if (dataField is IBackedPropertyDataField backedPropertyDataField) - backedPropertyDataField.SyncWithBackingProperty(); + backedPropertyDataField.SyncWithBackingPropertyIfBound(); if (dataField.HasPrefix) { @@ -108,11 +108,6 @@ protected virtual string Pack(List dataFields, ref int prefixIndex) return builder.ToString(); } - protected virtual Dictionary> RegisterDatafieldsDefinitions() - { - return new Dictionary>(); - } - /// /// Registers the data fields for the MID, ordered by their revision and field id. This method is called lazily when parsing or packing a MID to ensure revision defined at header. /// Each data field should be decorated with a to define its specifications. From 12929c925393abfe3ab77606b82faa063ff0559a Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Thu, 9 Jul 2026 14:08:05 -0300 Subject: [PATCH 36/78] Using dataattributes at mid0061 --- src/MIDTesters.Core/Tightening/TestMid0061.cs | 36 +- src/OpenProtocolInterpreter/DataField.cs | 11 + .../DataFieldDefinitionAttribute.cs | 26 +- .../IOInterface/Mid0211.cs | 16 +- .../IOInterface/Mid0215.cs | 49 +- src/OpenProtocolInterpreter/MID.cs | 32 +- .../OpenProtocolConvert.cs | 21 +- .../Tightening/Mid0061.cs | 1236 ++++++++--------- .../Tightening/StageResult.cs | 36 + .../Tightening/StrategyOptions.cs | 34 +- .../Tightening/TighteningErrorStatus.cs | 56 + 11 files changed, 814 insertions(+), 739 deletions(-) diff --git a/src/MIDTesters.Core/Tightening/TestMid0061.cs b/src/MIDTesters.Core/Tightening/TestMid0061.cs index 739dd7f3..32c8ae7e 100644 --- a/src/MIDTesters.Core/Tightening/TestMid0061.cs +++ b/src/MIDTesters.Core/Tightening/TestMid0061.cs @@ -154,7 +154,7 @@ public void Mid0061ByteRevision2() { 0xAA, //1010 1010 0x03, //0000 0011 - 0x0A, //0000 1010 + 0x0A, //0000 1010 0xD1, //1101 0001 0x00, 0x00, @@ -303,7 +303,7 @@ public void Mid0061ByteRevision3() { 0xAA, //1010 1010 0x03, //0000 0011 - 0x0A, //0000 1010 + 0x0A, //0000 1010 0xD1, //1101 0001 0x00, 0x00, @@ -458,7 +458,7 @@ public void Mid0061ByteRevision4() { 0xAA, //1010 1010 0x03, //0000 0011 - 0x0A, //0000 1010 + 0x0A, //0000 1010 0xD1, //1101 0001 0x00, 0x00, @@ -617,7 +617,7 @@ public void Mid0061ByteRevision5() { 0xAA, //1010 1010 0x03, //0000 0011 - 0x0A, //0000 1010 + 0x0A, //0000 1010 0xD1, //1101 0001 0x00, 0x00, @@ -779,7 +779,7 @@ public void Mid0061ByteRevision6() { 0xAA, //1010 1010 0x03, //0000 0011 - 0x0A, //0000 1010 + 0x0A, //0000 1010 0xD1, //1101 0001 0x00, 0x00, @@ -792,7 +792,7 @@ public void Mid0061ByteRevision6() var tighteningErrorStatus2 = new byte[] //10 bytes long { 0xAA, //1010 1010 - 0x03, //0000 0011 + 0x03, //0000 0011 0x02, //0000 0010 0x00, //Reserved from bit 19 to rest 0x00, @@ -962,7 +962,7 @@ public void Mid0061ByteRevision7() { 0xAA, //1010 1010 0x03, //0000 0011 - 0x0A, //0000 1010 + 0x0A, //0000 1010 0xD1, //1101 0001 0x00, 0x00, @@ -975,7 +975,7 @@ public void Mid0061ByteRevision7() var tighteningErrorStatus2 = new byte[] //10 bytes long { 0xAA, //1010 1010 - 0x03, //0000 0011 + 0x03, //0000 0011 0x02, //0000 0010 0x00, //Reserved from bit 19 to rest 0x00, @@ -1154,7 +1154,7 @@ public void Mid0061ByteRevision8() { 0xAA, //1010 1010 0x03, //0000 0011 - 0x0A, //0000 1010 + 0x0A, //0000 1010 0xD1, //1101 0001 0x00, 0x00, @@ -1167,7 +1167,7 @@ public void Mid0061ByteRevision8() var tighteningErrorStatus2 = new byte[] //10 bytes long { 0xAA, //1010 1010 - 0x03, //0000 0011 + 0x03, //0000 0011 0x02, //0000 0010 0x00, //Reserved from bit 19 to rest 0x00, @@ -1353,7 +1353,7 @@ public void Mid0061ByteRevision9() { 0xAA, //1010 1010 0x03, //0000 0011 - 0x0A, //0000 1010 + 0x0A, //0000 1010 0xD1, //1101 0001 0x00, 0x00, @@ -1366,7 +1366,7 @@ public void Mid0061ByteRevision9() var tighteningErrorStatus2 = new byte[] //10 bytes long { 0xAA, //1010 1010 - 0x03, //0000 0011 + 0x03, //0000 0011 0x02, //0000 0010 0x00, //Reserved from bit 19 to rest 0x00, @@ -1565,7 +1565,7 @@ public void Mid0061ByteRevision10() { 0xAA, //1010 1010 0x03, //0000 0011 - 0x0A, //0000 1010 + 0x0A, //0000 1010 0xD1, //1101 0001 0x00, 0x00, @@ -1578,7 +1578,7 @@ public void Mid0061ByteRevision10() var tighteningErrorStatus2 = new byte[] //10 bytes long { 0xAA, //1010 1010 - 0x03, //0000 0011 + 0x03, //0000 0011 0x02, //0000 0010 0x00, //Reserved from bit 19 to rest 0x00, @@ -1789,7 +1789,7 @@ public void Mid0061ByteRevision11() { 0xAA, //1010 1010 0x03, //0000 0011 - 0x0A, //0000 1010 + 0x0A, //0000 1010 0xD1, //1101 0001 0x00, 0x00, @@ -1802,7 +1802,7 @@ public void Mid0061ByteRevision11() var tighteningErrorStatus2 = new byte[] //10 bytes long { 0xAA, //1010 1010 - 0x03, //0000 0011 + 0x03, //0000 0011 0x02, //0000 0010 0x00, //Reserved from bit 19 to rest 0x00, @@ -1998,7 +1998,7 @@ public void Mid0061ByteRevision998() { 0xAA, //1010 1010 0x03, //0000 0011 - 0x0A, //0000 1010 + 0x0A, //0000 1010 0xD1, //1101 0001 0x00, 0x00, @@ -2011,7 +2011,7 @@ public void Mid0061ByteRevision998() var tighteningErrorStatus2 = new byte[] //10 bytes long { 0xAA, //1010 1010 - 0x03, //0000 0011 + 0x03, //0000 0011 0x02, //0000 0010 0x00, //Reserved from bit 19 to rest 0x00, diff --git a/src/OpenProtocolInterpreter/DataField.cs b/src/OpenProtocolInterpreter/DataField.cs index 3ac4a0bd..68a1630f 100644 --- a/src/OpenProtocolInterpreter/DataField.cs +++ b/src/OpenProtocolInterpreter/DataField.cs @@ -144,6 +144,17 @@ public static DataField Decimal(int field, int index, int size, bool ha public static DataField Decimal(Enum field, int index, int size, bool hasPrefix = true) => Decimal(field.GetHashCode(), index, size, hasPrefix); + public static DataField TruncatedDecimal(int field, int index, int size, int decimalPoints, bool hasPrefix = true) + { + return new DataField(field, index, size, '0', PaddingOrientation.LeftPadded, hasPrefix) + { + DefaultConverter = OpenProtocolConvert.TruncatedDecimalToString, + DefaultParser = (str) => OpenProtocolConvert.ToTruncatedDecimal(str, decimalPoints) + }; + } + public static DataField TruncatedDecimal(Enum field, int index, int size, int decimalPoints, bool hasPrefix = true) + => TruncatedDecimal(field.GetHashCode(), index, size, decimalPoints, hasPrefix); + public static DataField Int32(int field, int index, int size, bool hasPrefix = true) { return new DataField(field, index, size, '0', PaddingOrientation.LeftPadded, hasPrefix) diff --git a/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs b/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs index 80baca53..b092bef0 100644 --- a/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs +++ b/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs @@ -11,6 +11,7 @@ namespace OpenProtocolInterpreter /// This attribute is used to decorate properties in a MID class to define how they should be parsed and formatted in the MID message. /// /// + [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] public class DataFieldDefinitionAttribute : Attribute { public int Field { get; set; } @@ -58,7 +59,7 @@ public BooleanDataFieldDefinitionAttribute(int field, int revision) : base(field } internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) { - return DataField.Boolean(Field, index, HasPrefix) + return DataField.Boolean(Field, Index, HasPrefix) .Bind(mid, propertyInfo); } } @@ -132,6 +133,29 @@ internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) } } + public class TruncatedDecimalDataFieldDefinitionAttribute : DataFieldDefinitionAttribute + { + public int DecimalPoints { get; set; } + + public TruncatedDecimalDataFieldDefinitionAttribute(int revision) : base(revision) + { + DecimalPoints = 2; + PaddingChar = '0'; + PaddingOrientation = PaddingOrientation.LeftPadded; + } + public TruncatedDecimalDataFieldDefinitionAttribute(int field, int revision) : base(field, revision) + { + DecimalPoints = 2; + PaddingChar = '0'; + PaddingOrientation = PaddingOrientation.LeftPadded; + } + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + { + return DataField.TruncatedDecimal(Field, index, Size, DecimalPoints, HasPrefix) + .Bind(mid, propertyInfo); + } + } + public class TimestampDataFieldDefinitionAttribute : DataFieldDefinitionAttribute { public TimestampDataFieldDefinitionAttribute(int revision) : base(revision) diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0211.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0211.cs index aa1c175f..3b33ea9a 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0211.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0211.cs @@ -16,21 +16,21 @@ public class Mid0211 : Mid, IIOInterface, IController, IAcknowledgeable { public const int MID = 211; - [BooleanDataFieldDefinition(field: 0, revision: 1, HasPrefix = false)] - public bool StatusDigInOne { get; set; } [BooleanDataFieldDefinition(field: 1, revision: 1, HasPrefix = false)] - public bool StatusDigInTwo { get; set; } + public bool StatusDigInOne { get; set; } [BooleanDataFieldDefinition(field: 2, revision: 1, HasPrefix = false)] - public bool StatusDigInThree { get; set; } + public bool StatusDigInTwo { get; set; } [BooleanDataFieldDefinition(field: 3, revision: 1, HasPrefix = false)] - public bool StatusDigInFour { get; set; } + public bool StatusDigInThree { get; set; } [BooleanDataFieldDefinition(field: 4, revision: 1, HasPrefix = false)] - public bool StatusDigInFive { get; set; } + public bool StatusDigInFour { get; set; } [BooleanDataFieldDefinition(field: 5, revision: 1, HasPrefix = false)] - public bool StatusDigInSix { get; set; } + public bool StatusDigInFive { get; set; } [BooleanDataFieldDefinition(field: 6, revision: 1, HasPrefix = false)] - public bool StatusDigInSeven { get; set; } + public bool StatusDigInSix { get; set; } [BooleanDataFieldDefinition(field: 7, revision: 1, HasPrefix = false)] + public bool StatusDigInSeven { get; set; } + [BooleanDataFieldDefinition(field: 8, revision: 1, HasPrefix = false)] public bool StatusDigInEight { get; set; } public Mid0211() : this(new Header() diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs index 12286f41..d027747c 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs @@ -66,8 +66,8 @@ public override string Pack() NumberOfRelays = Relays.Count; NumberOfDigitalInputs = DigitalInputs.Count; - GetField(revision: 1, field: 2).Size = NumberOfRelays * 4; - GetField(revision: 1, field: 3).Size = NumberOfDigitalInputs * 4; + GetField(nameof(Relays)).Size = NumberOfRelays * 4; + GetField(nameof(DigitalInputs)).Size = NumberOfDigitalInputs * 4; var builder = new StringBuilder(BuildHeader()); int prefixIndex = 1; @@ -97,47 +97,24 @@ protected override void ProcessDataFields(ReadOnlySpan package) if (index == 2) { - var relays = GetField(revision: 1, field: 2); + var relays = GetField(nameof(Relays)); relays.Index = field.Index + field.TotalSize; relays.Size = NumberOfRelays * 4; } else if (index == 3) { - var numberOfDigitalInputs = GetField(revision: 2, field: 5); + var numberOfDigitalInputs = GetField(nameof(NumberOfDigitalInputs)); numberOfDigitalInputs.Index = field.Index + field.TotalSize; } else if (index == 4) { - var digitalInputs = GetField(revision: 1, field: 3); + var digitalInputs = GetField(nameof(DigitalInputs)); digitalInputs.Index = field.Index + field.TotalSize; digitalInputs.Size = NumberOfDigitalInputs * 4; } } } - protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) - { - base.ProcessDataField(dataField, package); - if (Header.StandardizedRevision <= 1) - return; - - if (dataField.Field == 2) - { - var relays = GetField(revision: 1, field: 2); - relays.Index = dataField.Index + dataField.TotalSize; - relays.Size = NumberOfRelays * 4; - - var numberOfDigitalInputs = GetField(revision: 2, field: 4); - numberOfDigitalInputs.Index = relays.Index + relays.TotalSize; - } - else if (dataField.Field == 4) - { - var digitalInputs = GetField(revision: 1, field: 3); - digitalInputs.Index = dataField.Index + dataField.TotalSize; - digitalInputs.Size = NumberOfDigitalInputs * 4; - } - } - private void HandleRevision() { if (Header.StandardizedRevision == 1) @@ -151,17 +128,17 @@ private IEnumerable OrderedDataFieldsByRevision() var revision = Header.StandardizedRevision; if (revision == 1) { - yield return GetField(revision: 1, field: 1); - yield return GetField(revision: 1, field: 2); - yield return GetField(revision: 1, field: 3); + yield return GetField(nameof(IODeviceId)); + yield return GetField(nameof(Relays)); + yield return GetField(nameof(DigitalInputs)); } else { - yield return GetField(revision: 1, field: 1); - yield return GetField(revision: 2, field: 4); - yield return GetField(revision: 1, field: 2); - yield return GetField(revision: 2, field: 5); - yield return GetField(revision: 1, field: 3); + yield return GetField(nameof(IODeviceId)); + yield return GetField(nameof(NumberOfRelays)); + yield return GetField(nameof(Relays)); + yield return GetField(nameof(NumberOfDigitalInputs)); + yield return GetField(nameof(DigitalInputs)); } } diff --git a/src/OpenProtocolInterpreter/MID.cs b/src/OpenProtocolInterpreter/MID.cs index 75a604d2..706f3a63 100644 --- a/src/OpenProtocolInterpreter/MID.cs +++ b/src/OpenProtocolInterpreter/MID.cs @@ -121,17 +121,24 @@ protected virtual Dictionary> RegisterDatafields() { var result = new List(); var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) - .OrderBy(x => x.GetCustomAttribute()?.Field ?? x.MetadataToken); + .Where(x => + { + return x.CustomAttributes.Any(a => a.AttributeType.IsAssignableTo(typeof(DataFieldDefinitionAttribute))); + }); int fieldIndex = 20; foreach (var prop in properties) { - var attr = prop.GetCustomAttribute(); - if (attr == null) + var attributes = prop.GetCustomAttributes(); + if (!attributes.Any()) continue; - fieldIndex = attr.Index > 0 ? attr.Index : fieldIndex; //enforced index if defined in attribute - result.Add(new DataFieldMetadata(fieldIndex, attr, prop)); - fieldIndex += attr.Size + (attr.HasPrefix ? 2 : 0); + foreach (var attr in attributes) + { + fieldIndex = attr.Index > 0 ? attr.Index : fieldIndex; //enforced index if defined in attribute + attr.Index = fieldIndex; + result.Add(new DataFieldMetadata(fieldIndex, attr, prop)); + fieldIndex += attr.Size + (attr.HasPrefix ? 2 : 0); + } } return result.ToArray(); }); @@ -294,6 +301,19 @@ protected DataField GetField(int revision, int field) return fields.FirstOrDefault(x => x.Field == field) ?? DataField.Default; } + protected DataField GetField(string propertyName) + { + var type = GetType(); + if (!_metadataCache.TryGetValue(type, out var metadata) && !_lazyFields.IsValueCreated) + { + _ = _lazyFields.Value; // Force initialization of the lazy fields to populate the metadata cache + _metadataCache.TryGetValue(type, out metadata); + } + + var dataField = metadata.First(x => x.Property.Name == propertyName); + return RevisionsByFields[dataField.Attribute.Revision].First(x => x.Field == dataField.Attribute.Field); + } + protected DataField GetField(int revision, TEnum field) where TEnum : struct, Enum => GetField(revision, field.GetHashCode()); diff --git a/src/OpenProtocolInterpreter/OpenProtocolConvert.cs b/src/OpenProtocolInterpreter/OpenProtocolConvert.cs index d22cfbe1..4b3990ff 100644 --- a/src/OpenProtocolInterpreter/OpenProtocolConvert.cs +++ b/src/OpenProtocolInterpreter/OpenProtocolConvert.cs @@ -80,7 +80,7 @@ public static string TruncatedDecimalToString(decimal value) public static string TruncatedDecimalToString(decimal value, int decimalDigits) { - var decimals = 10m * decimalDigits; + var decimals = (decimal)Math.Pow(10, decimalDigits); int convertedValue = (int)(Math.Round(value, 2) * decimals); return convertedValue.ToString(); } @@ -108,7 +108,7 @@ public static decimal ToTruncatedDecimal(ReadOnlySpan value) public static decimal ToTruncatedDecimal(ReadOnlySpan value, int decimalDigits) { - var decimals = 10m * decimalDigits; + var decimals = (decimal)Math.Pow(10, decimalDigits); int intValue = ToInt32(value); return intValue / decimals; } @@ -120,7 +120,14 @@ public static string ToString(TEnum value) where TEnum : struct, Enum => ToString(value.GetHashCode()); public static string ToString(char paddingChar, int size, PaddingOrientation orientation, int value) - => TruncatePadded(paddingChar, size, orientation, ToString(value)); + { + if (value < 0) + { + return string.Concat("-", TruncatePadded(paddingChar, size - 1, orientation, ToString(-value))); + } + + return TruncatePadded(paddingChar, size, orientation, ToString(value)); + } public static string ToString(char paddingChar, int size, PaddingOrientation orientation, TEnum value) where TEnum : struct, Enum => TruncatePadded(paddingChar, size, orientation, ToString(value)); @@ -145,7 +152,13 @@ public static string ToString(long value) => value.ToString(); public static string ToString(char paddingChar, int size, PaddingOrientation orientation, long value) - => TruncatePadded(paddingChar, size, orientation, ToString(value)); + { + if (value < 0) + { + return string.Concat("-", TruncatePadded(paddingChar, size - 1, orientation, ToString(-value))); + } + return TruncatePadded(paddingChar, size, orientation, ToString(value)); + } public static long ToInt64(string value) { diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0061.cs b/src/OpenProtocolInterpreter/Tightening/Mid0061.cs index bd0aedf8..f22e9d27 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0061.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0061.cs @@ -15,408 +15,298 @@ public class Mid0061 : Mid, ITightening, IController, IAcknowledgeable { public const int MID = 61; - public int CellId - { - get => GetField(GetCurrentRevisionIndex(), DataFields.CellId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(GetCurrentRevisionIndex(), DataFields.CellId).SetValue(OpenProtocolConvert.ToString, value); - } - public int ChannelId - { - get => GetField(GetCurrentRevisionIndex(), DataFields.ChannelId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(GetCurrentRevisionIndex(), DataFields.ChannelId).SetValue(OpenProtocolConvert.ToString, value); - } - public string TorqueControllerName - { - get => GetField(GetCurrentRevisionIndex(), DataFields.TorqueControllerName).Value; - set => GetField(GetCurrentRevisionIndex(), DataFields.TorqueControllerName).SetValue(value); - } - public string VinNumber - { - get => GetField(GetCurrentRevisionIndex(), DataFields.VinNumber).Value; - set => GetField(GetCurrentRevisionIndex(), DataFields.VinNumber).SetValue(value); - } - public int JobId - { - get => GetField(GetCurrentRevisionIndex(), DataFields.JobId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(GetCurrentRevisionIndex(), DataFields.JobId).SetValue(OpenProtocolConvert.ToString, value); - } - public int ParameterSetId - { - get => GetField(GetCurrentRevisionIndex(), DataFields.ParameterSetId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(GetCurrentRevisionIndex(), DataFields.ParameterSetId).SetValue(OpenProtocolConvert.ToString, value); - } - public int BatchSize - { - get => GetField(GetCurrentRevisionIndex(), DataFields.BatchSize).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(GetCurrentRevisionIndex(), DataFields.BatchSize).SetValue(OpenProtocolConvert.ToString, value); - } - public int BatchCounter - { - get => GetField(GetCurrentRevisionIndex(), DataFields.BatchCounter).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(GetCurrentRevisionIndex(), DataFields.BatchCounter).SetValue(OpenProtocolConvert.ToString, value); - } - public bool TighteningStatus - { - get => GetField(GetCurrentRevisionIndex(), DataFields.TighteningStatus).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(GetCurrentRevisionIndex(), DataFields.TighteningStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public TighteningValueStatus TorqueStatus - { - get => (TighteningValueStatus)GetField(GetCurrentRevisionIndex(), DataFields.TorqueStatus).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(GetCurrentRevisionIndex(), DataFields.TorqueStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public TighteningValueStatus AngleStatus - { - get => (TighteningValueStatus)GetField(GetCurrentRevisionIndex(), DataFields.AngleStatus).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(GetCurrentRevisionIndex(), DataFields.AngleStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public decimal TorqueMinLimit - { - get => GetField(GetCurrentRevisionIndex(), DataFields.TorqueMinLimit).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(GetCurrentRevisionIndex(), DataFields.TorqueMinLimit).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal TorqueMaxLimit - { - get => GetField(GetCurrentRevisionIndex(), DataFields.TorqueMaxLimit).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(GetCurrentRevisionIndex(), DataFields.TorqueMaxLimit).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal TorqueFinalTarget - { - get => GetField(GetCurrentRevisionIndex(), DataFields.TorqueFinalTarget).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(GetCurrentRevisionIndex(), DataFields.TorqueFinalTarget).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal Torque - { - get => GetField(GetCurrentRevisionIndex(), DataFields.Torque).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(GetCurrentRevisionIndex(), DataFields.Torque).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public int AngleMinLimit - { - get => GetField(GetCurrentRevisionIndex(), DataFields.AngleMinLimit).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(GetCurrentRevisionIndex(), DataFields.AngleMinLimit).SetValue(OpenProtocolConvert.ToString, value); - } - public int AngleMaxLimit - { - get => GetField(GetCurrentRevisionIndex(), DataFields.AngleMaxLimit).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(GetCurrentRevisionIndex(), DataFields.AngleMaxLimit).SetValue(OpenProtocolConvert.ToString, value); - } - public int AngleFinalTarget - { - get => GetField(GetCurrentRevisionIndex(), DataFields.AngleFinalTarget).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(GetCurrentRevisionIndex(), DataFields.AngleFinalTarget).SetValue(OpenProtocolConvert.ToString, value); - } - public int Angle - { - get => GetField(GetCurrentRevisionIndex(), DataFields.Angle).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(GetCurrentRevisionIndex(), DataFields.Angle).SetValue(OpenProtocolConvert.ToString, value); - } - public DateTime Timestamp - { - get => GetField(GetCurrentRevisionIndex(), DataFields.Timestamp).GetValue(OpenProtocolConvert.ToDateTime); - set => GetField(GetCurrentRevisionIndex(), DataFields.Timestamp).SetValue(OpenProtocolConvert.ToString, value); - } - public DateTime LastChangeInParameterSet - { - get => GetField(GetCurrentRevisionIndex(), DataFields.LastChangeInParameterSet).GetValue(OpenProtocolConvert.ToDateTime); - set => GetField(GetCurrentRevisionIndex(), DataFields.LastChangeInParameterSet).SetValue(OpenProtocolConvert.ToString, value); - } - public BatchStatus BatchStatus - { - get => (BatchStatus)GetField(GetCurrentRevisionIndex(), DataFields.BatchStatus).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(GetCurrentRevisionIndex(), DataFields.BatchStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public long TighteningId - { - get => GetField(GetCurrentRevisionIndex(), DataFields.TighteningId).GetValue(OpenProtocolConvert.ToInt64); - set => GetField(GetCurrentRevisionIndex(), DataFields.TighteningId).SetValue(OpenProtocolConvert.ToString, value); - } - //Rev 2 Addition - public Strategy Strategy - { - get => (Strategy)GetField(2, DataFields.Strategy).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.Strategy).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(field: 1, revision: 1, Index = 20, Size = 4)] + [Int32DataFieldDefinition(field: 1, revision: 2, Index = 20, Size = 4)] + public int CellId { get; set; } + + [Int32DataFieldDefinition(field: 2, revision: 1, Index = 26, Size = 2)] + [Int32DataFieldDefinition(field: 2, revision: 2, Index = 26, Size = 2)] + public int ChannelId { get; set; } + + [StringDataFieldDefinition(field: 3, revision: 1, Index = 30, Size = 25)] + [StringDataFieldDefinition(field: 3, revision: 2, Index = 30, Size = 25)] + public string TorqueControllerName { get; set; } + + [StringDataFieldDefinition(field: 4, revision: 1, Index = 57, Size = 25)] + [StringDataFieldDefinition(field: 4, revision: 2, Index = 57, Size = 25)] + [StringDataFieldDefinition(field: 1, revision: 999, Index = 20, Size = 25, HasPrefix = false)] + public string VinNumber { get; set; } + + [Int32DataFieldDefinition(field: 5, revision: 1, Index = 84, Size = 2)] + [Int32DataFieldDefinition(field: 5, revision: 2, Index = 84, Size = 4)] + [Int32DataFieldDefinition(field: 2, revision: 999, Index = 45, Size = 2, HasPrefix = false)] + public int JobId { get; set; } + + [Int32DataFieldDefinition(field: 6, revision: 1, Index = 88, Size = 3)] + [Int32DataFieldDefinition(field: 6, revision: 2, Index = 90, Size = 3)] + [Int32DataFieldDefinition(field: 3, revision: 999, Index = 47, Size = 3, HasPrefix = false)] + public int ParameterSetId { get; set; } + + [Int32DataFieldDefinition(field: 7, revision: 1, Index = 93, Size = 4)] + [Int32DataFieldDefinition(field: 9, revision: 2, Index = 106, Size = 4)] + [Int32DataFieldDefinition(field: 4, revision: 999, Index = 50, Size = 4, HasPrefix = false)] + public int BatchSize { get; set; } + + [Int32DataFieldDefinition(field: 8, revision: 1, Index = 99, Size = 4)] + [Int32DataFieldDefinition(field: 10, revision: 2, Index = 112, Size = 4)] + [Int32DataFieldDefinition(field: 5, revision: 999, Index = 54, Size = 4, HasPrefix = false)] + public int BatchCounter { get; set; } + + [BooleanDataFieldDefinition(field: 9, revision: 1, Index = 105)] + [BooleanDataFieldDefinition(field: 11, revision: 2, Index = 118)] + [BooleanDataFieldDefinition(field: 7, revision: 999, Index = 59, HasPrefix = false)] + public bool TighteningStatus { get; set; } + + [Int32DataFieldDefinition(field: 10, revision: 1, Index = 108, Size = 1)] + [Int32DataFieldDefinition(field: 13, revision: 2, Index = 124, Size = 1)] + [Int32DataFieldDefinition(field: 8, revision: 999, Index = 60, Size = 1, HasPrefix = false)] + public TighteningValueStatus TorqueStatus { get; set; } + + [Int32DataFieldDefinition(field: 11, revision: 1, Index = 111, Size = 1)] + [Int32DataFieldDefinition(field: 14, revision: 2, Index = 127, Size = 1)] + [Int32DataFieldDefinition(field: 9, revision: 999, Index = 61, Size = 1, HasPrefix = false)] + public TighteningValueStatus AngleStatus { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 12, revision: 1, Index = 114, Size = 6)] + [TruncatedDecimalDataFieldDefinition(field: 21, revision: 2, Index = 157, Size = 6)] + public decimal TorqueMinLimit { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 13, revision: 1, Index = 122, Size = 6)] + [TruncatedDecimalDataFieldDefinition(field: 22, revision: 2, Index = 165, Size = 6)] + public decimal TorqueMaxLimit { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 14, revision: 1, Index = 130, Size = 6)] + [TruncatedDecimalDataFieldDefinition(field: 23, revision: 2, Index = 173, Size = 6)] + public decimal TorqueFinalTarget { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 15, revision: 1, Index = 138, Size = 6)] + [TruncatedDecimalDataFieldDefinition(field: 24, revision: 2, Index = 181, Size = 6)] + [TruncatedDecimalDataFieldDefinition(field: 10, revision: 999, Index = 62, Size = 6, HasPrefix = false)] + public decimal Torque { get; set; } + + [Int32DataFieldDefinition(field: 16, revision: 1, Index = 146, Size = 5)] + [Int32DataFieldDefinition(field: 25, revision: 2, Index = 189, Size = 5)] + public int AngleMinLimit { get; set; } + + [Int32DataFieldDefinition(field: 17, revision: 1, Index = 153, Size = 5)] + [Int32DataFieldDefinition(field: 26, revision: 2, Index = 196, Size = 5)] + public int AngleMaxLimit { get; set; } + + [Int32DataFieldDefinition(field: 18, revision: 1, Index = 160, Size = 5)] + [Int32DataFieldDefinition(field: 27, revision: 2, Index = 203, Size = 5)] + public int AngleFinalTarget { get; set; } + + [Int32DataFieldDefinition(field: 19, revision: 1, Index = 167, Size = 5)] + [Int32DataFieldDefinition(field: 28, revision: 2, Index = 210, Size = 5)] + [Int32DataFieldDefinition(field: 11, revision: 999, Index = 68, Size = 5, HasPrefix = false)] + public int Angle { get; set; } + + [TimestampDataFieldDefinition(field: 20, revision: 1, Index = 174)] + [TimestampDataFieldDefinition(field: 45, revision: 2, Index = 343)] + [TimestampDataFieldDefinition(field: 12, revision: 999, Index = 73, HasPrefix = false)] + public DateTime Timestamp { get; set; } + + [TimestampDataFieldDefinition(field: 21, revision: 1, Index = 195)] + [TimestampDataFieldDefinition(field: 46, revision: 2, Index = 364)] + [TimestampDataFieldDefinition(field: 13, revision: 999, Index = 92, HasPrefix = false)] + public DateTime LastChangeInParameterSet { get; set; } + + [Int32DataFieldDefinition(field: 22, revision: 1, Index = 216, Size = 1)] + [Int32DataFieldDefinition(field: 12, revision: 2, Index = 121, Size = 1)] + [Int32DataFieldDefinition(field: 6, revision: 999, Index = 58, Size = 1, HasPrefix = false)] + public BatchStatus BatchStatus { get; set; } + + [Int64DataFieldDefinition(field: 23, revision: 1, Index = 219, Size = 10)] + [Int64DataFieldDefinition(field: 41, revision: 2, Index = 301, Size = 10)] + [Int64DataFieldDefinition(field: 14, revision: 999, Index = 111, Size = 10, HasPrefix = false)] + public long TighteningId { get; set; } + + //Rev 2 + [Int32DataFieldDefinition(field: 7, revision: 2, Index = 95, Size = 2)] + public Strategy Strategy { get; set; } + + [StrategyOptionsDefinition(field: 8, revision: 2, Index = 99, Size = 5, PaddingChar = '0', PaddingOrientation = PaddingOrientation.LeftPadded)] public StrategyOptions StrategyOptions { get; set; } - public TighteningValueStatus RundownAngleStatus - { - get => (TighteningValueStatus)GetField(2, DataFields.RundownAngleStatus).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.RundownAngleStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public TighteningValueStatus CurrentMonitoringStatus - { - get => (TighteningValueStatus)GetField(2, DataFields.CurrentMonitoringStatus).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.CurrentMonitoringStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public TighteningValueStatus SelftapStatus - { - get => (TighteningValueStatus)GetField(2, DataFields.SelftapStatus).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.SelftapStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public TighteningValueStatus PrevailTorqueMonitoringStatus - { - get => (TighteningValueStatus)GetField(2, DataFields.PrevailTorqueMonitoringStatus).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.PrevailTorqueMonitoringStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public TighteningValueStatus PrevailTorqueCompensateStatus - { - get => (TighteningValueStatus)GetField(2, DataFields.PrevailTorqueCompensateStatus).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.PrevailTorqueCompensateStatus).SetValue(OpenProtocolConvert.ToString, value); - } + + [Int32DataFieldDefinition(field: 15, revision: 2, Index = 130, Size = 1)] + public TighteningValueStatus RundownAngleStatus { get; set; } + + [Int32DataFieldDefinition(field: 16, revision: 2, Index = 133, Size = 1)] + public TighteningValueStatus CurrentMonitoringStatus { get; set; } + + [Int32DataFieldDefinition(field: 17, revision: 2, Index = 136, Size = 1)] + public TighteningValueStatus SelftapStatus { get; set; } + + [Int32DataFieldDefinition(field: 18, revision: 2, Index = 139, Size = 1)] + public TighteningValueStatus PrevailTorqueMonitoringStatus { get; set; } + + + [Int32DataFieldDefinition(field: 19, revision: 2, Index = 142, Size = 1)] + public TighteningValueStatus PrevailTorqueCompensateStatus { get; set; } + + [TighteningErrorStatusDefinition(field: 20, revision: 2, Index = 145, Size = 10, PaddingChar = '0', PaddingOrientation = PaddingOrientation.LeftPadded)] public TighteningErrorStatus TighteningErrorStatus { get; set; } - public int RundownAngleMin - { - get => GetField(2, DataFields.RundownAngleMin).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.RundownAngleMin).SetValue(OpenProtocolConvert.ToString, value); - } - public int RundownAngleMax - { - get => GetField(2, DataFields.RundownAngleMax).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.RundownAngleMax).SetValue(OpenProtocolConvert.ToString, value); - } - public int RundownAngle - { - get => GetField(2, DataFields.RundownAngle).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.RundownAngle).SetValue(OpenProtocolConvert.ToString, value); - } - public int CurrentMonitoringMin - { - get => GetField(2, DataFields.CurrentMonitoringMin).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.CurrentMonitoringMin).SetValue(OpenProtocolConvert.ToString, value); - } - public int CurrentMonitoringMax - { - get => GetField(2, DataFields.CurrentMonitoringMax).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.CurrentMonitoringMax).SetValue(OpenProtocolConvert.ToString, value); - } - public int CurrentMonitoringValue - { - get => GetField(2, DataFields.CurrentMonitoringValue).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.CurrentMonitoringValue).SetValue(OpenProtocolConvert.ToString, value); - } - public decimal SelftapMin - { - get => GetField(2, DataFields.SelftapMin).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(2, DataFields.SelftapMin).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal SelftapMax - { - get => GetField(2, DataFields.SelftapMax).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(2, DataFields.SelftapMax).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal SelftapTorque - { - get => GetField(2, DataFields.SelftapTorque).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(2, DataFields.SelftapTorque).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal PrevailTorqueMonitoringMin - { - get => GetField(2, DataFields.PrevailTorqueMonitoringMin).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(2, DataFields.PrevailTorqueMonitoringMin).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal PrevailTorqueMonitoringMax - { - get => GetField(2, DataFields.PrevailTorqueMonitoringMax).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(2, DataFields.PrevailTorqueMonitoringMax).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal PrevailTorque - { - get => GetField(2, DataFields.PrevailTorque).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(2, DataFields.PrevailTorque).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public int JobSequenceNumber - { - get => GetField(2, DataFields.JobSequenceNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.JobSequenceNumber).SetValue(OpenProtocolConvert.ToString, value); - } - public int SyncTighteningId - { - get => GetField(2, DataFields.SyncTighteningId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.SyncTighteningId).SetValue(OpenProtocolConvert.ToString, value); - } - public string ToolSerialNumber - { - get => GetField(2, DataFields.ToolSerialNumber).Value; - set => GetField(2, DataFields.ToolSerialNumber).SetValue(value); - } + + [Int32DataFieldDefinition(field: 29, revision: 2, Index = 217, Size = 5)] + public int RundownAngleMin { get; set; } + + [Int32DataFieldDefinition(field: 30, revision: 2, Index = 224, Size = 5)] + public int RundownAngleMax { get; set; } + + [Int32DataFieldDefinition(field: 31, revision: 2, Index = 231, Size = 5)] + public int RundownAngle { get; set; } + + [Int32DataFieldDefinition(field: 32, revision: 2, Index = 238, Size = 3)] + public int CurrentMonitoringMin { get; set; } + + [Int32DataFieldDefinition(field: 33, revision: 2, Index = 243, Size = 3)] + public int CurrentMonitoringMax { get; set; } + + [Int32DataFieldDefinition(field: 34, revision: 2, Index = 248, Size = 3)] + public int CurrentMonitoringValue { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 35, revision: 2, Index = 253, Size = 6)] + public decimal SelftapMin { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 36, revision: 2, Index = 261, Size = 6)] + public decimal SelftapMax { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 37, revision: 2, Index = 269, Size = 6)] + public decimal SelftapTorque { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 38, revision: 2, Index = 277, Size = 6)] + public decimal PrevailTorqueMonitoringMin { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 39, revision: 2, Index = 285, Size = 6)] + public decimal PrevailTorqueMonitoringMax { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 40, revision: 2, Index = 293, Size = 6)] + public decimal PrevailTorque { get; set; } + + [Int32DataFieldDefinition(field: 42, revision: 2, Index = 313, Size = 5)] + public int JobSequenceNumber { get; set; } + + [Int32DataFieldDefinition(field: 43, revision: 2, Index = 320, Size = 5)] + public int SyncTighteningId { get; set; } + + [StringDataFieldDefinition(field: 44, revision: 2, Index = 327, Size = 14)] + public string ToolSerialNumber { get; set; } + //Rev 3 Addition - public string ParameterSetName - { - get => GetField(3, DataFields.ParameterSetName).Value; - set => GetField(3, DataFields.ParameterSetName).SetValue(value); - } - public TorqueValuesUnit TorqueValuesUnit - { - get => (TorqueValuesUnit)GetField(3, DataFields.TorqueValuesUnit).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(3, DataFields.TorqueValuesUnit).SetValue(OpenProtocolConvert.ToString, value); - } - public ResultType ResultType - { - get => (ResultType)GetField(3, DataFields.ResultType).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(3, DataFields.ResultType).SetValue(OpenProtocolConvert.ToString, value); - } + [StringDataFieldDefinition(field: 47, revision: 3, Index = 385, Size = 25)] + public string ParameterSetName { get; set; } + + [Int32DataFieldDefinition(field: 48, revision: 3, Index = 412, Size = 1)] + public TorqueValuesUnit TorqueValuesUnit { get; set; } + + [Int32DataFieldDefinition(field: 49, revision: 3, Index = 415, Size = 2)] + public ResultType ResultType { get; set; } + //Rev 4 addition - public string IdentifierResultPart2 - { - get => GetField(4, DataFields.IdentifierResultPart2).Value; - set => GetField(4, DataFields.IdentifierResultPart2).SetValue(value); - } - public string IdentifierResultPart3 - { - get => GetField(4, DataFields.IdentifierResultPart3).Value; - set => GetField(4, DataFields.IdentifierResultPart3).SetValue(value); - } - public string IdentifierResultPart4 - { - get => GetField(4, DataFields.IdentifierResultPart4).Value; - set => GetField(4, DataFields.IdentifierResultPart4).SetValue(value); - } + [StringDataFieldDefinition(field: 50, revision: 4, Index = 419, Size = 25)] + public string IdentifierResultPart2 { get; set; } + + [StringDataFieldDefinition(field: 51, revision: 4, Index = 446, Size = 25)] + public string IdentifierResultPart3 { get; set; } + + [StringDataFieldDefinition(field: 52, revision: 4, Index = 473, Size = 25)] + public string IdentifierResultPart4 { get; set; } + //Rev 5 addition - public string CustomerTighteningErrorCode - { - get => GetField(5, DataFields.CustomerTighteningErrorCode).Value; - set => GetField(5, DataFields.CustomerTighteningErrorCode).SetValue(value); - } + [StringDataFieldDefinition(field: 53, revision: 5, Index = 500, Size = 4)] + public string CustomerTighteningErrorCode { get; set; } + //Rev 6 Addition - public decimal PrevailTorqueCompensateValue - { - get => GetField(6, DataFields.PrevailTorqueCompensateValue).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(6, DataFields.PrevailTorqueCompensateValue).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } + [TruncatedDecimalDataFieldDefinition(field: 54, revision: 6, Index = 506, Size = 6)] + public decimal PrevailTorqueCompensateValue { get; set; } + + [TighteningErrorStatus2Definition(field: 55, revision: 6, Index = 514, Size = 10, PaddingChar = '0', PaddingOrientation = PaddingOrientation.LeftPadded)] public TighteningErrorStatus2 TighteningErrorStatus2 { get; set; } + //Rev 7 addition - public decimal CompensatedAngle - { - get => GetField(7, DataFields.CompensatedAngle).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(7, DataFields.CompensatedAngle).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal FinalAngleDecimal - { - get => GetField(7, DataFields.FinalAngleDecimal).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(7, DataFields.FinalAngleDecimal).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } + [TruncatedDecimalDataFieldDefinition(field: 56, revision: 7, Index = 526, Size = 7)] + public decimal CompensatedAngle { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 57, revision: 7, Index = 535, Size = 7)] + public decimal FinalAngleDecimal { get; set; } + //Rev 8 addition - public decimal StartFinalAngle - { - get => GetField(8, DataFields.StartFinalAngle).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(8, DataFields.StartFinalAngle).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public PostViewTorque PostViewTorqueActivated - { - get => (PostViewTorque)GetField(8, DataFields.PostViewTorqueActivated).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(8, DataFields.PostViewTorqueActivated).SetValue(OpenProtocolConvert.ToString, value); - } - public decimal PostViewTorqueHigh - { - get => GetField(8, DataFields.PostViewTorqueHigh).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(8, DataFields.PostViewTorqueHigh).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal PostViewTorqueLow - { - get => GetField(8, DataFields.PostViewTorqueLow).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(8, DataFields.PostViewTorqueLow).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } + [TruncatedDecimalDataFieldDefinition(field: 58, revision: 8, Index = 544, Size = 6)] + public decimal StartFinalAngle { get; set; } + + [Int32DataFieldDefinition(field: 59, revision: 8, Index = 552, Size = 1)] + public PostViewTorque PostViewTorqueActivated { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 60, revision: 8, Index = 555, Size = 6)] + public decimal PostViewTorqueHigh { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 61, revision: 8, Index = 563, Size = 6)] + public decimal PostViewTorqueLow { get; set; } + //Rev 9 addition - public decimal CurrentMonitoringAmpere - { - get => GetField(9, DataFields.CurrentMonitoringAmp).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(9, DataFields.CurrentMonitoringAmp).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal CurrentMonitoringAmpereMin - { - get => GetField(9, DataFields.CurrentMonitoringAmpMin).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(9, DataFields.CurrentMonitoringAmpMin).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal CurrentMonitoringAmpereMax - { - get => GetField(9, DataFields.CurrentMonitoringAmpMax).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(9, DataFields.CurrentMonitoringAmpMax).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } + [TruncatedDecimalDataFieldDefinition(field: 62, revision: 9, Index = 571, Size = 5)] + public decimal CurrentMonitoringAmpere { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 63, revision: 9, Index = 578, Size = 5)] + public decimal CurrentMonitoringAmpereMin { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 64, revision: 9, Index = 585, Size = 5)] + public decimal CurrentMonitoringAmpereMax { get; set; } + //Rev 10 addition - public int AngleNumeratorScaleFactor - { - get => GetField(10, DataFields.AngleNumeratorScaleFactor).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(10, DataFields.AngleNumeratorScaleFactor).SetValue(OpenProtocolConvert.ToString, value); - } - public int AngleDenominatorScaleFactor - { - get => GetField(10, DataFields.AngleDenominatorScaleFactor).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(10, DataFields.AngleDenominatorScaleFactor).SetValue(OpenProtocolConvert.ToString, value); - } - public TighteningValueStatus OverallAngleStatus - { - get => (TighteningValueStatus)GetField(10, DataFields.OverallAngleStatus).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(10, DataFields.OverallAngleStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public int OverallAngleMin - { - get => GetField(10, DataFields.OverallAngleMin).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(10, DataFields.OverallAngleMin).SetValue(OpenProtocolConvert.ToString, value); - } - public int OverallAngleMax - { - get => GetField(10, DataFields.OverallAngleMax).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(10, DataFields.OverallAngleMax).SetValue(OpenProtocolConvert.ToString, value); - } - public int OverallAngle - { - get => GetField(10, DataFields.OverallAngle).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(10, DataFields.OverallAngle).SetValue(OpenProtocolConvert.ToString, value); - } - public decimal PeakTorque - { - get => GetField(10, DataFields.PeakTorque).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(10, DataFields.PeakTorque).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal ResidualBreakawayTorque - { - get => GetField(10, DataFields.ResidualBreakawayTorque).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(10, DataFields.ResidualBreakawayTorque).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal StartRundownAngle - { - get => GetField(10, DataFields.StartRundownAngle).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(10, DataFields.StartRundownAngle).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal RundownAngleComplete - { - get => GetField(10, DataFields.RundownAngleComplete).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(10, DataFields.RundownAngleComplete).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } + [Int32DataFieldDefinition(field: 65, revision: 10, Index = 592, Size = 5)] + public int AngleNumeratorScaleFactor { get; set; } + + [Int32DataFieldDefinition(field: 66, revision: 10, Index = 599, Size = 5)] + public int AngleDenominatorScaleFactor { get; set; } + + [Int32DataFieldDefinition(field: 67, revision: 10, Index = 606, Size = 1)] + public TighteningValueStatus OverallAngleStatus { get; set; } + + [Int32DataFieldDefinition(field: 68, revision: 10, Index = 609, Size = 5)] + public int OverallAngleMin { get; set; } + + [Int32DataFieldDefinition(field: 69, revision: 10, Index = 616, Size = 5)] + public int OverallAngleMax { get; set; } + + [Int32DataFieldDefinition(field: 70, revision: 10, Index = 623, Size = 5)] + public int OverallAngle { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 71, revision: 10, Index = 630, Size = 6)] + public decimal PeakTorque { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 72, revision: 10, Index = 638, Size = 6)] + public decimal ResidualBreakawayTorque { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 73, revision: 10, Index = 646, Size = 6)] + public decimal StartRundownAngle { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 74, revision: 10, Index = 654, Size = 6)] + public decimal RundownAngleComplete { get; set; } + //Rev 11 - public decimal ClickTorque - { - get => GetField(11, DataFields.ClickTorque).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(11, DataFields.ClickTorque).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public int ClickAngle - { - get => GetField(11, DataFields.ClickAngle).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(11, DataFields.ClickAngle).SetValue(OpenProtocolConvert.ToString, value); - } + [TruncatedDecimalDataFieldDefinition(field: 75, revision: 11, Index = 662, Size = 6)] + public decimal ClickTorque { get; set; } + + [Int32DataFieldDefinition(field: 76, revision: 11, Index = 670, Size = 5)] + public int ClickAngle { get; set; } + //Rev 12 - public int SelectedIdentifierNumber - { - get => GetField(12, DataFields.SelectedIdentifierNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(12, DataFields.SelectedIdentifierNumber).SetValue(OpenProtocolConvert.ToString, value); - } - public string JointId - { - get => GetField(12, DataFields.JointId).Value; - set => GetField(12, DataFields.JointId).SetValue(value); - } - //Rev 998 addition - public int NumberOfStagesInMultistage - { - get => GetField(998, DataFields.NumberOfStagesInMultistage).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(998, DataFields.NumberOfStagesInMultistage).SetValue(OpenProtocolConvert.ToString, value); - } - public int NumberOfStageResults - { - get => GetField(998, DataFields.NumberOfStageResults).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(998, DataFields.NumberOfStageResults).SetValue(OpenProtocolConvert.ToString, value); - } - public List StageResults { get; set; } + [Int32DataFieldDefinition(field: 77, revision: 12, Index = 677, Size = 4)] + public int SelectedIdentifierNumber { get; set; } + + [StringDataFieldDefinition(field: 78, revision: 12, Index = 683, Size = 25)] + public string JointId { get; set; } + + //Rev 998 addition + [Int32DataFieldDefinition(field: 56, revision: 998, Index = 526, Size = 2)] + public int NumberOfStagesInMultistage { get; set; } + [Int32DataFieldDefinition(field: 57, revision: 998, Index = 530, Size = 2)] + public int NumberOfStageResults { get; set; } + + [StageResultCollectionDefinition(field: 58, revision: 998, Index = 534, Size = 0)] + public List StageResults { get; set; } public Mid0061() : this(DEFAULT_REVISION) { @@ -440,27 +330,14 @@ protected override string BuildHeader() if (RevisionsByFields.Any()) { Header.Length = 20; - Header.Revision = Header.Revision > 0 ? Header.Revision : 1; - if (Header.Revision == 1 || Header.Revision == 999) - { - foreach (var dataField in RevisionsByFields[Header.Revision]) - Header.Length += (dataField.HasPrefix ? 2 : 0) + dataField.Size; - } - else + if (Header.Revision == 998) { - int processUntil = Header.Revision != 998 ? Header.Revision : 6; - - for (int i = 2; i <= processUntil; i++) - foreach (var dataField in RevisionsByFields[i]) - Header.Length += (dataField.HasPrefix ? 2 : 0) + dataField.Size; - - if (Header.Revision == 998) - { - GetField(998, DataFields.StageResult).Size = StageResults.Count * 11; - foreach (var dataField in RevisionsByFields[998]) - Header.Length += (dataField.HasPrefix ? 2 : 0) + dataField.Size; - } + var stageResultField = GetField(nameof(StageResults)); + stageResultField.Size = StageResults.Count * 11; } + + var fields = DataFieldsByRevision(); + Header.Length += fields.Sum(x => x.TotalSize); } return Header.ToString(); } @@ -468,288 +345,321 @@ protected override string BuildHeader() public override string Pack() { var builder = new StringBuilder(); + var fields = DataFieldsByRevision().OrderBy(f => f.Index).ToList(); int prefixIndex = 1; - if (Header.Revision > 1 && Header.Revision != 999) - { - GetField(2, DataFields.StrategyOptions).SetValue(StrategyOptions.Pack()); - GetField(2, DataFields.TighteningErrorStatus).SetValue(TighteningErrorStatus.Pack()); + builder.Append(BuildHeader()); + builder.Append(Pack(fields, ref prefixIndex)); + // if (Header.Revision > 1 && Header.Revision != 999) + // { + // GetField(2, DataFields.StrategyOptions).SetValue(StrategyOptions.Pack()); + // GetField(2, DataFields.TighteningErrorStatus).SetValue(TighteningErrorStatus.Pack()); - if (Header.Revision > 5) - { - GetField(6, DataFields.TighteningErrorStatus2).SetValue(TighteningErrorStatus2.Pack()); - } + // if (Header.Revision > 5) + // { + // GetField(6, DataFields.TighteningErrorStatus2).SetValue(TighteningErrorStatus2.Pack()); + // } - if (Header.Revision == 998) - { - NumberOfStageResults = StageResults.Count; - var stageResultField = GetField(998, DataFields.StageResult); - stageResultField.Size = StageResults.Count * 11; - stageResultField.SetValue(PackStageResults()); - } + // if (Header.Revision == 998) + // { + // NumberOfStageResults = StageResults.Count; + // var stageResultField = GetField(998, DataFields.StageResult); + // stageResultField.Size = StageResults.Count * 11; + // stageResultField.SetValue(PackStageResults()); + // } - builder.Append(BuildHeader()); - int processUntilRevision = Header.Revision != 998 ? Header.Revision : 6; - for (int revision = 2; revision <= processUntilRevision; revision++) - { - builder.Append(Pack(revision, ref prefixIndex)); - } + // builder.Append(BuildHeader()); + // int processUntilRevision = Header.Revision != 998 ? Header.Revision : 6; + // for (int revision = 2; revision <= processUntilRevision; revision++) + // { + // builder.Append(Pack(revision, ref prefixIndex)); + // } - if (Header.Revision == 998) - { - builder.Append(Pack(998, ref prefixIndex)); - } - } - else - { - builder.Append(BuildHeader()); - builder.Append(Pack(Header.Revision, ref prefixIndex)); - } + // if (Header.Revision == 998) + // { + // builder.Append(Pack(998, ref prefixIndex)); + // } + // } + // else + // { + // builder.Append(BuildHeader()); + // builder.Append(Pack(Header.Revision, ref prefixIndex)); + // } return builder.ToString(); } protected override void ProcessDataFields(ReadOnlySpan package) { - if (Header.Revision == 1 || Header.Revision == 999) - { - ProcessDataFields(Header.Revision, package); - } - else - { - int processUntilRevision = Header.Revision; - if (Header.Revision == 998) - { - processUntilRevision = 6; - var stageResultField = GetField(998, DataFields.StageResult); - stageResultField.Size = Header.Length - stageResultField.Index - 2; - ProcessDataFields(998, package); - StageResults = StageResult.ParseAll(stageResultField.Value).ToList(); - } + var fields = DataFieldsByRevision().OrderBy(f => f.Index).ToList(); + ProcessDataFields(fields, package); + // if (Header.Revision == 1 || Header.Revision == 999) + // { + // ProcessDataFields(Header.Revision, package); + // } + // else + // { + // int processUntilRevision = Header.Revision; + // if (Header.Revision == 998) + // { + // processUntilRevision = 6; + // var stageResultField = GetField(998, DataFields.StageResult); + // stageResultField.Size = Header.Length - stageResultField.Index - 2; + // ProcessDataFields(998, package); + // StageResults = StageResult.ParseAll(stageResultField.Value).ToList(); + // } - for (int revision = 2; revision <= processUntilRevision; revision++) - ProcessDataFields(revision, package); + // for (int revision = 2; revision <= processUntilRevision; revision++) + // ProcessDataFields(revision, package); - var strategyOptionsField = GetField(2, DataFields.StrategyOptions); - StrategyOptions = StrategyOptions.Parse(strategyOptionsField.Value); + // var strategyOptionsField = GetField(2, DataFields.StrategyOptions); + // StrategyOptions = StrategyOptions.Parse(strategyOptionsField.Value); - var tighteningErrorStatusField = GetField(2, DataFields.TighteningErrorStatus); - TighteningErrorStatus = TighteningErrorStatus.Parse(tighteningErrorStatusField.Value); + // var tighteningErrorStatusField = GetField(2, DataFields.TighteningErrorStatus); + // TighteningErrorStatus = TighteningErrorStatus.Parse(tighteningErrorStatusField.Value); - if (Header.Revision > 5) - { - var tighteningErrorStatus2Field = GetField(6, DataFields.TighteningErrorStatus2); - TighteningErrorStatus2 = TighteningErrorStatus2.Parse(tighteningErrorStatus2Field.Value); - } - } + // if (Header.Revision > 5) + // { + // var tighteningErrorStatus2Field = GetField(6, DataFields.TighteningErrorStatus2); + // TighteningErrorStatus2 = TighteningErrorStatus2.Parse(tighteningErrorStatus2Field.Value); + // } + // } } - protected virtual string PackStageResults() + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - var builder = new StringBuilder(); - foreach (var v in StageResults) + base.ProcessDataField(dataField, package); + if (Header.StandardizedRevision == 998 && dataField.Field == 57) { - builder.Append(v.Pack()); + var stageResultField = GetField(nameof(StageResults)); + stageResultField.Size = NumberOfStageResults * 11; } - - return builder.ToString(); } - protected override Dictionary> RegisterDatafields() + private IEnumerable DataFieldsByRevision() { - //opted to work with a different approuch (since it would need to modify too much fields) - return new Dictionary>() + var currentRevision = Header.StandardizedRevision; + var fromRevision = Header.StandardizedRevision; + var toRevision = Header.StandardizedRevision; + if (currentRevision == 999) { - { - 1, new List() - { - DataField.Number(DataFields.CellId, 20, 4), - DataField.Number(DataFields.ChannelId, 26, 2), - DataField.String(DataFields.TorqueControllerName, 30, 25), - DataField.String(DataFields.VinNumber, 57, 25), - DataField.Number(DataFields.JobId, 84, 2), - DataField.Number(DataFields.ParameterSetId, 88, 3), - DataField.Number(DataFields.BatchSize, 93, 4), - DataField.Number(DataFields.BatchCounter, 99, 4), - DataField.Number(DataFields.TighteningStatus, 105, 1), - DataField.Number(DataFields.TorqueStatus, 108, 1), - DataField.Number(DataFields.AngleStatus, 111, 1), - DataField.Number(DataFields.TorqueMinLimit, 114, 6), - DataField.Number(DataFields.TorqueMaxLimit, 122, 6), - DataField.Number(DataFields.TorqueFinalTarget, 130, 6), - DataField.Number(DataFields.Torque, 138, 6), - DataField.Number(DataFields.AngleMinLimit, 146, 5), - DataField.Number(DataFields.AngleMaxLimit, 153, 5), - DataField.Number(DataFields.AngleFinalTarget, 160, 5), - DataField.Number(DataFields.Angle, 167, 5), - DataField.Timestamp(DataFields.Timestamp, 174), - DataField.Timestamp(DataFields.LastChangeInParameterSet, 195), - DataField.Number(DataFields.BatchStatus, 216, 1), - DataField.Number(DataFields.TighteningId, 219, 10) - } - }, - { - 2, new List() - { - DataField.Number(DataFields.CellId, 20, 4), - DataField.Number(DataFields.ChannelId, 26, 2), - DataField.String(DataFields.TorqueControllerName, 30, 25), - DataField.String(DataFields.VinNumber, 57, 25), - DataField.Number(DataFields.JobId, 84, 4), - DataField.Number(DataFields.ParameterSetId, 90, 3), - DataField.Number(DataFields.Strategy, 95, 2), - new(DataFields.StrategyOptions, 99, 5, '0', PaddingOrientation.LeftPadded), - DataField.Number(DataFields.BatchSize, 106, 4), - DataField.Number(DataFields.BatchCounter, 112, 4), - DataField.Number(DataFields.TighteningStatus, 118, 1), - DataField.Number(DataFields.BatchStatus, 121, 1), - DataField.Number(DataFields.TorqueStatus, 124, 1), - DataField.Number(DataFields.AngleStatus, 127, 1), - DataField.Number(DataFields.RundownAngleStatus, 130, 1), - DataField.Number(DataFields.CurrentMonitoringStatus, 133, 1), - DataField.Number(DataFields.SelftapStatus, 136, 1), - DataField.Number(DataFields.PrevailTorqueMonitoringStatus, 139, 1), - DataField.Number(DataFields.PrevailTorqueCompensateStatus, 142, 1), - new(DataFields.TighteningErrorStatus, 145, 10, '0', PaddingOrientation.LeftPadded), - DataField.Number(DataFields.TorqueMinLimit, 157, 6), - DataField.Number(DataFields.TorqueMaxLimit, 165, 6), - DataField.Number(DataFields.TorqueFinalTarget, 173, 6), - DataField.Number(DataFields.Torque, 181, 6), - DataField.Number(DataFields.AngleMinLimit, 189, 5), - DataField.Number(DataFields.AngleMaxLimit, 196, 5), - DataField.Number(DataFields.AngleFinalTarget, 203, 5), - DataField.Number(DataFields.Angle, 210, 5), - DataField.Number(DataFields.RundownAngleMin, 217, 5), - DataField.Number(DataFields.RundownAngleMax, 224, 5), - DataField.Number(DataFields.RundownAngle, 231, 5), - DataField.Number(DataFields.CurrentMonitoringMin, 238, 3), - DataField.Number(DataFields.CurrentMonitoringMax, 243, 3), - DataField.Number(DataFields.CurrentMonitoringValue, 248, 3), - DataField.Number(DataFields.SelftapMin, 253, 6), - DataField.Number(DataFields.SelftapMax, 261, 6 ), - DataField.Number(DataFields.SelftapTorque, 269, 6), - DataField.Number(DataFields.PrevailTorqueMonitoringMin, 277, 6), - DataField.Number(DataFields.PrevailTorqueMonitoringMax, 285, 6), - DataField.Number(DataFields.PrevailTorque, 293, 6), - DataField.Number(DataFields.TighteningId, 301, 10), - DataField.Number(DataFields.JobSequenceNumber, 313, 5), - DataField.Number(DataFields.SyncTighteningId, 320, 5), - DataField.String(DataFields.ToolSerialNumber, 327, 14), - DataField.Timestamp(DataFields.Timestamp, 343), - DataField.Timestamp(DataFields.LastChangeInParameterSet, 364) - } - }, - { - 3, new List() - { - DataField.String(DataFields.ParameterSetName, 385, 25), - DataField.Number(DataFields.TorqueValuesUnit, 412, 1), - DataField.Number(DataFields.ResultType, 415, 2) - } - }, - { - 4, new List() - { - DataField.String(DataFields.IdentifierResultPart2, 419, 25), - DataField.String(DataFields.IdentifierResultPart3, 446, 25), - DataField.String(DataFields.IdentifierResultPart4, 473, 25) - } - }, - { - 5, new List() - { - DataField.String(DataFields.CustomerTighteningErrorCode, 500, 4) - } - }, - { - 6, new List() - { - DataField.Number(DataFields.PrevailTorqueCompensateValue, 506, 6), - new(DataFields.TighteningErrorStatus2, 514, 10, '0', PaddingOrientation.LeftPadded) - } - }, - { - 7, new List() - { - DataField.Number(DataFields.CompensatedAngle, 526, 7), - DataField.Number(DataFields.FinalAngleDecimal, 535, 7) - } - }, - { - 8, new List() - { - DataField.Number(DataFields.StartFinalAngle, 544, 6), - DataField.Number(DataFields.PostViewTorqueActivated, 552, 1), - DataField.Number(DataFields.PostViewTorqueHigh, 555, 6), - DataField.Number(DataFields.PostViewTorqueLow, 563, 6) - } - }, - { - 9, new List() - { - DataField.Number(DataFields.CurrentMonitoringAmp, 571, 5), - DataField.Number(DataFields.CurrentMonitoringAmpMin, 578, 5), - DataField.Number(DataFields.CurrentMonitoringAmpMax, 585, 5) - } - }, - { - 10, new List() - { - DataField.Number(DataFields.AngleNumeratorScaleFactor, 592, 5), - DataField.Number(DataFields.AngleDenominatorScaleFactor, 599, 5), - DataField.Number(DataFields.OverallAngleStatus, 606, 1), - DataField.Number(DataFields.OverallAngleMin, 609, 5), - DataField.Number(DataFields.OverallAngleMax, 616, 5), - DataField.Number(DataFields.OverallAngle, 623, 5), - DataField.Number(DataFields.PeakTorque, 630, 6), - DataField.Number(DataFields.ResidualBreakawayTorque, 638, 6), - DataField.Number(DataFields.StartRundownAngle, 646, 6), - DataField.Number(DataFields.RundownAngleComplete, 654, 6) - } - }, - { - 11, new List() - { - DataField.Number(DataFields.ClickTorque, 662, 6), - DataField.Number(DataFields.ClickAngle, 670, 5), - } - }, - { - 12, new List() - { - DataField.Number(DataFields.SelectedIdentifierNumber, 677, 4), - DataField.String(DataFields.JointId, 683, 25) - } - }, - { - 998, new List() - { - DataField.Number(DataFields.NumberOfStagesInMultistage, 526, 2), - DataField.Number(DataFields.NumberOfStageResults, 530, 2), - new(DataFields.StageResult, 534, 11) - } - }, - { - 999, new List() - { - DataField.String(DataFields.VinNumber, 20, 25, false), - DataField.Number(DataFields.JobId, 45, 2, false), - DataField.Number(DataFields.ParameterSetId, 47, 3, false), - DataField.Number(DataFields.BatchSize, 50, 4, false), - DataField.Number(DataFields.BatchCounter, 54, 4, false), - DataField.Number(DataFields.BatchStatus, 58, 1, false), - DataField.Number(DataFields.TighteningStatus, 59, 1, false), - DataField.Number(DataFields.TorqueStatus, 60, 1, false), - DataField.Number(DataFields.AngleStatus, 61, 1, false), - DataField.Number(DataFields.Torque, 62, 6, false), - DataField.Number(DataFields.Angle, 68, 5, false), - DataField.Timestamp(DataFields.Timestamp, 73, false), - DataField.Timestamp(DataFields.LastChangeInParameterSet, 92, false), - DataField.Number(DataFields.TighteningId, 111, 10, false) - } - } - }; + fromRevision = toRevision = 999; + } + else if (currentRevision > 1) + { + fromRevision = 2; + toRevision = Header.StandardizedRevision != 998 ? Header.StandardizedRevision : 6; + } + + for (int i = fromRevision; i <= toRevision; i++) + { + foreach (var dataField in RevisionsByFields[i]) + yield return dataField; + } + + if (currentRevision == 998) + { + foreach (var dataField in RevisionsByFields[998]) + yield return dataField; + } } + // protected override Dictionary> RegisterDatafields() + // { + // //opted to work with a different approuch (since it would need to modify too much fields) + // return new Dictionary>() + // { + // { + // 1, new List() + // { + // DataField.Number(DataFields.CellId, 20, 4), + // DataField.Number(DataFields.ChannelId, 26, 2), + // DataField.String(DataFields.TorqueControllerName, 30, 25), + // DataField.String(DataFields.VinNumber, 57, 25), + // DataField.Number(DataFields.JobId, 84, 2), + // DataField.Number(DataFields.ParameterSetId, 88, 3), + // DataField.Number(DataFields.BatchSize, 93, 4), + // DataField.Number(DataFields.BatchCounter, 99, 4), + // DataField.Number(DataFields.TighteningStatus, 105, 1), + // DataField.Number(DataFields.TorqueStatus, 108, 1), + // DataField.Number(DataFields.AngleStatus, 111, 1), + // DataField.Number(DataFields.TorqueMinLimit, 114, 6), + // DataField.Number(DataFields.TorqueMaxLimit, 122, 6), + // DataField.Number(DataFields.TorqueFinalTarget, 130, 6), + // DataField.Number(DataFields.Torque, 138, 6), + // DataField.Number(DataFields.AngleMinLimit, 146, 5), + // DataField.Number(DataFields.AngleMaxLimit, 153, 5), + // DataField.Number(DataFields.AngleFinalTarget, 160, 5), + // DataField.Number(DataFields.Angle, 167, 5), + // DataField.Timestamp(DataFields.Timestamp, 174), + // DataField.Timestamp(DataFields.LastChangeInParameterSet, 195), + // DataField.Number(DataFields.BatchStatus, 216, 1), + // DataField.Number(DataFields.TighteningId, 219, 10) + // } + // }, + // { + // 2, new List() + // { + // DataField.Number(DataFields.CellId, 20, 4), + // DataField.Number(DataFields.ChannelId, 26, 2), + // DataField.String(DataFields.TorqueControllerName, 30, 25), + // DataField.String(DataFields.VinNumber, 57, 25), + // DataField.Number(DataFields.JobId, 84, 4), + // DataField.Number(DataFields.ParameterSetId, 90, 3), + // DataField.Number(DataFields.Strategy, 95, 2), + // new(DataFields.StrategyOptions, 99, 5, '0', PaddingOrientation.LeftPadded), + // DataField.Number(DataFields.BatchSize, 106, 4), + // DataField.Number(DataFields.BatchCounter, 112, 4), + // DataField.Number(DataFields.TighteningStatus, 118, 1), + // DataField.Number(DataFields.BatchStatus, 121, 1), + // DataField.Number(DataFields.TorqueStatus, 124, 1), + // DataField.Number(DataFields.AngleStatus, 127, 1), + // DataField.Number(DataFields.RundownAngleStatus, 130, 1), + // DataField.Number(DataFields.CurrentMonitoringStatus, 133, 1), + // DataField.Number(DataFields.SelftapStatus, 136, 1), + // DataField.Number(DataFields.PrevailTorqueMonitoringStatus, 139, 1), + // DataField.Number(DataFields.PrevailTorqueCompensateStatus, 142, 1), + // new(DataFields.TighteningErrorStatus, 145, 10, '0', PaddingOrientation.LeftPadded), + // DataField.Number(DataFields.TorqueMinLimit, 157, 6), + // DataField.Number(DataFields.TorqueMaxLimit, 165, 6), + // DataField.Number(DataFields.TorqueFinalTarget, 173, 6), + // DataField.Number(DataFields.Torque, 181, 6), + // DataField.Number(DataFields.AngleMinLimit, 189, 5), + // DataField.Number(DataFields.AngleMaxLimit, 196, 5), + // DataField.Number(DataFields.AngleFinalTarget, 203, 5), + // DataField.Number(DataFields.Angle, 210, 5), + // DataField.Number(DataFields.RundownAngleMin, 217, 5), + // DataField.Number(DataFields.RundownAngleMax, 224, 5), + // DataField.Number(DataFields.RundownAngle, 231, 5), + // DataField.Number(DataFields.CurrentMonitoringMin, 238, 3), + // DataField.Number(DataFields.CurrentMonitoringMax, 243, 3), + // DataField.Number(DataFields.CurrentMonitoringValue, 248, 3), + // DataField.Number(DataFields.SelftapMin, 253, 6), + // DataField.Number(DataFields.SelftapMax, 261, 6 ), + // DataField.Number(DataFields.SelftapTorque, 269, 6), + // DataField.Number(DataFields.PrevailTorqueMonitoringMin, 277, 6), + // DataField.Number(DataFields.PrevailTorqueMonitoringMax, 285, 6), + // DataField.Number(DataFields.PrevailTorque, 293, 6), + // DataField.Number(DataFields.TighteningId, 301, 10), + // DataField.Number(DataFields.JobSequenceNumber, 313, 5), + // DataField.Number(DataFields.SyncTighteningId, 320, 5), + // DataField.String(DataFields.ToolSerialNumber, 327, 14), + // DataField.Timestamp(DataFields.Timestamp, 343), + // DataField.Timestamp(DataFields.LastChangeInParameterSet, 364) + // } + // }, + // { + // 3, new List() + // { + // DataField.String(DataFields.ParameterSetName, 385, 25), + // DataField.Number(DataFields.TorqueValuesUnit, 412, 1), + // DataField.Number(DataFields.ResultType, 415, 2) + // } + // }, + // { + // 4, new List() + // { + // DataField.String(DataFields.IdentifierResultPart2, 419, 25), + // DataField.String(DataFields.IdentifierResultPart3, 446, 25), + // DataField.String(DataFields.IdentifierResultPart4, 473, 25) + // } + // }, + // { + // 5, new List() + // { + // DataField.String(DataFields.CustomerTighteningErrorCode, 500, 4) + // } + // }, + // { + // 6, new List() + // { + // DataField.Number(DataFields.PrevailTorqueCompensateValue, 506, 6), + // new(DataFields.TighteningErrorStatus2, 514, 10, '0', PaddingOrientation.LeftPadded) + // } + // }, + // { + // 7, new List() + // { + // DataField.Number(DataFields.CompensatedAngle, 526, 7), + // DataField.Number(DataFields.FinalAngleDecimal, 535, 7) + // } + // }, + // { + // 8, new List() + // { + // DataField.Number(DataFields.StartFinalAngle, 544, 6), + // DataField.Number(DataFields.PostViewTorqueActivated, 552, 1), + // DataField.Number(DataFields.PostViewTorqueHigh, 555, 6), + // DataField.Number(DataFields.PostViewTorqueLow, 563, 6) + // } + // }, + // { + // 9, new List() + // { + // DataField.Number(DataFields.CurrentMonitoringAmp, 571, 5), + // DataField.Number(DataFields.CurrentMonitoringAmpMin, 578, 5), + // DataField.Number(DataFields.CurrentMonitoringAmpMax, 585, 5) + // } + // }, + // { + // 10, new List() + // { + // DataField.Number(DataFields.AngleNumeratorScaleFactor, 592, 5), + // DataField.Number(DataFields.AngleDenominatorScaleFactor, 599, 5), + // DataField.Number(DataFields.OverallAngleStatus, 606, 1), + // DataField.Number(DataFields.OverallAngleMin, 609, 5), + // DataField.Number(DataFields.OverallAngleMax, 616, 5), + // DataField.Number(DataFields.OverallAngle, 623, 5), + // DataField.Number(DataFields.PeakTorque, 630, 6), + // DataField.Number(DataFields.ResidualBreakawayTorque, 638, 6), + // DataField.Number(DataFields.StartRundownAngle, 646, 6), + // DataField.Number(DataFields.RundownAngleComplete, 654, 6) + // } + // }, + // { + // 11, new List() + // { + // DataField.Number(DataFields.ClickTorque, 662, 6), + // DataField.Number(DataFields.ClickAngle, 670, 5), + // } + // }, + // { + // 12, new List() + // { + // DataField.Number(DataFields.SelectedIdentifierNumber, 677, 4), + // DataField.String(DataFields.JointId, 683, 25) + // } + // }, + // { + // 998, new List() + // { + // DataField.Number(DataFields.NumberOfStagesInMultistage, 526, 2), + // DataField.Number(DataFields.NumberOfStageResults, 530, 2), + // new(DataFields.StageResult, 534, 11) + // } + // }, + // { + // 999, new List() + // { + // DataField.String(DataFields.VinNumber, 20, 25, false), + // DataField.Number(DataFields.JobId, 45, 2, false), + // DataField.Number(DataFields.ParameterSetId, 47, 3, false), + // DataField.Number(DataFields.BatchSize, 50, 4, false), + // DataField.Number(DataFields.BatchCounter, 54, 4, false), + // DataField.Number(DataFields.BatchStatus, 58, 1, false), + // DataField.Number(DataFields.TighteningStatus, 59, 1, false), + // DataField.Number(DataFields.TorqueStatus, 60, 1, false), + // DataField.Number(DataFields.AngleStatus, 61, 1, false), + // DataField.Number(DataFields.Torque, 62, 6, false), + // DataField.Number(DataFields.Angle, 68, 5, false), + // DataField.Timestamp(DataFields.Timestamp, 73, false), + // DataField.Timestamp(DataFields.LastChangeInParameterSet, 92, false), + // DataField.Number(DataFields.TighteningId, 111, 10, false) + // } + // } + // }; + // } + + /// /// Obtain which revision we will work with for shared properties /// (since rev 1, 2 and 999 are way too different, they are processed in different datafields) diff --git a/src/OpenProtocolInterpreter/Tightening/StageResult.cs b/src/OpenProtocolInterpreter/Tightening/StageResult.cs index 99e7deeb..41007822 100644 --- a/src/OpenProtocolInterpreter/Tightening/StageResult.cs +++ b/src/OpenProtocolInterpreter/Tightening/StageResult.cs @@ -1,5 +1,8 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; namespace OpenProtocolInterpreter.Tightening { @@ -54,4 +57,37 @@ public static IEnumerable ParseAll(ReadOnlySpan value) return result; } } + + public class StageResultCollectionDefinitionAttribute : DataFieldDefinitionAttribute + { + public StageResultCollectionDefinitionAttribute(int revision) : base(revision) + { + + } + public StageResultCollectionDefinitionAttribute(int field, int revision) : base(field, revision) + { + + } + + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + { + return new DataField>(Field, index, Size, HasPrefix) + { + DefaultConverter = PackStageResults, + DefaultParser = ParseStageResults + }.Bind(mid, propertyInfo); + } + + private static string PackStageResults(char paddingChar, int size, PaddingOrientation orientation, List stageResults) + { + var builder = new StringBuilder(stageResults.Count * 11); + foreach (var stageResult in stageResults) + builder.Append(stageResult.Pack()); + + return builder.ToString(); + } + + private static List ParseStageResults(string value) + => StageResult.ParseAll(value).ToList(); + } } diff --git a/src/OpenProtocolInterpreter/Tightening/StrategyOptions.cs b/src/OpenProtocolInterpreter/Tightening/StrategyOptions.cs index 4db0b8fe..db2e5a94 100644 --- a/src/OpenProtocolInterpreter/Tightening/StrategyOptions.cs +++ b/src/OpenProtocolInterpreter/Tightening/StrategyOptions.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; using System.Text; namespace OpenProtocolInterpreter.Tightening @@ -54,21 +55,21 @@ public byte[] PackBytes() false }); - var asciiInt = System.BitConverter.ToInt32(bytes, 0).ToString("D5"); + var asciiInt = BitConverter.ToInt32(bytes, 0).ToString("D5"); return Encoding.ASCII.GetBytes(asciiInt); } public static StrategyOptions Parse(string value) { var intValue = OpenProtocolConvert.ToInt32(value); - var bytes = System.BitConverter.GetBytes(intValue); + var bytes = BitConverter.GetBytes(intValue); return Parse(bytes); } public static StrategyOptions Parse(ReadOnlySpan value) { var intValue = OpenProtocolConvert.ToInt32(value); - var bytes = System.BitConverter.GetBytes(intValue); + var bytes = BitConverter.GetBytes(intValue); return Parse(bytes); } @@ -92,4 +93,31 @@ public static StrategyOptions Parse(byte[] value) }; } } + + public class StrategyOptionsDefinitionAttribute : DataFieldDefinitionAttribute + { + public StrategyOptionsDefinitionAttribute(int revision) : base(revision) + { + + } + public StrategyOptionsDefinitionAttribute(int field, int revision) : base(field, revision) + { + + } + + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + { + return new DataField(Field, index, Size, HasPrefix) + { + DefaultConverter = PackStrategyOptions, + DefaultParser = ParseStrategyOptions + }.Bind(mid, propertyInfo); + } + + private static string PackStrategyOptions(char paddingChar, int size, PaddingOrientation orientation, StrategyOptions strategyOptions) + => strategyOptions.Pack().PadLeft(size, paddingChar); + + private static StrategyOptions ParseStrategyOptions(string value) + => StrategyOptions.Parse(value); + } } diff --git a/src/OpenProtocolInterpreter/Tightening/TighteningErrorStatus.cs b/src/OpenProtocolInterpreter/Tightening/TighteningErrorStatus.cs index 849f3090..4f101854 100644 --- a/src/OpenProtocolInterpreter/Tightening/TighteningErrorStatus.cs +++ b/src/OpenProtocolInterpreter/Tightening/TighteningErrorStatus.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; using System.Text; namespace OpenProtocolInterpreter.Tightening @@ -161,6 +162,33 @@ public static TighteningErrorStatus Parse(byte[] value) } } + public class TighteningErrorStatusDefinitionAttribute : DataFieldDefinitionAttribute + { + public TighteningErrorStatusDefinitionAttribute(int revision) : base(revision) + { + + } + public TighteningErrorStatusDefinitionAttribute(int field, int revision) : base(field, revision) + { + + } + + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + { + return new DataField(Field, index, Size, HasPrefix) + { + DefaultConverter = PackTighteningErrorStatus, + DefaultParser = ParseTighteningErrorStatus + }.Bind(mid, propertyInfo); + } + + private static string PackTighteningErrorStatus(char paddingChar, int size, PaddingOrientation orientation, TighteningErrorStatus tighteningErrorStatus) + => tighteningErrorStatus.Pack().PadLeft(size, paddingChar); + + private static TighteningErrorStatus ParseTighteningErrorStatus(string value) + => TighteningErrorStatus.Parse(value); + } + public class TighteningErrorStatus2 { public bool DriveDeactivated { get; set; } @@ -293,4 +321,32 @@ public static TighteningErrorStatus2 Parse(byte[] value) return obj; } } + + public class TighteningErrorStatus2DefinitionAttribute : DataFieldDefinitionAttribute + { + public TighteningErrorStatus2DefinitionAttribute(int revision) : base(revision) + { + + } + public TighteningErrorStatus2DefinitionAttribute(int field, int revision) : base(field, revision) + { + + } + + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + { + return new DataField(Field, index, Size, HasPrefix) + { + DefaultConverter = PackTighteningErrorStatus2, + DefaultParser = ParseTighteningErrorStatus2 + }.Bind(mid, propertyInfo); + } + + private static string PackTighteningErrorStatus2(char paddingChar, int size, PaddingOrientation orientation, TighteningErrorStatus2 tighteningErrorStatus) + => tighteningErrorStatus.Pack().PadLeft(size, paddingChar); + + private static TighteningErrorStatus2 ParseTighteningErrorStatus2(string value) + => TighteningErrorStatus2.Parse(value); + } + } From ba0b6dfc62c19921e5d5efa7683fce4300ab27db Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Thu, 9 Jul 2026 14:29:55 -0300 Subject: [PATCH 37/78] Fixes on field counting and cleaned up some garbage --- .../ApplicationSelector/Mid0251.cs | 10 +- .../ApplicationSelector/Mid0254.cs | 4 +- .../ApplicationSelector/Mid0255.cs | 4 +- .../ApplicationToolLocationSystem/Mid0265.cs | 4 +- .../AutomaticManualMode/Mid0401.cs | 2 +- .../AutomaticManualMode/Mid0411.cs | 4 +- .../Communication/Mid0001.cs | 6 +- .../Communication/Mid0002.cs | 38 +-- .../Communication/Mid0004.cs | 6 +- .../Communication/Mid0005.cs | 2 +- .../Communication/Mid0006.cs | 12 +- .../Communication/Mid0008.cs | 12 +- .../Communication/Mid0009.cs | 12 +- .../IOInterface/Mid0200.cs | 29 +- .../IOInterface/Mid0214.cs | 12 +- .../IOInterface/Mid0215.cs | 3 + .../Tightening/Mid0061.cs | 287 +----------------- 17 files changed, 89 insertions(+), 358 deletions(-) diff --git a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0251.cs b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0251.cs index 8e20d760..6b93f924 100644 --- a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0251.cs +++ b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0251.cs @@ -20,11 +20,11 @@ public class Mid0251 : Mid, IApplicationSelector, IController, IAcknowledgeable< { public const int MID = 251; - [Int32DataFieldDefinition(field: 0, revision: 1, Size = 2)] - public int DeviceId { get; set; } [Int32DataFieldDefinition(field: 1, revision: 1, Size = 2)] + public int DeviceId { get; set; } + [Int32DataFieldDefinition(field: 2, revision: 1, Size = 2)] public int NumberOfSockets { get; set; } - [SocketStatusListDefinition(field: 2, revision: 1)] + [SocketStatusListDefinition(field: 3, revision: 1)] public List SocketStatus { get; set; } public Mid0251() : this(new Header() @@ -44,13 +44,13 @@ public Mid0251(Header header) : base(header) public override string Pack() { NumberOfSockets = SocketStatus.Count; - GetField(revision: 1, field: 2).Size = NumberOfSockets; + GetField(nameof(SocketStatus)).Size = NumberOfSockets; return base.Pack(); } protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - if (dataField.Field == 2) + if (dataField.Field == 3) { dataField.Size = NumberOfSockets; } diff --git a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0254.cs b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0254.cs index 8663a281..cfe50e96 100644 --- a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0254.cs +++ b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0254.cs @@ -26,10 +26,10 @@ public class Mid0254 : Mid, IApplicationSelector, IIntegrator, IAcceptableComman public IEnumerable DocumentedPossibleErrors => new Error[] { Error.FaultyIODeviceId }; - [Int32DataFieldDefinition(field: 0, revision: 1, Size = 2)] + [Int32DataFieldDefinition(field: 1, revision: 1, Size = 2)] public int DeviceId { get; set; } - [EnumCollectionDefinition(field: 1, revision: 1, Size = 8)] + [EnumCollectionDefinition(field: 2, revision: 1, Size = 8)] public List GreenLights { get; set; } public Mid0254() : this(new Header() diff --git a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0255.cs b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0255.cs index a2d35d4e..6b7f28bf 100644 --- a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0255.cs +++ b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0255.cs @@ -23,9 +23,9 @@ public class Mid0255 : Mid, IApplicationSelector, IIntegrator, IAcceptableComman public IEnumerable DocumentedPossibleErrors => new Error[] { Error.FaultyIODeviceId }; - [Int32DataFieldDefinition(field: 0, revision: 1, Size = 2)] + [Int32DataFieldDefinition(field: 1, revision: 1, Size = 2)] public int DeviceId { get; set; } - [EnumCollectionDefinition(field: 1, revision: 1, Size = 8)] + [EnumCollectionDefinition(field: 2, revision: 1, Size = 8)] public List RedLights { get; set; } public Mid0255() : this(new Header() diff --git a/src/OpenProtocolInterpreter/ApplicationToolLocationSystem/Mid0265.cs b/src/OpenProtocolInterpreter/ApplicationToolLocationSystem/Mid0265.cs index 27c9b2ca..77f6350d 100644 --- a/src/OpenProtocolInterpreter/ApplicationToolLocationSystem/Mid0265.cs +++ b/src/OpenProtocolInterpreter/ApplicationToolLocationSystem/Mid0265.cs @@ -15,10 +15,10 @@ public class Mid0265 : Mid, IApplicationToolLocationSystem, IIntegrator, IAccept public IEnumerable DocumentedPossibleErrors => new Error[] { Error.MidRevisionUnsupported }; - [StringDataFieldDefinition(field: 0, revision: 1, Size = 8)] + [StringDataFieldDefinition(field: 1, revision: 1, Size = 8)] public string ToolTagId { get; set; } - [Int32DataFieldDefinition(field: 1, revision: 1, Size = 2)] + [Int32DataFieldDefinition(field: 2, revision: 1, Size = 2)] public ToolStatus ToolStatus { get; set; } public Mid0265() : this(new Header() diff --git a/src/OpenProtocolInterpreter/AutomaticManualMode/Mid0401.cs b/src/OpenProtocolInterpreter/AutomaticManualMode/Mid0401.cs index f37832f5..a734539a 100644 --- a/src/OpenProtocolInterpreter/AutomaticManualMode/Mid0401.cs +++ b/src/OpenProtocolInterpreter/AutomaticManualMode/Mid0401.cs @@ -20,7 +20,7 @@ public class Mid0401 : Mid, IAutomaticManualMode, IController, IAcknowledgeable< /// Automatic Mode = false (0) /// Manual Mode = true (1) /// - [BooleanDataFieldDefinition(field: 0, revision: 1, HasPrefix = false)] + [BooleanDataFieldDefinition(field: 1, revision: 1, HasPrefix = false)] public bool ManualAutomaticMode { get; set; } public Mid0401() : this(new Header() diff --git a/src/OpenProtocolInterpreter/AutomaticManualMode/Mid0411.cs b/src/OpenProtocolInterpreter/AutomaticManualMode/Mid0411.cs index 57008c6e..9d24af3a 100644 --- a/src/OpenProtocolInterpreter/AutomaticManualMode/Mid0411.cs +++ b/src/OpenProtocolInterpreter/AutomaticManualMode/Mid0411.cs @@ -33,9 +33,9 @@ public class Mid0411 : Mid, IAutomaticManualMode, IController { public const int MID = 411; - [Int32DataFieldDefinition(field: 0, revision: 1, Size = 2, HasPrefix = false)] - public int AutoDisableSetting { get; set; } [Int32DataFieldDefinition(field: 1, revision: 1, Size = 2, HasPrefix = false)] + public int AutoDisableSetting { get; set; } + [Int32DataFieldDefinition(field: 2, revision: 1, Size = 2, HasPrefix = false)] public int CurrentBatch { get; set; } public Mid0411() : this(new Header() diff --git a/src/OpenProtocolInterpreter/Communication/Mid0001.cs b/src/OpenProtocolInterpreter/Communication/Mid0001.cs index 0efc5c8b..a1bd0179 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0001.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0001.cs @@ -15,13 +15,13 @@ public class Mid0001 : Mid, ICommunication, IIntegrator, IAnswerableBy, public IEnumerable DocumentedPossibleErrors => new Error[] { Error.ClientAlreadyConnected, Error.MidRevisionUnsupported }; - [BooleanDataFieldDefinition(field: 0, revision: 7)] + [BooleanDataFieldDefinition(field: 1, revision: 7)] public bool OptionalKeepAlive { get; set; } - [BooleanDataFieldDefinition(field: 1, revision: 8)] + [BooleanDataFieldDefinition(field: 2, revision: 8)] public bool OptionalToolLockAtDisconnection { get; set; } - [DecimalDataFieldDefinition(field: 2, revision: 8, Size = 4)] + [DecimalDataFieldDefinition(field: 3, revision: 8, Size = 4)] public decimal OptionalEarlyLock { get; set; } public Mid0001() : this(DEFAULT_REVISION) diff --git a/src/OpenProtocolInterpreter/Communication/Mid0002.cs b/src/OpenProtocolInterpreter/Communication/Mid0002.cs index 19ed67b9..a4dd79f0 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0002.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0002.cs @@ -17,38 +17,38 @@ public class Mid0002 : Mid, ICommunication, IController { public const int MID = 2; - [Int32DataFieldDefinition(field: 0, revision: 1, Size = 4)] + [Int32DataFieldDefinition(field: 1, revision: 1, Size = 4)] public int CellId { get; set; } - [Int32DataFieldDefinition(field: 1, revision: 1, Size = 2)] + [Int32DataFieldDefinition(field: 2, revision: 1, Size = 2)] public int ChannelId { get; set; } - [StringDataFieldDefinition(field: 2, revision: 1, Size = 25)] + [StringDataFieldDefinition(field: 3, revision: 1, Size = 25)] public string ControllerName { get; set; } //Rev 2 - [StringDataFieldDefinition(field: 3, revision: 2, Size = 3)] + [StringDataFieldDefinition(field: 4, revision: 2, Size = 3)] public string SupplierCode { get; set; } //Rev 3 - [StringDataFieldDefinition(field: 4, revision: 3, Size = 19)] + [StringDataFieldDefinition(field: 5, revision: 3, Size = 19)] public string OpenProtocolVersion { get; set; } - [StringDataFieldDefinition(field: 5, revision: 3, Size = 19)] + [StringDataFieldDefinition(field: 6, revision: 3, Size = 19)] public string ControllerSoftwareVersion { get; set; } - [StringDataFieldDefinition(field: 6, revision: 3, Size = 19)] + [StringDataFieldDefinition(field: 7, revision: 3, Size = 19)] public string ToolSoftwareVersion { get; set; } //Rev 4 - [StringDataFieldDefinition(field: 7, revision: 4, Size = 24)] + [StringDataFieldDefinition(field: 8, revision: 4, Size = 24)] public string RBUType { get; set; } - [StringDataFieldDefinition(field: 8, revision: 4, Size = 10)] + [StringDataFieldDefinition(field: 9, revision: 4, Size = 10)] public string ControllerSerialNumber { get; set; } //Rev 5 - [Int32DataFieldDefinition(field: 9, revision: 5, Size = 3)] + [Int32DataFieldDefinition(field: 10, revision: 5, Size = 3)] public SystemType SystemType { get; set; } /// @@ -60,31 +60,31 @@ public class Mid0002 : Mid, ICommunication, IController /// 002 = a system running presses instead of spindles. /// - [Int32DataFieldDefinition(field: 10, revision: 5, Size = 3)] + [Int32DataFieldDefinition(field: 11, revision: 5, Size = 3)] public SystemSubType SystemSubType { get; set; } //Rev 6 - [BooleanDataFieldDefinition(field: 11, revision: 6)] + [BooleanDataFieldDefinition(field: 12, revision: 6)] public bool SequenceNumberSupport { get; set; } - [BooleanDataFieldDefinition(field: 12, revision: 6)] + [BooleanDataFieldDefinition(field: 13, revision: 6)] public bool LinkingHandlingSupport { get; set; } /// /// Station ID for PF6000 /// Cell ID for PF4000 /// - [Int64DataFieldDefinition(field: 13, revision: 6, Size = 10)] + [Int64DataFieldDefinition(field: 14, revision: 6, Size = 10)] public long StationCellId { get; set; } /// /// Station ID for PF6000 /// Cell ID for PF4000 /// - [StringDataFieldDefinition(field: 14, revision: 6, Size = 25)] + [StringDataFieldDefinition(field: 15, revision: 6, Size = 25)] public string StationCellName { get; set; } - [StringDataFieldDefinition(field: 15, revision: 6, Size = 1)] + [StringDataFieldDefinition(field: 16, revision: 6, Size = 1)] public string ClientId { get; set; } //Rev 7 @@ -92,13 +92,13 @@ public class Mid0002 : Mid, ICommunication, IController /// False = Use Keep alive (Keep alive is mandatory) /// True = Ignore Keep alive (Keep alive is optional) /// - [BooleanDataFieldDefinition(field: 16, revision: 7)] + [BooleanDataFieldDefinition(field: 17, revision: 7)] public bool OptionalKeepAlive { get; set; } - [BooleanDataFieldDefinition(field: 17, revision: 8)] + [BooleanDataFieldDefinition(field: 18, revision: 8)] public bool OptionalToolLockAtDisconnection { get; set; } - [DecimalDataFieldDefinition(field: 18, revision: 8, Size = 1)] + [DecimalDataFieldDefinition(field: 19, revision: 8, Size = 1)] public decimal OptionalEarlyLock { get; set; } public Mid0002() : this(DEFAULT_REVISION) diff --git a/src/OpenProtocolInterpreter/Communication/Mid0004.cs b/src/OpenProtocolInterpreter/Communication/Mid0004.cs index fe82d1a2..cfcda05b 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0004.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0004.cs @@ -23,10 +23,10 @@ public class Mid0004 : Mid, ICommunication, IController { public const int MID = 4; - [Int32DataFieldDefinition(field: 0, revision: 1, Size = 4, HasPrefix = false)] + [Int32DataFieldDefinition(field: 1, revision: 1, Size = 4, HasPrefix = false)] public int FailedMid { get; set; } - [Int32DataFieldDefinition(field: 1, revision: 1, Size = 2, HasPrefix = false)] + [Int32DataFieldDefinition(field: 2, revision: 1, Size = 2, HasPrefix = false)] public Error ErrorCode { get; set; } public Mid0004() : this(DEFAULT_REVISION) @@ -60,7 +60,7 @@ protected override void ProcessDataFields(ReadOnlySpan package) } private void HandleRevision() - => GetField(revision: 1, field: 1).Size = Header.Revision > 1 ? 3 : 2; + => GetField(nameof(ErrorCode)).Size = Header.Revision > 1 ? 3 : 2; [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields diff --git a/src/OpenProtocolInterpreter/Communication/Mid0005.cs b/src/OpenProtocolInterpreter/Communication/Mid0005.cs index 522cd2d3..2620623c 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0005.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0005.cs @@ -27,7 +27,7 @@ public class Mid0005 : Mid, ICommunication, IController { public const int MID = 5; - [Int32DataFieldDefinition(field: 0, revision: 1, Size = 4, HasPrefix = false)] + [Int32DataFieldDefinition(field: 1, revision: 1, Size = 4, HasPrefix = false)] public int MidAccepted { get; set; } public Mid0005() : this(DEFAULT_REVISION) diff --git a/src/OpenProtocolInterpreter/Communication/Mid0006.cs b/src/OpenProtocolInterpreter/Communication/Mid0006.cs index b2bb8a7c..04b76b09 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0006.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0006.cs @@ -20,16 +20,16 @@ public class Mid0006 : Mid, ICommunication, IIntegrator { public const int MID = 6; - [Int32DataFieldDefinition(field: 0, revision: 1, Size = 4, HasPrefix = false)] + [Int32DataFieldDefinition(field: 1, revision: 1, Size = 4, HasPrefix = false)] public int RequestedMid { get; set; } - [Int32DataFieldDefinition(field: 1, revision: 1, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(field: 2, revision: 1, Size = 3, HasPrefix = false)] public int WantedRevision { get; set; } - [Int32DataFieldDefinition(field: 2, revision: 1, Size = 2, HasPrefix = false)] + [Int32DataFieldDefinition(field: 3, revision: 1, Size = 2, HasPrefix = false)] public int ExtraDataLength { get; set; } - [StringDataFieldDefinition(field: 3, revision: 1, Size = 0, HasPrefix = false)] + [StringDataFieldDefinition(field: 4, revision: 1, Size = 0, HasPrefix = false)] public string ExtraData { get; set; } public Mid0006() : this(new Header() @@ -55,7 +55,7 @@ public override string Pack() protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { base.ProcessDataField(dataField, package); - if (dataField.Field == 2) + if (dataField.Field == 3) { HandleExtraDataFieldSize(); } @@ -63,7 +63,7 @@ protected override void ProcessDataField(DataField dataField, ReadOnlySpan private void HandleExtraDataFieldSize() { - GetField(revision: 1, field: 3).Size = ExtraDataLength; + GetField(nameof(ExtraData)).Size = ExtraDataLength; } [Obsolete("Use DataFieldDefinition attributes instead")] diff --git a/src/OpenProtocolInterpreter/Communication/Mid0008.cs b/src/OpenProtocolInterpreter/Communication/Mid0008.cs index 16d63531..42e2a1d8 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0008.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0008.cs @@ -24,13 +24,13 @@ public class Mid0008 : Mid, ICommunication, IIntegrator { public const int MID = 8; - [Int32DataFieldDefinition(field: 0, revision: 1, Size = 4, HasPrefix = false)] + [Int32DataFieldDefinition(field: 1, revision: 1, Size = 4, HasPrefix = false)] public int SubscriptionMid { get; set; } - [Int32DataFieldDefinition(field: 1, revision: 1, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(field: 2, revision: 1, Size = 3, HasPrefix = false)] public int WantedRevision { get; set; } - [Int32DataFieldDefinition(field: 2, revision: 1, Size = 2, HasPrefix = false)] + [Int32DataFieldDefinition(field: 3, revision: 1, Size = 2, HasPrefix = false)] public int ExtraDataLength { get; set; } - [StringDataFieldDefinition(field: 3, revision: 1, Size = 0, HasPrefix = false)] + [StringDataFieldDefinition(field: 4, revision: 1, Size = 0, HasPrefix = false)] public string ExtraData { get; set; } public Mid0008() : this(new Header() @@ -56,7 +56,7 @@ public override string Pack() protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { base.ProcessDataField(dataField, package); - if (dataField.Field == 2) + if (dataField.Field == 3) { HandleExtraDataFieldSize(); } @@ -64,7 +64,7 @@ protected override void ProcessDataField(DataField dataField, ReadOnlySpan private void HandleExtraDataFieldSize() { - GetField(revision: 1, field: 3).Size = ExtraDataLength; + GetField(nameof(ExtraData)).Size = ExtraDataLength; } [Obsolete("Use DataFieldDefinition attributes instead")] diff --git a/src/OpenProtocolInterpreter/Communication/Mid0009.cs b/src/OpenProtocolInterpreter/Communication/Mid0009.cs index 75370389..1e5631e2 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0009.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0009.cs @@ -23,13 +23,13 @@ public class Mid0009 : Mid, ICommunication, IIntegrator { public const int MID = 9; - [Int32DataFieldDefinition(field: 0, revision: 1, Size = 4, HasPrefix = false)] + [Int32DataFieldDefinition(field: 1, revision: 1, Size = 4, HasPrefix = false)] public int UnsubscriptionMid { get; set; } - [Int32DataFieldDefinition(field: 1, revision: 1, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(field: 2, revision: 1, Size = 3, HasPrefix = false)] public int ExtraDataRevision { get; set; } - [Int32DataFieldDefinition(field: 2, revision: 1, Size = 2, HasPrefix = false)] + [Int32DataFieldDefinition(field: 3, revision: 1, Size = 2, HasPrefix = false)] public int ExtraDataLength { get; set; } - [StringDataFieldDefinition(field: 3, revision: 1, Size = 0, HasPrefix = false)] + [StringDataFieldDefinition(field: 4, revision: 1, Size = 0, HasPrefix = false)] public string ExtraData { get; set; } public Mid0009() : this(new Header() @@ -55,7 +55,7 @@ public override string Pack() protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { base.ProcessDataField(dataField, package); - if (dataField.Field == 2) + if (dataField.Field == 3) { HandleExtraDataFieldSize(); } @@ -63,7 +63,7 @@ protected override void ProcessDataField(DataField dataField, ReadOnlySpan private void HandleExtraDataFieldSize() { - GetField(revision: 1, field: 3).Size = ExtraDataLength; + GetField(nameof(ExtraData)).Size = ExtraDataLength; } [Obsolete("Use DataFieldDefinition attributes instead")] diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0200.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0200.cs index a77d1485..6567fe23 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0200.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0200.cs @@ -16,25 +16,34 @@ public class Mid0200 : Mid, IIOInterface, IIntegrator, IAcceptableCommand { public const int MID = 200; - [Int32DataFieldDefinition(field: 0, revision: 1, Size = 1, HasPrefix = false)] - public RelayStatus StatusRelayOne { get; set; } [Int32DataFieldDefinition(field: 1, revision: 1, Size = 1, HasPrefix = false)] - public RelayStatus StatusRelayTwo { get; set; } + public RelayStatus StatusRelayOne { get; set; } + [Int32DataFieldDefinition(field: 2, revision: 1, Size = 1, HasPrefix = false)] - public RelayStatus StatusRelayThree { get; set; } + public RelayStatus StatusRelayTwo { get; set; } + [Int32DataFieldDefinition(field: 3, revision: 1, Size = 1, HasPrefix = false)] - public RelayStatus StatusRelayFour { get; set; } + public RelayStatus StatusRelayThree { get; set; } + [Int32DataFieldDefinition(field: 4, revision: 1, Size = 1, HasPrefix = false)] - public RelayStatus StatusRelayFive { get; set; } + public RelayStatus StatusRelayFour { get; set; } + [Int32DataFieldDefinition(field: 5, revision: 1, Size = 1, HasPrefix = false)] - public RelayStatus StatusRelaySix { get; set; } + public RelayStatus StatusRelayFive { get; set; } + [Int32DataFieldDefinition(field: 6, revision: 1, Size = 1, HasPrefix = false)] - public RelayStatus StatusRelaySeven { get; set; } + public RelayStatus StatusRelaySix { get; set; } + [Int32DataFieldDefinition(field: 7, revision: 1, Size = 1, HasPrefix = false)] - public RelayStatus StatusRelayEight { get; set; } + public RelayStatus StatusRelaySeven { get; set; } + [Int32DataFieldDefinition(field: 8, revision: 1, Size = 1, HasPrefix = false)] - public RelayStatus StatusRelayNine { get; set; } + public RelayStatus StatusRelayEight { get; set; } + [Int32DataFieldDefinition(field: 9, revision: 1, Size = 1, HasPrefix = false)] + public RelayStatus StatusRelayNine { get; set; } + + [Int32DataFieldDefinition(field: 10, revision: 1, Size = 1, HasPrefix = false)] public RelayStatus StatusRelayTen { get; set; } public Mid0200() : this(new Header() diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0214.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0214.cs index 949aca33..cd13babe 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0214.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0214.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.IOInterface { @@ -20,12 +21,8 @@ public class Mid0214 : Mid, IIOInterface, IIntegrator, IAnswerableBy, I public IEnumerable DocumentedPossibleErrors => new Error[] { Error.FaultyIODeviceId, Error.IODeviceNotConnected }; - [Int32DataFieldDefinition(field: 0, revision: 1, Size = 2, HasPrefix = false)] - public int DeviceNumber - { - get => GetField(1, DataFields.DeviceNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.DeviceNumber).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(field: 1, revision: 1, Size = 2, HasPrefix = false)] + public int DeviceNumber { get; set; } public Mid0214() : this(DEFAULT_REVISION) { @@ -43,6 +40,7 @@ public Mid0214(int revision) : this(new Header() { } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { DeviceNumber diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs index d027747c..75512a46 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs @@ -27,14 +27,17 @@ public class Mid0215 : Mid, IIOInterface, IController [Int32DataFieldDefinition(field: 1, revision: 1, Size = 2)] public int IODeviceId { get; set; } + [RelayCollectionDefinition(field: 2, revision: 1, Size = 4 * 8)] public List Relays { get; set; } + [DigitalInputCollectionDefinition(field: 3, revision: 1, Size = 4 * 8)] public List DigitalInputs { get; set; } //At revision 2 number of relays/digital inputs comes before their lists [Int32DataFieldDefinition(field: 4, revision: 2, Index = 24, Size = 2)] public int NumberOfRelays { get; set; } + [Int32DataFieldDefinition(field: 5, revision: 2, Index = 0, Size = 2)] public int NumberOfDigitalInputs { get; set; } diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0061.cs b/src/OpenProtocolInterpreter/Tightening/Mid0061.cs index f22e9d27..1f274da1 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0061.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0061.cs @@ -349,41 +349,6 @@ public override string Pack() int prefixIndex = 1; builder.Append(BuildHeader()); builder.Append(Pack(fields, ref prefixIndex)); - // if (Header.Revision > 1 && Header.Revision != 999) - // { - // GetField(2, DataFields.StrategyOptions).SetValue(StrategyOptions.Pack()); - // GetField(2, DataFields.TighteningErrorStatus).SetValue(TighteningErrorStatus.Pack()); - - // if (Header.Revision > 5) - // { - // GetField(6, DataFields.TighteningErrorStatus2).SetValue(TighteningErrorStatus2.Pack()); - // } - - // if (Header.Revision == 998) - // { - // NumberOfStageResults = StageResults.Count; - // var stageResultField = GetField(998, DataFields.StageResult); - // stageResultField.Size = StageResults.Count * 11; - // stageResultField.SetValue(PackStageResults()); - // } - - // builder.Append(BuildHeader()); - // int processUntilRevision = Header.Revision != 998 ? Header.Revision : 6; - // for (int revision = 2; revision <= processUntilRevision; revision++) - // { - // builder.Append(Pack(revision, ref prefixIndex)); - // } - - // if (Header.Revision == 998) - // { - // builder.Append(Pack(998, ref prefixIndex)); - // } - // } - // else - // { - // builder.Append(BuildHeader()); - // builder.Append(Pack(Header.Revision, ref prefixIndex)); - // } return builder.ToString(); } @@ -392,47 +357,15 @@ protected override void ProcessDataFields(ReadOnlySpan package) { var fields = DataFieldsByRevision().OrderBy(f => f.Index).ToList(); ProcessDataFields(fields, package); - // if (Header.Revision == 1 || Header.Revision == 999) - // { - // ProcessDataFields(Header.Revision, package); - // } - // else - // { - // int processUntilRevision = Header.Revision; - // if (Header.Revision == 998) - // { - // processUntilRevision = 6; - // var stageResultField = GetField(998, DataFields.StageResult); - // stageResultField.Size = Header.Length - stageResultField.Index - 2; - // ProcessDataFields(998, package); - // StageResults = StageResult.ParseAll(stageResultField.Value).ToList(); - // } - - // for (int revision = 2; revision <= processUntilRevision; revision++) - // ProcessDataFields(revision, package); - - // var strategyOptionsField = GetField(2, DataFields.StrategyOptions); - // StrategyOptions = StrategyOptions.Parse(strategyOptionsField.Value); - - // var tighteningErrorStatusField = GetField(2, DataFields.TighteningErrorStatus); - // TighteningErrorStatus = TighteningErrorStatus.Parse(tighteningErrorStatusField.Value); - - // if (Header.Revision > 5) - // { - // var tighteningErrorStatus2Field = GetField(6, DataFields.TighteningErrorStatus2); - // TighteningErrorStatus2 = TighteningErrorStatus2.Parse(tighteningErrorStatus2Field.Value); - // } - // } } protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - base.ProcessDataField(dataField, package); - if (Header.StandardizedRevision == 998 && dataField.Field == 57) + if (Header.StandardizedRevision == 998 && dataField.Field == 58) { - var stageResultField = GetField(nameof(StageResults)); - stageResultField.Size = NumberOfStageResults * 11; + dataField.Size = NumberOfStageResults * 11; } + base.ProcessDataField(dataField, package); } private IEnumerable DataFieldsByRevision() @@ -463,219 +396,7 @@ private IEnumerable DataFieldsByRevision() } } - // protected override Dictionary> RegisterDatafields() - // { - // //opted to work with a different approuch (since it would need to modify too much fields) - // return new Dictionary>() - // { - // { - // 1, new List() - // { - // DataField.Number(DataFields.CellId, 20, 4), - // DataField.Number(DataFields.ChannelId, 26, 2), - // DataField.String(DataFields.TorqueControllerName, 30, 25), - // DataField.String(DataFields.VinNumber, 57, 25), - // DataField.Number(DataFields.JobId, 84, 2), - // DataField.Number(DataFields.ParameterSetId, 88, 3), - // DataField.Number(DataFields.BatchSize, 93, 4), - // DataField.Number(DataFields.BatchCounter, 99, 4), - // DataField.Number(DataFields.TighteningStatus, 105, 1), - // DataField.Number(DataFields.TorqueStatus, 108, 1), - // DataField.Number(DataFields.AngleStatus, 111, 1), - // DataField.Number(DataFields.TorqueMinLimit, 114, 6), - // DataField.Number(DataFields.TorqueMaxLimit, 122, 6), - // DataField.Number(DataFields.TorqueFinalTarget, 130, 6), - // DataField.Number(DataFields.Torque, 138, 6), - // DataField.Number(DataFields.AngleMinLimit, 146, 5), - // DataField.Number(DataFields.AngleMaxLimit, 153, 5), - // DataField.Number(DataFields.AngleFinalTarget, 160, 5), - // DataField.Number(DataFields.Angle, 167, 5), - // DataField.Timestamp(DataFields.Timestamp, 174), - // DataField.Timestamp(DataFields.LastChangeInParameterSet, 195), - // DataField.Number(DataFields.BatchStatus, 216, 1), - // DataField.Number(DataFields.TighteningId, 219, 10) - // } - // }, - // { - // 2, new List() - // { - // DataField.Number(DataFields.CellId, 20, 4), - // DataField.Number(DataFields.ChannelId, 26, 2), - // DataField.String(DataFields.TorqueControllerName, 30, 25), - // DataField.String(DataFields.VinNumber, 57, 25), - // DataField.Number(DataFields.JobId, 84, 4), - // DataField.Number(DataFields.ParameterSetId, 90, 3), - // DataField.Number(DataFields.Strategy, 95, 2), - // new(DataFields.StrategyOptions, 99, 5, '0', PaddingOrientation.LeftPadded), - // DataField.Number(DataFields.BatchSize, 106, 4), - // DataField.Number(DataFields.BatchCounter, 112, 4), - // DataField.Number(DataFields.TighteningStatus, 118, 1), - // DataField.Number(DataFields.BatchStatus, 121, 1), - // DataField.Number(DataFields.TorqueStatus, 124, 1), - // DataField.Number(DataFields.AngleStatus, 127, 1), - // DataField.Number(DataFields.RundownAngleStatus, 130, 1), - // DataField.Number(DataFields.CurrentMonitoringStatus, 133, 1), - // DataField.Number(DataFields.SelftapStatus, 136, 1), - // DataField.Number(DataFields.PrevailTorqueMonitoringStatus, 139, 1), - // DataField.Number(DataFields.PrevailTorqueCompensateStatus, 142, 1), - // new(DataFields.TighteningErrorStatus, 145, 10, '0', PaddingOrientation.LeftPadded), - // DataField.Number(DataFields.TorqueMinLimit, 157, 6), - // DataField.Number(DataFields.TorqueMaxLimit, 165, 6), - // DataField.Number(DataFields.TorqueFinalTarget, 173, 6), - // DataField.Number(DataFields.Torque, 181, 6), - // DataField.Number(DataFields.AngleMinLimit, 189, 5), - // DataField.Number(DataFields.AngleMaxLimit, 196, 5), - // DataField.Number(DataFields.AngleFinalTarget, 203, 5), - // DataField.Number(DataFields.Angle, 210, 5), - // DataField.Number(DataFields.RundownAngleMin, 217, 5), - // DataField.Number(DataFields.RundownAngleMax, 224, 5), - // DataField.Number(DataFields.RundownAngle, 231, 5), - // DataField.Number(DataFields.CurrentMonitoringMin, 238, 3), - // DataField.Number(DataFields.CurrentMonitoringMax, 243, 3), - // DataField.Number(DataFields.CurrentMonitoringValue, 248, 3), - // DataField.Number(DataFields.SelftapMin, 253, 6), - // DataField.Number(DataFields.SelftapMax, 261, 6 ), - // DataField.Number(DataFields.SelftapTorque, 269, 6), - // DataField.Number(DataFields.PrevailTorqueMonitoringMin, 277, 6), - // DataField.Number(DataFields.PrevailTorqueMonitoringMax, 285, 6), - // DataField.Number(DataFields.PrevailTorque, 293, 6), - // DataField.Number(DataFields.TighteningId, 301, 10), - // DataField.Number(DataFields.JobSequenceNumber, 313, 5), - // DataField.Number(DataFields.SyncTighteningId, 320, 5), - // DataField.String(DataFields.ToolSerialNumber, 327, 14), - // DataField.Timestamp(DataFields.Timestamp, 343), - // DataField.Timestamp(DataFields.LastChangeInParameterSet, 364) - // } - // }, - // { - // 3, new List() - // { - // DataField.String(DataFields.ParameterSetName, 385, 25), - // DataField.Number(DataFields.TorqueValuesUnit, 412, 1), - // DataField.Number(DataFields.ResultType, 415, 2) - // } - // }, - // { - // 4, new List() - // { - // DataField.String(DataFields.IdentifierResultPart2, 419, 25), - // DataField.String(DataFields.IdentifierResultPart3, 446, 25), - // DataField.String(DataFields.IdentifierResultPart4, 473, 25) - // } - // }, - // { - // 5, new List() - // { - // DataField.String(DataFields.CustomerTighteningErrorCode, 500, 4) - // } - // }, - // { - // 6, new List() - // { - // DataField.Number(DataFields.PrevailTorqueCompensateValue, 506, 6), - // new(DataFields.TighteningErrorStatus2, 514, 10, '0', PaddingOrientation.LeftPadded) - // } - // }, - // { - // 7, new List() - // { - // DataField.Number(DataFields.CompensatedAngle, 526, 7), - // DataField.Number(DataFields.FinalAngleDecimal, 535, 7) - // } - // }, - // { - // 8, new List() - // { - // DataField.Number(DataFields.StartFinalAngle, 544, 6), - // DataField.Number(DataFields.PostViewTorqueActivated, 552, 1), - // DataField.Number(DataFields.PostViewTorqueHigh, 555, 6), - // DataField.Number(DataFields.PostViewTorqueLow, 563, 6) - // } - // }, - // { - // 9, new List() - // { - // DataField.Number(DataFields.CurrentMonitoringAmp, 571, 5), - // DataField.Number(DataFields.CurrentMonitoringAmpMin, 578, 5), - // DataField.Number(DataFields.CurrentMonitoringAmpMax, 585, 5) - // } - // }, - // { - // 10, new List() - // { - // DataField.Number(DataFields.AngleNumeratorScaleFactor, 592, 5), - // DataField.Number(DataFields.AngleDenominatorScaleFactor, 599, 5), - // DataField.Number(DataFields.OverallAngleStatus, 606, 1), - // DataField.Number(DataFields.OverallAngleMin, 609, 5), - // DataField.Number(DataFields.OverallAngleMax, 616, 5), - // DataField.Number(DataFields.OverallAngle, 623, 5), - // DataField.Number(DataFields.PeakTorque, 630, 6), - // DataField.Number(DataFields.ResidualBreakawayTorque, 638, 6), - // DataField.Number(DataFields.StartRundownAngle, 646, 6), - // DataField.Number(DataFields.RundownAngleComplete, 654, 6) - // } - // }, - // { - // 11, new List() - // { - // DataField.Number(DataFields.ClickTorque, 662, 6), - // DataField.Number(DataFields.ClickAngle, 670, 5), - // } - // }, - // { - // 12, new List() - // { - // DataField.Number(DataFields.SelectedIdentifierNumber, 677, 4), - // DataField.String(DataFields.JointId, 683, 25) - // } - // }, - // { - // 998, new List() - // { - // DataField.Number(DataFields.NumberOfStagesInMultistage, 526, 2), - // DataField.Number(DataFields.NumberOfStageResults, 530, 2), - // new(DataFields.StageResult, 534, 11) - // } - // }, - // { - // 999, new List() - // { - // DataField.String(DataFields.VinNumber, 20, 25, false), - // DataField.Number(DataFields.JobId, 45, 2, false), - // DataField.Number(DataFields.ParameterSetId, 47, 3, false), - // DataField.Number(DataFields.BatchSize, 50, 4, false), - // DataField.Number(DataFields.BatchCounter, 54, 4, false), - // DataField.Number(DataFields.BatchStatus, 58, 1, false), - // DataField.Number(DataFields.TighteningStatus, 59, 1, false), - // DataField.Number(DataFields.TorqueStatus, 60, 1, false), - // DataField.Number(DataFields.AngleStatus, 61, 1, false), - // DataField.Number(DataFields.Torque, 62, 6, false), - // DataField.Number(DataFields.Angle, 68, 5, false), - // DataField.Timestamp(DataFields.Timestamp, 73, false), - // DataField.Timestamp(DataFields.LastChangeInParameterSet, 92, false), - // DataField.Number(DataFields.TighteningId, 111, 10, false) - // } - // } - // }; - // } - - - /// - /// Obtain which revision we will work with for shared properties - /// (since rev 1, 2 and 999 are way too different, they are processed in different datafields) - /// - /// Datafield Revision Index - private int GetCurrentRevisionIndex() - { - if (Header.Revision == 999) - return 999; - - if (Header.Revision > 1) - return 2; - - return 1; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { CellId, From 7a497f218ca3e7da9e531fa6359f770a6cfc9421 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Thu, 9 Jul 2026 19:40:05 -0300 Subject: [PATCH 38/78] updated tightening mids to use datafields --- src/MIDTesters.Core/Tightening/TestMid0902.cs | 2 +- .../Tightening/Mid0064.cs | 43 +- .../Tightening/Mid0065.cs | 727 ++++++------------ .../Tightening/Mid0066.cs | 62 +- .../Tightening/Mid0902.cs | 88 +-- 5 files changed, 298 insertions(+), 624 deletions(-) diff --git a/src/MIDTesters.Core/Tightening/TestMid0902.cs b/src/MIDTesters.Core/Tightening/TestMid0902.cs index cc83ecb6..1f792e1a 100644 --- a/src/MIDTesters.Core/Tightening/TestMid0902.cs +++ b/src/MIDTesters.Core/Tightening/TestMid0902.cs @@ -2,7 +2,7 @@ using OpenProtocolInterpreter.Tightening; using System; -namespace MIDTesters.Core.Tightening +namespace MIDTesters.Tightening { [TestClass] [TestCategory("Tightening")] diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0064.cs b/src/OpenProtocolInterpreter/Tightening/Mid0064.cs index af2aebad..f95be229 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0064.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0064.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.Tightening { @@ -10,14 +11,14 @@ namespace OpenProtocolInterpreter.Tightening /// network in order to retrieve the missing result during the communication interruption.The integrator /// can see the missing results by always comparing the last tightening IDs of the two last received /// tightenings packets (parameter 23 in the result message). - /// + /// /// /// Requesting tightening ID zero is the same as requesting the latest tightening performed. /// /// Message sent by: Integrator /// - /// Answer: Old tightening result upload reply or - /// Command error, Tightening ID requested not found, or + /// Answer: Old tightening result upload reply or + /// Command error, Tightening ID requested not found, or /// MID revision not supported /// /// @@ -27,16 +28,11 @@ public class Mid0064 : Mid, ITightening, IIntegrator, IAnswerableBy, ID public IEnumerable DocumentedPossibleErrors => new Error[] { Error.TighteningIdRequestNotFound, Error.MidRevisionUnsupported }; - public long TighteningId - { - get => GetField(1, DataFields.TighteningId).GetValue(OpenProtocolConvert.ToInt64); - set => GetField(1, DataFields.TighteningId).SetValue(OpenProtocolConvert.ToString, value); - } - public bool OfflineResult - { - get => GetField(10, DataFields.OfflineResult).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(10, DataFields.OfflineResult).SetValue(OpenProtocolConvert.ToString, value); - } + [Int64DataFieldDefinition(field: 1, revision: 1, Size = 10, HasPrefix = false)] + public long TighteningId { get; set; } + + [BooleanDataFieldDefinition(field: 2, revision: 10, Size = 1, HasPrefix = false)] + public bool OfflineResult { get; set; } public Mid0064() : this(DEFAULT_REVISION) { @@ -56,25 +52,12 @@ public Mid0064(int revision) : this(new Header() { } - protected override Dictionary> RegisterDatafields() + override public string Pack() { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.TighteningId, 20, 10, false) - } - }, - { - 10, new List() - { - DataField.Boolean(DataFields.OfflineResult, 30, false) - } - } - }; + return base.Pack(); } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { TighteningId, diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0065.cs b/src/OpenProtocolInterpreter/Tightening/Mid0065.cs index 34fb1afc..aa54e102 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0065.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0065.cs @@ -14,311 +14,214 @@ public class Mid0065 : Mid, ITightening, IController { public const int MID = 65; - public long TighteningId - { - get => GetField(GetCurrentRevisionIndex(), DataFields.TighteningId).GetValue(OpenProtocolConvert.ToInt64); - set => GetField(GetCurrentRevisionIndex(), DataFields.TighteningId).SetValue(OpenProtocolConvert.ToString, value); - } - public string VinNumber - { - get => GetField(GetCurrentRevisionIndex(), DataFields.VinNumber).Value; - set => GetField(GetCurrentRevisionIndex(), DataFields.VinNumber).SetValue(value); - } - public int ParameterSetId - { - get => GetField(GetCurrentRevisionIndex(), DataFields.ParameterSetId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(GetCurrentRevisionIndex(), DataFields.ParameterSetId).SetValue(OpenProtocolConvert.ToString, value); - } - public int BatchCounter - { - get => GetField(GetCurrentRevisionIndex(), DataFields.BatchCounter).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(GetCurrentRevisionIndex(), DataFields.BatchCounter).SetValue(OpenProtocolConvert.ToString, value); - } - public bool TighteningStatus - { - get => GetField(GetCurrentRevisionIndex(), DataFields.TighteningStatus).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(GetCurrentRevisionIndex(), DataFields.TighteningStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public TighteningValueStatus TorqueStatus - { - get => (TighteningValueStatus)GetField(GetCurrentRevisionIndex(), DataFields.TorqueStatus).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(GetCurrentRevisionIndex(), DataFields.TorqueStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public TighteningValueStatus AngleStatus - { - get => (TighteningValueStatus)GetField(GetCurrentRevisionIndex(), DataFields.AngleStatus).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(GetCurrentRevisionIndex(), DataFields.AngleStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public decimal Torque - { - get => GetField(GetCurrentRevisionIndex(), DataFields.Torque).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(GetCurrentRevisionIndex(), DataFields.Torque).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public int Angle - { - get => GetField(GetCurrentRevisionIndex(), DataFields.Angle).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(GetCurrentRevisionIndex(), DataFields.Angle).SetValue(OpenProtocolConvert.ToString, value); - } - public DateTime Timestamp - { - get => GetField(GetCurrentRevisionIndex(), DataFields.Timestamp).GetValue(OpenProtocolConvert.ToDateTime); - set => GetField(GetCurrentRevisionIndex(), DataFields.Timestamp).SetValue(OpenProtocolConvert.ToString, value); - } - public BatchStatus BatchStatus - { - get => (BatchStatus)GetField(GetCurrentRevisionIndex(), DataFields.BatchStatus).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(GetCurrentRevisionIndex(), DataFields.BatchStatus).SetValue(OpenProtocolConvert.ToString, value); - } + [Int64DataFieldDefinition(field: 1, revision: 1, Index = 20, Size = 10)] + [Int64DataFieldDefinition(field: 1, revision: 2, Index = 20, Size = 10)] + public long TighteningId { get; set; } + + [StringDataFieldDefinition(field: 2, revision: 1, Index = 32, Size = 25)] + [StringDataFieldDefinition(field: 2, revision: 2, Index = 32, Size = 25)] + public string VinNumber { get; set; } + + [Int32DataFieldDefinition(field: 3, revision: 1, Index = 59, Size = 3)] + [Int32DataFieldDefinition(field: 4, revision: 2, Index = 65, Size = 3)] + public int ParameterSetId { get; set; } + + [Int32DataFieldDefinition(field: 4, revision: 1, Index = 64, Size = 4)] + [Int32DataFieldDefinition(field: 8, revision: 2, Index = 87, Size = 4)] + public int BatchCounter { get; set; } + + [BooleanDataFieldDefinition(field: 5, revision: 1, Index = 70, Size = 1)] + [BooleanDataFieldDefinition(field: 9, revision: 2, Index = 93, Size = 1)] + public bool TighteningStatus { get; set; } + + [Int32DataFieldDefinition(field: 6, revision: 1, Index = 73, Size = 1)] + [Int32DataFieldDefinition(field: 11, revision: 2, Index = 99, Size = 1)] + public TighteningValueStatus TorqueStatus { get; set; } + + [Int32DataFieldDefinition(field: 7, revision: 1, Index = 76, Size = 1)] + [Int32DataFieldDefinition(field: 12, revision: 2, Index = 102, Size = 1)] + public TighteningValueStatus AngleStatus { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 8, revision: 1, Index = 79, Size = 6)] + [TruncatedDecimalDataFieldDefinition(field: 19, revision: 2, Index = 132, Size = 6)] + public decimal Torque { get; set; } + + [Int32DataFieldDefinition(field: 9, revision: 1, Index = 87, Size = 5)] + [Int32DataFieldDefinition(field: 20, revision: 2, Index = 140, Size = 5)] + public int Angle { get; set; } + + [TimestampDataFieldDefinition(field: 10, revision: 1, Index = 94, Size = 19)] + [TimestampDataFieldDefinition(field: 28, revision: 2, Index = 205, Size = 19)] + public DateTime Timestamp { get; set; } + + [Int32DataFieldDefinition(field: 11, revision: 1, Index = 115, Size = 1)] + [Int32DataFieldDefinition(field: 10, revision: 2, Index = 96, Size = 1)] + public BatchStatus BatchStatus { get; set; } + //Rev 2 - public int JobId - { - get => GetField(2, DataFields.JobId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.JobId).SetValue(OpenProtocolConvert.ToString, value); - } - public Strategy Strategy - { - get => (Strategy)GetField(2, DataFields.Strategy).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.Strategy).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(field: 3, revision: 2, Index = 59, Size = 4)] + public int JobId { get; set; } + + [Int32DataFieldDefinition(field: 5, revision: 2, Index = 70, Size = 2)] + public Strategy Strategy { get; set; } + + [StrategyOptionsDefinition(field: 6, revision: 2, Index = 74, Size = 5, PaddingChar = '0', PaddingOrientation = PaddingOrientation.LeftPadded)] public StrategyOptions StrategyOptions { get; set; } - public int BatchSize - { - get => GetField(2, DataFields.BatchSize).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.BatchSize).SetValue(OpenProtocolConvert.ToString, value); - } - public TighteningValueStatus RundownAngleStatus - { - get => (TighteningValueStatus)GetField(2, DataFields.RundownAngleStatus).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.RundownAngleStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public TighteningValueStatus CurrentMonitoringStatus - { - get => (TighteningValueStatus)GetField(2, DataFields.CurrentMonitoringStatus).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.CurrentMonitoringStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public TighteningValueStatus SelftapStatus - { - get => (TighteningValueStatus)GetField(2, DataFields.SelftapStatus).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.SelftapStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public TighteningValueStatus PrevailTorqueMonitoringStatus - { - get => (TighteningValueStatus)GetField(2, DataFields.PrevailTorqueMonitoringStatus).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.PrevailTorqueMonitoringStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public TighteningValueStatus PrevailTorqueCompensateStatus - { - get => (TighteningValueStatus)GetField(2, DataFields.PrevaiTorqueMonitoringStatus).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.PrevaiTorqueMonitoringStatus).SetValue(OpenProtocolConvert.ToString, value); - } + + [Int32DataFieldDefinition(field: 7, revision: 2, Index = 81, Size = 4)] + public int BatchSize { get; set; } + + [Int32DataFieldDefinition(field: 13, revision: 2, Index = 105, Size = 1)] + public TighteningValueStatus RundownAngleStatus { get; set; } + + [Int32DataFieldDefinition(field: 14, revision: 2, Index = 108, Size = 1)] + public TighteningValueStatus CurrentMonitoringStatus { get; set; } + + [Int32DataFieldDefinition(field: 15, revision: 2, Index = 111, Size = 1)] + public TighteningValueStatus SelftapStatus { get; set; } + + [Int32DataFieldDefinition(field: 16, revision: 2, Index = 114, Size = 1)] + public TighteningValueStatus PrevailTorqueMonitoringStatus { get; set; } + + [Int32DataFieldDefinition(field: 17, revision: 2, Index = 117, Size = 1)] + public TighteningValueStatus PrevailTorqueCompensateStatus { get; set; } + + [TighteningErrorStatusDefinition(field: 18, revision: 2, Index = 120, Size = 10, PaddingChar = '0', PaddingOrientation = PaddingOrientation.LeftPadded)] public TighteningErrorStatus TighteningErrorStatus { get; set; } - public int RundownAngle - { - get => GetField(2, DataFields.RundownAngle).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.RundownAngle).SetValue(OpenProtocolConvert.ToString, value); - } - public int CurrentMonitoringValue - { - get => GetField(2, DataFields.CurrentMonitoringValue).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.CurrentMonitoringValue).SetValue(OpenProtocolConvert.ToString, value); - } - public decimal SelftapTorque - { - get => GetField(2, DataFields.SelftapTorque).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(2, DataFields.SelftapTorque).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal PrevailTorque - { - get => GetField(2, DataFields.PrevailTorque).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(2, DataFields.PrevailTorque).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public int JobSequenceNumber - { - get => GetField(2, DataFields.JobSequenceNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.JobSequenceNumber).SetValue(OpenProtocolConvert.ToString, value); - } - public int SyncTighteningId - { - get => GetField(2, DataFields.SyncTighteningId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.SyncTighteningId).SetValue(OpenProtocolConvert.ToString, value); - } - public string ToolSerialNumber - { - get => GetField(2, DataFields.ToolSerialNumber).Value; - set => GetField(2, DataFields.ToolSerialNumber).SetValue(value); - } + + [Int32DataFieldDefinition(field: 21, revision: 2, Index = 147, Size = 5)] + public int RundownAngle { get; set; } + + [Int32DataFieldDefinition(field: 22, revision: 2, Index = 154, Size = 3)] + public int CurrentMonitoringValue { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 23, revision: 2, Index = 159, Size = 6)] + public decimal SelftapTorque { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 24, revision: 2, Index = 167, Size = 6)] + public decimal PrevailTorque { get; set; } + + [Int32DataFieldDefinition(field: 25, revision: 2, Index = 175, Size = 5)] + public int JobSequenceNumber { get; set; } + + [Int32DataFieldDefinition(field: 26, revision: 2, Index = 182, Size = 5)] + public int SyncTighteningId { get; set; } + + [StringDataFieldDefinition(field: 27, revision: 2, Index = 189, Size = 14)] + public string ToolSerialNumber { get; set; } + //Rev 3 - public TorqueValuesUnit TorqueValuesUnit - { - get => (TorqueValuesUnit)GetField(3, DataFields.TorqueValuesUnit).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(3, DataFields.TorqueValuesUnit).SetValue(OpenProtocolConvert.ToString, value); - } - public ResultType ResultType - { - get => (ResultType)GetField(3, DataFields.ResultType).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(3, DataFields.ResultType).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(field: 29, revision: 3, Index = 226, Size = 1)] + public TorqueValuesUnit TorqueValuesUnit { get; set; } + + [Int32DataFieldDefinition(field: 30, revision: 3, Index = 229, Size = 2)] + public ResultType ResultType { get; set; } + + //Rev 4 - public string IdentifierResultPart2 - { - get => GetField(4, DataFields.IdentifierResulPart2).Value; - set => GetField(4, DataFields.IdentifierResulPart2).SetValue(value); - } - public string IdentifierResultPart3 - { - get => GetField(4, DataFields.IdentifierResulPart3).Value; - set => GetField(4, DataFields.IdentifierResulPart3).SetValue(value); - } - public string IdentifierResultPart4 - { - get => GetField(4, DataFields.IdentifierResulPart4).Value; - set => GetField(4, DataFields.IdentifierResulPart4).SetValue(value); - } + [StringDataFieldDefinition(field: 31, revision: 4, Index = 233, Size = 25)] + public string IdentifierResultPart2 { get; set; } + + [StringDataFieldDefinition(field: 32, revision: 4, Index = 260, Size = 25)] + public string IdentifierResultPart3 { get; set; } + + [StringDataFieldDefinition(field: 33, revision: 4, Index = 287, Size = 25)] + public string IdentifierResultPart4 { get; set; } + //Rev 5 - public string CustomerTighteningErrorCode - { - get => GetField(5, DataFields.CustomerTighteningErrorCode).Value; - set => GetField(5, DataFields.CustomerTighteningErrorCode).SetValue(value); - } + [StringDataFieldDefinition(field: 34, revision: 5, Index = 314, Size = 4)] + public string CustomerTighteningErrorCode { get; set; } + //Rev 6 - public decimal PrevailTorqueCompensateValue - { - get => GetField(6, DataFields.PrevailTorqueCompensateValue).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(6, DataFields.PrevailTorqueCompensateValue).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } + [TruncatedDecimalDataFieldDefinition(field: 35, revision: 6, Index = 320, Size = 6)] + public decimal PrevailTorqueCompensateValue { get; set; } + + [TighteningErrorStatus2Definition(field: 36, revision: 6, Index = 328, Size = 10, PaddingChar = '0', PaddingOrientation = PaddingOrientation.LeftPadded)] public TighteningErrorStatus2 TighteningErrorStatus2 { get; set; } + //Rev 7 - public long StationId - { - get => GetField(7, DataFields.StationId).GetValue(OpenProtocolConvert.ToInt64); - set => GetField(7, DataFields.StationId).SetValue(OpenProtocolConvert.ToString, value); - } - public string StationName - { - get => GetField(7, DataFields.StationName).Value; - set => GetField(7, DataFields.StationName).SetValue(value); - } + [Int64DataFieldDefinition(field: 37, revision: 7, Index = 340, Size = 10)] + public long StationId { get; set; } + + [StringDataFieldDefinition(field: 38, revision: 7, Index = 352, Size = 25)] + public string StationName { get; set; } + //Rev 8 - public decimal StartFinalAngle - { - get => GetField(8, DataFields.StartFinalAngle).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(8, DataFields.StartFinalAngle).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public PostViewTorque PostViewTorqueActivated - { - get => (PostViewTorque)GetField(8, DataFields.PostViewTorqueActivated).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(8, DataFields.PostViewTorqueActivated).SetValue(OpenProtocolConvert.ToString, value); - } - public decimal PostViewTorqueHigh - { - get => GetField(8, DataFields.PostViewTorqueHigh).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(8, DataFields.PostViewTorqueHigh).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal PostViewTorqueLow - { - get => GetField(8, DataFields.PostViewTorqueLow).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(8, DataFields.PostViewTorqueLow).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } + [TruncatedDecimalDataFieldDefinition(field: 39, revision: 8, Index = 379, Size = 6)] + public decimal StartFinalAngle { get; set; } + + [Int32DataFieldDefinition(field: 40, revision: 8, Index = 387, Size = 1)] + public PostViewTorque PostViewTorqueActivated { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 41, revision: 8, Index = 390, Size = 6)] + public decimal PostViewTorqueHigh { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 42, revision: 8, Index = 398, Size = 6)] + public decimal PostViewTorqueLow { get; set; } + //Rev 9 - public decimal CurrentMonitoringAmpere - { - get => GetField(9, DataFields.CurrentMonitoringAmp).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(9, DataFields.CurrentMonitoringAmp).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal CurrentMonitoringAmpereMin - { - get => GetField(9, DataFields.CurrentMonitoringAmpMin).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(9, DataFields.CurrentMonitoringAmpMin).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal CurrentMonitoringAmpereMax - { - get => GetField(9, DataFields.CurrentMonitoringAmpMax).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(9, DataFields.CurrentMonitoringAmpMax).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } + [TruncatedDecimalDataFieldDefinition(field: 43, revision: 9, Index = 406, Size = 5)] + public decimal CurrentMonitoringAmpere { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 44, revision: 9, Index = 413, Size = 5)] + public decimal CurrentMonitoringAmpereMin { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 45, revision: 9, Index = 420, Size = 5)] + public decimal CurrentMonitoringAmpereMax { get; set; } + //Rev 10 addition - public int AngleNumeratorScaleFactor - { - get => GetField(10, DataFields.AngleNumeratorScaleFactor).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(10, DataFields.AngleNumeratorScaleFactor).SetValue(OpenProtocolConvert.ToString, value); - } - public int AngleDenominatorScaleFactor - { - get => GetField(10, DataFields.AngleDenominatorScaleFactor).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(10, DataFields.AngleDenominatorScaleFactor).SetValue(OpenProtocolConvert.ToString, value); - } - public TighteningValueStatus OverallAngleStatus - { - get => (TighteningValueStatus)GetField(10, DataFields.OverallAngleStatus).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(10, DataFields.OverallAngleStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public int OverallAngleMin - { - get => GetField(10, DataFields.OverallAngleMin).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(10, DataFields.OverallAngleMin).SetValue(OpenProtocolConvert.ToString, value); - } - public int OverallAngleMax - { - get => GetField(10, DataFields.OverallAngleMax).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(10, DataFields.OverallAngleMax).SetValue(OpenProtocolConvert.ToString, value); - } - public int OverallAngle - { - get => GetField(10, DataFields.OverallAngle).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(10, DataFields.OverallAngle).SetValue(OpenProtocolConvert.ToString, value); - } - public decimal PeakTorque - { - get => GetField(10, DataFields.PeakTorque).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(10, DataFields.PeakTorque).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal ResidualBreakawayTorque - { - get => GetField(10, DataFields.ResidualBreakawayTorque).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(10, DataFields.ResidualBreakawayTorque).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal StartRundownAngle - { - get => GetField(10, DataFields.StartRundownAngle).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(10, DataFields.StartRundownAngle).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal RundownAngleComplete - { - get => GetField(10, DataFields.RundownAngleComplete).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(10, DataFields.RundownAngleComplete).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } + [Int32DataFieldDefinition(field: 46, revision: 10, Index = 427, Size = 5)] + public int AngleNumeratorScaleFactor { get; set; } + + [Int32DataFieldDefinition(field: 47, revision: 10, Index = 434, Size = 5)] + public int AngleDenominatorScaleFactor { get; set; } + + [Int32DataFieldDefinition(field: 48, revision: 10, Index = 441, Size = 1)] + public TighteningValueStatus OverallAngleStatus { get; set; } + + [Int32DataFieldDefinition(field: 49, revision: 10, Index = 444, Size = 5)] + public int OverallAngleMin { get; set; } + + [Int32DataFieldDefinition(field: 50, revision: 10, Index = 451, Size = 5)] + public int OverallAngleMax { get; set; } + + [Int32DataFieldDefinition(field: 51, revision: 10, Index = 458, Size = 5)] + public int OverallAngle { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 52, revision: 10, Index = 465, Size = 6)] + public decimal PeakTorque { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 53, revision: 10, Index = 473, Size = 6)] + public decimal ResidualBreakawayTorque { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 54, revision: 10, Index = 481, Size = 6)] + public decimal StartRundownAngle { get; set; } + + [TruncatedDecimalDataFieldDefinition(field: 55, revision: 10, Index = 489, Size = 6)] + public decimal RundownAngleComplete { get; set; } + //Rev 11 - public decimal ClickTorque - { - get => GetField(11, DataFields.ClickTorque).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(11, DataFields.ClickTorque).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public int ClickAngle - { - get => GetField(11, DataFields.ClickAngle).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(11, DataFields.ClickAngle).SetValue(OpenProtocolConvert.ToString, value); - } + [TruncatedDecimalDataFieldDefinition(field: 56, revision: 11, Index = 497, Size = 6)] + public decimal ClickTorque { get; set; } + + [Int32DataFieldDefinition(field: 57, revision: 11, Index = 505, Size = 5)] + public int ClickAngle { get; set; } + //Rev 12 - public int SelectedIdentifierNumber - { - get => GetField(12, DataFields.SelectedIdentifierNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(12, DataFields.SelectedIdentifierNumber).SetValue(OpenProtocolConvert.ToString, value); - } - public string JointId - { - get => GetField(12, DataFields.JointId).Value; - set => GetField(12, DataFields.JointId).SetValue(value); - } - //Rev 998 addition - public int NumberOfStagesInMultistage - { - get => GetField(998, DataFields.NumberOfStagesInMultistage).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(998, DataFields.NumberOfStagesInMultistage).SetValue(OpenProtocolConvert.ToString, value); - } - public int NumberOfStageResults - { - get => GetField(998, DataFields.NumberOfStageResults).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(998, DataFields.NumberOfStageResults).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(field: 58, revision: 12, Index = 512, Size = 4)] + public int SelectedIdentifierNumber { get; set; } + + [StringDataFieldDefinition(field: 59, revision: 12, Index = 518, Size = 25)] + public string JointId { get; set; } + + //Rev 998 addition + [Int32DataFieldDefinition(field: 37, revision: 998, Index = 340, Size = 2)] + public int NumberOfStagesInMultistage { get; set; } + + [Int32DataFieldDefinition(field: 38, revision: 998, Index = 344, Size = 2)] + public int NumberOfStageResults { get; set; } + + [StageResultCollectionDefinition(field: 39, revision: 998, Index = 348, Size = 0)] public List StageResults { get; set; } public Mid0065() : this(DEFAULT_REVISION) @@ -344,234 +247,70 @@ protected override string BuildHeader() if (RevisionsByFields.Any()) { Header.Length = 20; - Header.Revision = Header.Revision > 0 ? Header.Revision : 1; - if (Header.Revision > 1) - { - for (int i = 2; i <= Header.Revision; i++) - foreach (var dataField in RevisionsByFields[i]) - Header.Length += (dataField.HasPrefix ? 2 : 0) + dataField.Size; - } - else + if (Header.Revision == 998) { - foreach (var dataField in RevisionsByFields[1]) - Header.Length += (dataField.HasPrefix ? 2 : 0) + dataField.Size; + var stageResultField = GetField(nameof(StageResults)); + stageResultField.Size = StageResults.Count * 11; } + + var fields = DataFieldsByRevision(); + Header.Length += fields.Sum(x => x.TotalSize); } return Header.ToString(); } public override string Pack() { - var builder = new StringBuilder(BuildHeader()); + var builder = new StringBuilder(); + var fields = DataFieldsByRevision().OrderBy(f => f.Index).ToList(); int prefixIndex = 1; - if (Header.Revision > 1) - { - GetField(2, DataFields.StrategyOptions).SetValue(StrategyOptions.Pack()); - GetField(2, DataFields.TighteningErrorStatus).SetValue(TighteningErrorStatus.Pack()); - - if (Header.Revision > 5) - { - GetField(6, DataFields.TighteningErrorStatus2).SetValue(TighteningErrorStatus2.Pack()); - } - - int processUntil = Header.Revision; - for (int revision = 2; revision <= processUntil; revision++) - { - builder.Append(Pack(revision, ref prefixIndex)); - } - } - else - { - builder.Append(Pack(Header.Revision, ref prefixIndex)); - } + builder.Append(BuildHeader()); + builder.Append(Pack(fields, ref prefixIndex)); return builder.ToString(); } protected override void ProcessDataFields(ReadOnlySpan package) { - if (Header.Revision == 1) + var fields = DataFieldsByRevision().OrderBy(f => f.Index).ToList(); + ProcessDataFields(fields, package); + } + + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) + { + if (Header.StandardizedRevision == 998 && dataField.Field == 39) { - ProcessDataFields(Header.Revision, package); + dataField.Size = NumberOfStageResults * 11; } - else - { - int processUntilRevision = Header.Revision; - if (Header.Revision == 998) - { - processUntilRevision = 6; - var stageResultField = GetField(998, DataFields.StageResult); - stageResultField.Size = Header.Length - stageResultField.Index - 2; - ProcessDataFields(998, package); - StageResults = StageResult.ParseAll(stageResultField.Value).ToList(); - } - - for (int revision = 2; revision <= processUntilRevision; revision++) - ProcessDataFields(revision, package); + base.ProcessDataField(dataField, package); + } - var strategyOptionsField = GetField(2, DataFields.StrategyOptions); - StrategyOptions = StrategyOptions.Parse(strategyOptionsField.Value); - var tighteningErrorStatusField = GetField(2, DataFields.TighteningErrorStatus); - TighteningErrorStatus = TighteningErrorStatus.Parse(tighteningErrorStatusField.Value); + private IEnumerable DataFieldsByRevision() + { + var currentRevision = Header.StandardizedRevision; + var fromRevision = Header.StandardizedRevision; + var toRevision = Header.StandardizedRevision; + if (currentRevision > 1) + { + fromRevision = 2; + toRevision = Header.StandardizedRevision != 998 ? Header.StandardizedRevision : 6; + } - if (Header.Revision > 5) - { - var tighteningErrorStatus2Field = GetField(6, DataFields.TighteningErrorStatus2); - TighteningErrorStatus2 = TighteningErrorStatus2.Parse(tighteningErrorStatus2Field.Value); - } + for (int i = fromRevision; i <= toRevision; i++) + { + foreach (var dataField in RevisionsByFields[i]) + yield return dataField; } - } - protected override Dictionary> RegisterDatafields() - { - //opted to work with a different approuch (since it would need to modify too much fields) - return new Dictionary>() + if (currentRevision == 998) { - { - 1, new List() - { - DataField.Number(DataFields.TighteningId, 20, 10), - DataField.String(DataFields.VinNumber, 32, 25), - DataField.Number(DataFields.ParameterSetId, 59, 3), - DataField.Number(DataFields.BatchCounter, 64, 4), - DataField.Number(DataFields.TighteningStatus, 70, 1), - DataField.Number(DataFields.TorqueStatus, 73, 1), - DataField.Number(DataFields.AngleStatus, 76, 1), - DataField.Number(DataFields.Torque, 79, 6), - DataField.Number(DataFields.Angle, 87, 5), - DataField.Timestamp(DataFields.Timestamp, 94), - DataField.Number(DataFields.BatchStatus, 115, 1) - } - }, - { - 2, new List() - { - DataField.Number(DataFields.TighteningId, 20, 10), - DataField.String(DataFields.VinNumber, 32, 25), - DataField.Number(DataFields.JobId, 59, 4), - DataField.Number(DataFields.ParameterSetId, 65, 3), - DataField.Number(DataFields.Strategy, 70, 2), - new(DataFields.StrategyOptions, 74, 5, '0', PaddingOrientation.LeftPadded), - DataField.Number(DataFields.BatchSize, 81, 4), - DataField.Number(DataFields.BatchCounter, 87, 4), - DataField.Number(DataFields.TighteningStatus, 93, 1), - DataField.Number(DataFields.BatchStatus, 96, 1), - DataField.Number(DataFields.TorqueStatus, 99, 1), - DataField.Number(DataFields.AngleStatus, 102, 1), - DataField.Number(DataFields.RundownAngleStatus, 105, 1), - DataField.Number(DataFields.CurrentMonitoringStatus, 108, 1), - DataField.Number(DataFields.SelftapStatus, 111, 1), - DataField.Number(DataFields.PrevailTorqueMonitoringStatus, 114, 1), - DataField.Number(DataFields.PrevaiTorqueMonitoringStatus, 117, 1), - new(DataFields.TighteningErrorStatus, 120, 10, '0', PaddingOrientation.LeftPadded), - DataField.Number(DataFields.Torque, 132, 6), - DataField.Number(DataFields.Angle, 140, 5), - DataField.Number(DataFields.RundownAngle, 147, 5), - DataField.Number(DataFields.CurrentMonitoringValue, 154, 3), - DataField.Number(DataFields.SelftapTorque, 159, 6), - DataField.Number(DataFields.PrevailTorque, 167, 6), - DataField.Number(DataFields.JobSequenceNumber, 175, 5), - DataField.Number(DataFields.SyncTighteningId, 182, 5), - DataField.String(DataFields.ToolSerialNumber, 189, 14), - DataField.Timestamp(DataFields.Timestamp, 205), - } - }, - { - 3, new List() - { - DataField.Number(DataFields.TorqueValuesUnit, 226, 1), - DataField.Number(DataFields.ResultType, 229, 2) - } - }, - { - 4, new List() - { - DataField.String(DataFields.IdentifierResulPart2, 233, 25), - DataField.String(DataFields.IdentifierResulPart3, 260, 25), - DataField.String(DataFields.IdentifierResulPart4, 287, 25) - } - }, - { - 5, new List() - { - DataField.String(DataFields.CustomerTighteningErrorCode, 314, 4), - } - }, - { - 6, new List() - { - DataField.Number(DataFields.PrevailTorqueCompensateValue, 320, 6), - new(DataFields.TighteningErrorStatus2, 328, 10, '0', PaddingOrientation.LeftPadded) - } - }, - { - 7, new List() - { - DataField.Number(DataFields.StationId, 340, 10), - DataField.String(DataFields.StationName, 352, 25) - } - }, - { - 8, new List() - { - DataField.Number(DataFields.StartFinalAngle, 379, 6), - DataField.Number(DataFields.PostViewTorqueActivated, 387, 1), - DataField.Number(DataFields.PostViewTorqueHigh, 390, 6), - DataField.Number(DataFields.PostViewTorqueLow, 398, 6), - } - }, - { - 9, new List() - { - DataField.Number(DataFields.CurrentMonitoringAmp, 406, 5), - DataField.Number(DataFields.CurrentMonitoringAmpMin, 413, 5), - DataField.Number(DataFields.CurrentMonitoringAmpMax, 420, 5) - } - }, - { - 10, new List() - { - DataField.Number(DataFields.AngleNumeratorScaleFactor, 427, 5), - DataField.Number(DataFields.AngleDenominatorScaleFactor, 434, 5), - DataField.Number(DataFields.OverallAngleStatus, 441, 1), - DataField.Number(DataFields.OverallAngleMin, 444, 5), - DataField.Number(DataFields.OverallAngleMax, 451, 5), - DataField.Number(DataFields.OverallAngle, 458, 5), - DataField.Number(DataFields.PeakTorque, 465, 6), - DataField.Number(DataFields.ResidualBreakawayTorque, 473, 6), - DataField.Number(DataFields.StartRundownAngle, 481, 6), - DataField.Number(DataFields.RundownAngleComplete, 489, 6) - } - }, - { - 11, new List() - { - DataField.Number(DataFields.ClickTorque, 497, 6), - DataField.Number(DataFields.ClickAngle, 505, 5), - } - }, - { - 12, new List() - { - DataField.Number(DataFields.SelectedIdentifierNumber, 512, 4), - DataField.String(DataFields.JointId, 518, 25) - } - }, - { - //Revision 998 is a continuation of Revision 6. - 998, new List() - { - DataField.Number(DataFields.NumberOfStagesInMultistage, 340, 2), - DataField.Number(DataFields.NumberOfStageResults, 344, 2), - new(DataFields.StageResult, 348, 11) - } - } - }; + foreach (var dataField in RevisionsByFields[998]) + yield return dataField; + } } - private int GetCurrentRevisionIndex() => (Header.Revision > 1) ? 2 : 1; - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { TighteningId, diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0066.cs b/src/OpenProtocolInterpreter/Tightening/Mid0066.cs index 8e8c5997..011197ad 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0066.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0066.cs @@ -1,5 +1,7 @@ using System; -using System.Collections.Generic; +using System.Collections.Generic; +using System.Linq; +using System.Text; namespace OpenProtocolInterpreter.Tightening { @@ -13,17 +15,13 @@ public class Mid0066 : Mid, ITightening, IController { public const int MID = 66; - public int NumberOfOfflineResults - { - get => GetField(1, DataFields.NumberOfOfflineResults).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.NumberOfOfflineResults).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(field: 1, revision: 1, Index = 20, Size = 2)] + [Int32DataFieldDefinition(field: 1, revision: 2, Index = 20, Size = 3)] + public int NumberOfOfflineResults { get; set; } + + [Int32DataFieldDefinition(field: 2, revision: 2, Index = 25, Size = 3)] + public int NumberOfOfflineCurves { get; set; } - public int NumberOfOfflineCurves - { - get => GetField(2, DataFields.NumberOfOfflineCurves).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.NumberOfOfflineCurves).SetValue(OpenProtocolConvert.ToString, value); - } public Mid0066() : this(new Header() { @@ -37,44 +35,32 @@ public Mid0066(Header header) : base(header) { } + protected override string BuildHeader() + { + var fields = RevisionsByFields[Header.StandardizedRevision]; + Header.Length = Header.DefaultSize + fields.Sum(dataField => dataField.TotalSize); + return Header.ToString(); + } + public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); - HandleRevisionSizes(); - ProcessDataFields(package); + var fields = RevisionsByFields[Header.StandardizedRevision]; + ProcessDataFields(fields, package); return this; } public override string Pack() { - HandleRevisionSizes(); - return base.Pack(); - } - - private void HandleRevisionSizes() - { - GetField(1, DataFields.NumberOfOfflineResults).Size = Header.Revision > 1 ? 3 : 2; - } + var fields = RevisionsByFields[Header.StandardizedRevision]; + var builder = new StringBuilder(BuildHeader()); - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.NumberOfOfflineResults, 20, 2) - } - }, - { - 2, new List() - { - DataField.Number(DataFields.NumberOfOfflineCurves, 25, 3), - } - } - }; + int prefixIndex = 1; + builder.Append(Pack(fields, ref prefixIndex)); + return builder.ToString(); } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { NumberOfOfflineResults, diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0902.cs b/src/OpenProtocolInterpreter/Tightening/Mid0902.cs index 34a766c7..2f185633 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0902.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0902.cs @@ -15,36 +15,25 @@ public class Mid0902 : Mid, ITightening, IController { public const int MID = 902; - public long Capacity - { - get => GetField(1, DataFields.Capacity).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.Capacity).SetValue(OpenProtocolConvert.ToString, value); - } - public long OldestSequenceNumber - { - get => GetField(1, DataFields.OldestSequenceNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.OldestSequenceNumber).SetValue(OpenProtocolConvert.ToString, value); - } - public DateTime OldestTime - { - get => GetField(1, DataFields.OldestTime).GetValue(OpenProtocolConvert.ToDateTime); - set => GetField(1, DataFields.OldestTime).SetValue(OpenProtocolConvert.ToString, value); - } - public long NewestSequenceNumber - { - get => GetField(1, DataFields.NewestSequenceNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.NewestTime).SetValue(OpenProtocolConvert.ToString, value); - } - public DateTime NewestTime - { - get => GetField(1, DataFields.NewestTime).GetValue(OpenProtocolConvert.ToDateTime); - set => GetField(1, DataFields.NewestTime).SetValue(OpenProtocolConvert.ToString, value); - } - public int NumberOfPIDs - { - get => GetField(1, DataFields.NumberOfPIDs).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.NumberOfPIDs).SetValue(OpenProtocolConvert.ToString, value); - } + [Int64DataFieldDefinition(field: 1, revision: 1, Size = 10, HasPrefix = false)] + public long Capacity { get; set; } + + [Int64DataFieldDefinition(field: 2, revision: 1, Size = 10, HasPrefix = false)] + public long OldestSequenceNumber { get; set; } + + [TimestampDataFieldDefinition(field: 3, revision: 1, HasPrefix = false)] + public DateTime OldestTime { get; set; } + + [Int64DataFieldDefinition(field: 4, revision: 1, Size = 10, HasPrefix = false)] + public long NewestSequenceNumber { get; set; } + + [TimestampDataFieldDefinition(field: 5, revision: 1, HasPrefix = false)] + public DateTime NewestTime { get; set; } + + [Int32DataFieldDefinition(field: 6, revision: 1, Size = 3, HasPrefix = false)] + public int NumberOfPIDs { get; set; } + + [VariableDataFieldCollectionDefinition(field: 7, revision: 1, HasPrefix = false)] public List VariableDataFields { get; set; } public Mid0902() : this(new Header() @@ -62,43 +51,20 @@ public Mid0902(Header header) : base(header) public override string Pack() { - var revision = Header.StandardizedRevision; - GetField(revision, DataFields.VariableDataFields).SetValue(OpenProtocolConvert.ToString(VariableDataFields)); - - var index = 1; - return string.Concat(BuildHeader(), base.Pack(revision, ref index)); - } - - public override Mid Parse(ReadOnlySpan package) - { - Header = ProcessHeader(package); - - var field = GetField(1, DataFields.VariableDataFields); - field.Size = Header.Length - field.Index; - base.Parse(package); - VariableDataFields = VariableDataField.ParseAll(field.Value).ToList(); - return this; + NumberOfPIDs = VariableDataFields?.Count ?? 0; //Enforce list size even if modified + return base.Pack(); } - protected override Dictionary> RegisterDatafields() + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - return new Dictionary>() + if (dataField.Field == 7) //VariableDataFields { - { - 1, new List() - { - DataField.Number(DataFields.Capacity, 20, 10, false), - DataField.Number(DataFields.OldestSequenceNumber, 30, 10, false), - DataField.Timestamp(DataFields.OldestTime, 40, false), - DataField.Number(DataFields.NewestSequenceNumber, 59, 10, false), - DataField.Timestamp(DataFields.NewestTime, 69, false), - DataField.Number(DataFields.NumberOfPIDs, 88, 3, false), - DataField.Volatile(DataFields.VariableDataFields, 91, false) - } - } - }; + dataField.Size = Header.Length - dataField.Index; + } + base.ProcessDataField(dataField, package); } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { Capacity, From d549ea50bb45f7351e645716e4818dcc99b84dcb Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Fri, 10 Jul 2026 01:25:59 -0300 Subject: [PATCH 39/78] implemented datafield attribute at job mids --- .../Communication/Mid0004.cs | 39 ++- .../DataFieldDefinitionAttribute.cs | 54 ++++- .../IOInterface/Mid0215.cs | 57 +++-- src/OpenProtocolInterpreter/Job/Mid0031.cs | 99 +++----- src/OpenProtocolInterpreter/Job/Mid0032.cs | 67 +++--- src/OpenProtocolInterpreter/Job/Mid0033.cs | 226 ++++++++---------- src/OpenProtocolInterpreter/Job/Mid0035.cs | 222 ++++++----------- src/OpenProtocolInterpreter/Job/Mid0038.cs | 64 ++--- src/OpenProtocolInterpreter/Job/Mid0039.cs | 61 ++--- .../Job/ParameterSet.cs | 39 ++- src/OpenProtocolInterpreter/MID.cs | 2 +- .../Tightening/Mid0065.cs | 4 +- 12 files changed, 456 insertions(+), 478 deletions(-) diff --git a/src/OpenProtocolInterpreter/Communication/Mid0004.cs b/src/OpenProtocolInterpreter/Communication/Mid0004.cs index cfcda05b..119da5cb 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0004.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0004.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Text; namespace OpenProtocolInterpreter.Communication { @@ -23,10 +25,12 @@ public class Mid0004 : Mid, ICommunication, IController { public const int MID = 4; - [Int32DataFieldDefinition(field: 1, revision: 1, Size = 4, HasPrefix = false)] + [Int32DataFieldDefinition(field: 1, revision: 1, Index = 20, Size = 4, HasPrefix = false)] + [Int32DataFieldDefinition(field: 1, revision: 2, Index = 20, Size = 4, HasPrefix = false)] public int FailedMid { get; set; } - [Int32DataFieldDefinition(field: 2, revision: 1, Size = 2, HasPrefix = false)] + [Int32DataFieldDefinition(field: 2, revision: 1, Index = 24, Size = 2, HasPrefix = false)] + [Int32DataFieldDefinition(field: 2, revision: 2, Index = 24, Size = 3, HasPrefix = false)] public Error ErrorCode { get; set; } public Mid0004() : this(DEFAULT_REVISION) @@ -47,20 +51,39 @@ public Mid0004(int revision) : this(new Header() } + protected override string BuildHeader() + { + if (RevisionsByFields.TryGetValue(Header.StandardizedRevision, out var dataFields)) + { + Header.Length = Header.DefaultSize + dataFields.Sum(x => x.TotalSize); + } + + return Header.ToString(); + } + public override string Pack() { - HandleRevision(); - return base.Pack(); + if (!RevisionsByFields.TryGetValue(Header.StandardizedRevision, out var dataFields)) + { + throw new InvalidOperationException($"No data fields defined for revision {Header.StandardizedRevision}"); + } + + var builder = new StringBuilder(BuildHeader()); + int prefixIndex = 1; + builder.Append(base.Pack(dataFields, ref prefixIndex)); + return builder.ToString(); } protected override void ProcessDataFields(ReadOnlySpan package) { - HandleRevision(); - base.ProcessDataFields(package); + if (!RevisionsByFields.TryGetValue(Header.StandardizedRevision, out var dataFields)) + { + throw new InvalidOperationException($"No data fields defined for revision {Header.StandardizedRevision}"); + } + + base.ProcessDataFields(dataFields, package); } - private void HandleRevision() - => GetField(nameof(ErrorCode)).Size = Header.Revision > 1 ? 3 : 2; [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields diff --git a/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs b/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs index b092bef0..227d1399 100644 --- a/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs +++ b/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs @@ -194,6 +194,54 @@ internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) } } + public class Int32CollectionDefinitionAttribute : DataFieldDefinitionAttribute + { + public int EachFieldSize { get; set; } + + public Int32CollectionDefinitionAttribute(int revision) : base(revision) + { + PaddingChar = '0'; + PaddingOrientation = PaddingOrientation.LeftPadded; + } + + public Int32CollectionDefinitionAttribute(int field, int revision) : base(field, revision) + { + PaddingChar = '0'; + PaddingOrientation = PaddingOrientation.LeftPadded; + } + + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + { + return new DataField>(Field, index, Size, PaddingChar, PaddingOrientation, HasPrefix) + { + DefaultConverter = PackList, + DefaultParser = ParseList + }.Bind(mid, propertyInfo); + } + + private string PackList(char paddingChar, int size, PaddingOrientation orientation, List list) + { + var builder = new StringBuilder(list.Count); + foreach (var e in list) + builder.Append(OpenProtocolConvert.ToString(paddingChar, EachFieldSize, orientation, e)); + + return builder.ToString(); + } + + private List ParseList(string value) + { + var span = value.AsSpan(); + var list = new List(); + for (int i = 0; i < span.Length; i += EachFieldSize) + { + var slice = span.Slice(i, EachFieldSize); + list.Add(OpenProtocolConvert.ToInt32(slice.ToString())); + } + + return list; + } + } + public class EnumCollectionDefinitionAttribute : DataFieldDefinitionAttribute where T : Enum { public EnumCollectionDefinitionAttribute(int revision) : base(revision) @@ -214,10 +262,10 @@ internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) }.Bind(mid, propertyInfo); } - private static string PackEnums(char paddingChar, int size, PaddingOrientation orientation, List greenLights) + private static string PackEnums(char paddingChar, int size, PaddingOrientation orientation, List list) { - var builder = new StringBuilder(greenLights.Count); - foreach (var e in greenLights) + var builder = new StringBuilder(list.Count); + foreach (var e in list) builder.Append(OpenProtocolConvert.ToString((int)(object)e)); return builder.ToString(); diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs index 75512a46..801e5323 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs @@ -25,13 +25,16 @@ public class Mid0215 : Mid, IIOInterface, IController { public const int MID = 215; - [Int32DataFieldDefinition(field: 1, revision: 1, Size = 2)] + [Int32DataFieldDefinition(field: 1, revision: 1, Index = 20, Size = 2)] + [Int32DataFieldDefinition(field: 1, revision: 2, Index = 20, Size = 2)] public int IODeviceId { get; set; } - [RelayCollectionDefinition(field: 2, revision: 1, Size = 4 * 8)] + [RelayCollectionDefinition(field: 2, revision: 1, Index = 24, Size = 4 * 8)] + [RelayCollectionDefinition(field: 2, revision: 2, Index = 28, Size = 4 * 8)] public List Relays { get; set; } - [DigitalInputCollectionDefinition(field: 3, revision: 1, Size = 4 * 8)] + [DigitalInputCollectionDefinition(field: 3, revision: 1, Index = 58, Size = 4 * 8)] + [DigitalInputCollectionDefinition(field: 3, revision: 2, Index = 0, Size = 4 * 8)] public List DigitalInputs { get; set; } //At revision 2 number of relays/digital inputs comes before their lists @@ -61,16 +64,32 @@ public Mid0215(int revision) : this(new Header() } + protected override string BuildHeader() + { + if (RevisionsByFields.TryGetValue(Header.StandardizedRevision, out var dataFields)) + { + Header.Length = Header.DefaultSize + dataFields.Sum(x => x.TotalSize); + } + + return Header.ToString(); + } + public override string Pack() { - HandleRevision(); if (Header.Revision > 1) { NumberOfRelays = Relays.Count; NumberOfDigitalInputs = DigitalInputs.Count; - GetField(nameof(Relays)).Size = NumberOfRelays * 4; - GetField(nameof(DigitalInputs)).Size = NumberOfDigitalInputs * 4; + var relaysField = GetField(nameof(Relays)); + relaysField.Size = NumberOfRelays * 4; + + var numberOfDigitalInputsField = GetField(nameof(NumberOfDigitalInputs)); + numberOfDigitalInputsField.Index = relaysField.Index + relaysField.TotalSize; + + var digitalInputsField = GetField(nameof(DigitalInputs)); + digitalInputsField.Index = numberOfDigitalInputsField.Index + numberOfDigitalInputsField.TotalSize; + digitalInputsField.Size = NumberOfDigitalInputs * 4; var builder = new StringBuilder(BuildHeader()); int prefixIndex = 1; @@ -79,13 +98,19 @@ public override string Pack() builder.Append(base.Pack(fields, ref prefixIndex)); return builder.ToString(); } + else + EnsureEightRelaysAndDigitalInputs(); return base.Pack(); } protected override void ProcessDataFields(ReadOnlySpan package) { - HandleRevision(); + if (Header.StandardizedRevision == 1) + { + EnsureEightRelaysAndDigitalInputs(); + } + var fields = OrderedDataFieldsByRevision(); var enumerator = fields.GetEnumerator(); @@ -118,18 +143,9 @@ protected override void ProcessDataFields(ReadOnlySpan package) } } - private void HandleRevision() - { - if (Header.StandardizedRevision == 1) - { - EnsureEightRelaysAndDigitalInputs(); - } - } - private IEnumerable OrderedDataFieldsByRevision() { - var revision = Header.StandardizedRevision; - if (revision == 1) + if (Header.StandardizedRevision == 1) { yield return GetField(nameof(IODeviceId)); yield return GetField(nameof(Relays)); @@ -147,11 +163,14 @@ private IEnumerable OrderedDataFieldsByRevision() private void EnsureEightRelaysAndDigitalInputs() { - GetField(revision: 1, field: 2).Size = 4 * 8; + if (Relays.Count > 8 || DigitalInputs.Count > 8) + throw new InvalidOperationException("Revision 1 of MID 0215 can only have up to 8 relays and digital inputs."); + + GetField(nameof(Relays)).Size = 4 * 8; for (int i = Relays.Count; i < 8; i++) Relays.Add(new Relay(RelayNumber.Off, false)); - GetField(revision: 1, field: 3).Size = 4 * 8; + GetField(nameof(DigitalInputs)).Size = 4 * 8; for (int i = DigitalInputs.Count; i < 8; i++) DigitalInputs.Add(new DigitalInput(DigitalInputNumber.Off, false)); } diff --git a/src/OpenProtocolInterpreter/Job/Mid0031.cs b/src/OpenProtocolInterpreter/Job/Mid0031.cs index 3d0dad1a..b7382a59 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0031.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0031.cs @@ -1,5 +1,6 @@ using System; -using System.Collections.Generic; +using System.Collections.Generic; +using System.Linq; using System.Text; namespace OpenProtocolInterpreter.Job @@ -7,24 +8,22 @@ namespace OpenProtocolInterpreter.Job /// /// Job ID upload reply /// - /// The transmission of all the valid Job IDs of the controller. + /// The transmission of all the valid Job IDs of the controller. /// The data field contains the number of valid Jobs currently present in the controller, and the ID of each Job. - /// + /// /// Message sent by: Controller /// Answer: None /// public class Mid0031 : Mid, IJob, IController { - private int JobSize => Header.Revision > 1 ? 4 : 2; - public const int MID = 31; - public int TotalJobs - { - get => GetField(1, DataFields.NumberOfJobs).GetValue(OpenProtocolConvert.ToInt32); - private set => GetField(1, DataFields.NumberOfJobs).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(field: 1, revision: 1, Index = 20, Size = 2, HasPrefix = false)] + [Int32DataFieldDefinition(field: 1, revision: 2, Index = 20, Size = 4, HasPrefix = false)] + public int TotalJobs { get; set; } + [Int32CollectionDefinition(field: 2, revision: 1, Index = 22, Size = 2, EachFieldSize = 2, HasPrefix = false)] + [Int32CollectionDefinition(field: 2, revision: 2, Index = 24, Size = 4, EachFieldSize = 4, HasPrefix = false)] public List JobIds { get; set; } public Mid0031() : this(DEFAULT_REVISION) @@ -35,7 +34,6 @@ public Mid0031() : this(DEFAULT_REVISION) public Mid0031(Header header) : base(header) { JobIds ??= []; - HandleRevisions(); } public Mid0031(int revision) : this(new Header() @@ -44,84 +42,47 @@ public Mid0031(int revision) : this(new Header() Revision = revision }) { - + } - public override string Pack() + protected override string BuildHeader() { - TotalJobs = JobIds.Count; - - var eachJobField = GetField(1, DataFields.EachJobId); - eachJobField.Size = JobSize * TotalJobs; - eachJobField.Value = PackJobIdList(); - return base.Pack(); + var fields = RevisionsByFields[Header.StandardizedRevision]; + Header.Length = Header.DefaultSize + fields.Sum(dataField => dataField.TotalSize); + return Header.ToString(); } public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); - HandleRevisions(); - - var eachJobField = GetField(1, DataFields.EachJobId); - eachJobField.Size = Header.Length - eachJobField.Index; - base.Parse(package); - JobIds = ParseJobIdList(eachJobField.Value); + var fields = RevisionsByFields[Header.StandardizedRevision]; + ProcessDataFields(fields, package); return this; } - - protected virtual string PackJobIdList() - { - var builder = new StringBuilder(); - foreach (var jobId in JobIds) - builder.Append(OpenProtocolConvert.ToString('0', JobSize, PaddingOrientation.LeftPadded, jobId)); - - return builder.ToString(); - } - - protected virtual List ParseJobIdList(string section) + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - var list = new List(); - if (string.IsNullOrWhiteSpace(section)) + if (dataField.Field == 2) //JobIds { - return list; + var jobSize = Header.StandardizedRevision == 1 ? 2 : 4; + dataField.Size = TotalJobs * jobSize; } - - for (int i = 0; i < section.Length; i += JobSize) - { - list.Add(OpenProtocolConvert.ToInt32(section.Substring(i, JobSize))); - } - - return list; + base.ProcessDataField(dataField, package); } - protected override Dictionary> RegisterDatafields() + public override string Pack() { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.NumberOfJobs, 20, 2, false), - DataField.Number(DataFields.EachJobId, 22, 2, false) - } - }, - }; - } + var jobSize = Header.StandardizedRevision == 1 ? 2 : 4; + GetField(nameof(JobIds)).Size = JobIds.Count * jobSize; + var fields = RevisionsByFields[Header.StandardizedRevision]; + var builder = new StringBuilder(BuildHeader()); - private void HandleRevisions() - { - if (Header.Revision > 1) - { - GetField(1, DataFields.NumberOfJobs).Size = 4; - GetField(1, DataFields.EachJobId).Index = 24; - } - else - { - GetField(1, DataFields.NumberOfJobs).Size = GetField(1, DataFields.EachJobId).Size = 2; - } + int prefixIndex = 1; + builder.Append(Pack(fields, ref prefixIndex)); + return builder.ToString(); } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { NumberOfJobs, diff --git a/src/OpenProtocolInterpreter/Job/Mid0032.cs b/src/OpenProtocolInterpreter/Job/Mid0032.cs index 97c732ab..0ce509bf 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0032.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0032.cs @@ -1,5 +1,6 @@ using System; -using System.Collections.Generic; +using System.Collections.Generic; +using System.Text; namespace OpenProtocolInterpreter.Job { @@ -15,11 +16,9 @@ public class Mid0032 : Mid, IJob, IIntegrator, IAnswerableBy, IDeclinab public IEnumerable DocumentedPossibleErrors => new Error[] { Error.JobIdNotPresent }; - public int JobId - { - get => GetField(1, DataFields.JobId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.JobId).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(field: 1, revision: 1, Index = 20, Size = 2, HasPrefix = false)] + [Int32DataFieldDefinition(field: 1, revision: 2, Index = 20, Size = 4, HasPrefix = false)] + public int JobId { get; set; } public Mid0032() : this(DEFAULT_REVISION) { @@ -28,7 +27,7 @@ public Mid0032() : this(DEFAULT_REVISION) public Mid0032(Header header) : base(header) { - HandleRevision(); + } public Mid0032(int revision) : this(new Header() @@ -39,39 +38,51 @@ public Mid0032(int revision) : this(new Header() { } - public override Mid Parse(ReadOnlySpan package) + protected override string BuildHeader() { - Header = ProcessHeader(package); - HandleRevision(); - ProcessDataFields(package); - return this; - } + Header.Length = Header.DefaultSize; + var fromRevision = Header.StandardizedRevision > 1 ? 2 : 1; + for (int i = fromRevision; i <= Header.StandardizedRevision; i++) + { + if (RevisionsByFields.TryGetValue(i, out var dataFields)) + { + foreach (var dataField in dataFields) + Header.Length += dataField.TotalSize; + } + } + return Header.ToString(); + } - protected override Dictionary> RegisterDatafields() + public override string Pack() { - return new Dictionary>() + var builder = new StringBuilder(BuildHeader()); + + var fromRevision = Header.StandardizedRevision > 1 ? 2 : 1; + int prefixIndex = 1; + for (int i = fromRevision; i <= Header.StandardizedRevision; i++) { - { - 1, new List() - { - DataField.Number(DataFields.JobId, 20, 2, false), - } - } - }; + builder.Append(Pack(i, ref prefixIndex)); + } + return builder.ToString(); } - private void HandleRevision() + public override Mid Parse(ReadOnlySpan package) { - if (Header.Revision == 1) - GetField(1, DataFields.JobId).Size = 2; - else - GetField(1, DataFields.JobId).Size = 4; + Header = ProcessHeader(package); + var fromRevision = Header.StandardizedRevision > 1 ? 2 : 1; + for (int i = fromRevision; i <= Header.StandardizedRevision; i++) + { + if (RevisionsByFields.TryGetValue(i, out var fields)) + ProcessDataFields(fields, package); + } + return this; } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { JobId } } -} \ No newline at end of file +} diff --git a/src/OpenProtocolInterpreter/Job/Mid0033.cs b/src/OpenProtocolInterpreter/Job/Mid0033.cs index 12872d41..7f2a38b4 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0033.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0033.cs @@ -1,6 +1,7 @@ using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; +using System.Text; namespace OpenProtocolInterpreter.Job { @@ -14,66 +15,59 @@ public class Mid0033 : Mid, IJob, IController { public const int MID = 33; - public int JobId - { - get => GetField(1, DataFields.JobId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.JobId).SetValue(OpenProtocolConvert.ToString, value); - } - public string JobName - { - get => GetField(1, DataFields.JobName).Value; - set => GetField(1, DataFields.JobName).SetValue(value); - } - public ForcedOrder ForcedOrder - { - get => (ForcedOrder)GetField(1, DataFields.ForcedOrder).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.ForcedOrder).SetValue(OpenProtocolConvert.ToString, value); - } - public int MaxTimeForFirstTightening - { - get => GetField(1, DataFields.MaxTimeForFirstTightening).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.MaxTimeForFirstTightening).SetValue(OpenProtocolConvert.ToString, value); - } - public int MaxTimeToCompleteJob - { - get => GetField(1, DataFields.MaxTimeToCompleteJob).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.MaxTimeToCompleteJob).SetValue(OpenProtocolConvert.ToString, value); - } - public JobBatchMode JobBatchMode - { - get => (JobBatchMode)GetField(1, DataFields.JobBatchDone).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.JobBatchDone).SetValue(OpenProtocolConvert.ToString, value); - } - public bool LockAtJobDone - { - get => GetField(1, DataFields.LockAtJobDone).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.LockAtJobDone).SetValue(OpenProtocolConvert.ToString, value); - } - public bool UseLineControl - { - get => GetField(1, DataFields.UseLineControl).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.UseLineControl).SetValue(OpenProtocolConvert.ToString, value); - } - public bool RepeatJob - { - get => GetField(1, DataFields.RepeatJob).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.RepeatJob).SetValue(OpenProtocolConvert.ToString, value); - } - public ToolLoosening ToolLoosening - { - get => (ToolLoosening)GetField(1, DataFields.ToolLoosening).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.ToolLoosening).SetValue(OpenProtocolConvert.ToString, value); - } - public Reserved Reserved - { - get => (Reserved)GetField(1, DataFields.Reserved).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.Reserved).SetValue(OpenProtocolConvert.ToString, value); - } - public int NumberOfParameterSets - { - get => GetField(1, DataFields.NumberOfParameterSets).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.NumberOfParameterSets).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(field: 1, revision: 1, Index = 20, Size = 2)] + [Int32DataFieldDefinition(field: 1, revision: 2, Index = 20, Size = 4)] + public int JobId { get; set; } + + [StringDataFieldDefinition(field: 2, revision: 1, Index = 24, Size = 25)] + [StringDataFieldDefinition(field: 2, revision: 2, Index = 26, Size = 25)] + public string JobName { get; set; } + + [Int32DataFieldDefinition(field: 3, revision: 1, Index = 51, Size = 1)] + [Int32DataFieldDefinition(field: 3, revision: 2, Index = 53, Size = 1)] + public ForcedOrder ForcedOrder { get; set; } + + [Int32DataFieldDefinition(field: 4, revision: 1, Index = 54, Size = 4)] + [Int32DataFieldDefinition(field: 4, revision: 2, Index = 56, Size = 4)] + public int MaxTimeForFirstTightening { get; set; } + + [Int32DataFieldDefinition(field: 5, revision: 1, Index = 60, Size = 5)] + [Int32DataFieldDefinition(field: 5, revision: 2, Index = 62, Size = 5)] + public int MaxTimeToCompleteJob { get; set; } + + [Int32DataFieldDefinition(field: 6, revision: 1, Index = 67, Size = 1)] + [Int32DataFieldDefinition(field: 6, revision: 2, Index = 69, Size = 1)] + public JobBatchMode JobBatchMode { get; set; } + + [BooleanDataFieldDefinition(field: 7, revision: 1, Index = 70, Size = 1)] + [BooleanDataFieldDefinition(field: 7, revision: 2, Index = 72, Size = 1)] + public bool LockAtJobDone { get; set; } + + [BooleanDataFieldDefinition(field: 8, revision: 1, Index = 73)] + [BooleanDataFieldDefinition(field: 8, revision: 2, Index = 75)] + public bool UseLineControl { get; set; } + + [BooleanDataFieldDefinition(field: 9, revision: 1, Index = 76)] + [BooleanDataFieldDefinition(field: 9, revision: 2, Index = 78)] + public bool RepeatJob { get; set; } + + [Int32DataFieldDefinition(field: 10, revision: 1, Index = 79, Size = 1)] + [Int32DataFieldDefinition(field: 10, revision: 2, Index = 81, Size = 1)] + public ToolLoosening ToolLoosening { get; set; } + + [Int32DataFieldDefinition(field: 11, revision: 1, Index = 82, Size = 1)] + [Int32DataFieldDefinition(field: 11, revision: 2, Index = 84, Size = 1)] + public Reserved Reserved { get; set; } + + [Int32DataFieldDefinition(field: 12, revision: 1, Index = 85, Size = 2)] + [Int32DataFieldDefinition(field: 12, revision: 2, Index = 87, Size = 2)] + public int NumberOfParameterSets { get; set; } + + [ParameterSetCollectionDefinition(field: 13, revision: 1, Index = 89, Size = 0)] + [ParameterSetCollectionDefinition(field: 13, revision: 2, Index = 91, Size = 0)] + [ParameterSetCollectionDefinition(field: 13, revision: 3, Index = 91, Size = 0)] + [ParameterSetCollectionDefinition(field: 13, revision: 4, Index = 91, Size = 0)] + [ParameterSetCollectionDefinition(field: 13, revision: 5, Index = 91, Size = 0)] public List ParameterSetList { get; set; } public Mid0033() : this(DEFAULT_REVISION) @@ -94,86 +88,74 @@ public Mid0033(int revision) : this(new Header() { } - public override string Pack() + protected override string BuildHeader() { - HandleRevisions(); - NumberOfParameterSets = ParameterSetList.Count; + Header.Length = Header.DefaultSize; + var fromRevision = Header.StandardizedRevision > 1 ? 2 : 1; + for (int i = fromRevision; i <= Header.StandardizedRevision; i++) + { + if (RevisionsByFields.TryGetValue(i, out var dataFields)) + { + foreach (var dataField in dataFields) + { + if (dataField.Field == 13) //ParameterSetList + continue; + Header.Length += dataField.TotalSize; + } + } + } - var psetListField = GetField(1, DataFields.ParameterSetList); - psetListField.Size = ParameterSetList.Count * ParameterSet.Size(Header.Revision); - psetListField.Value = PackParameterSetList(); - return base.Pack(); + Header.Length += GetField(nameof(ParameterSetList)).TotalSize; + return Header.ToString(); } - public override Mid Parse(ReadOnlySpan package) + public override string Pack() { - Header = ProcessHeader(package); - HandleRevisions(); - var jobListField = GetField(1, DataFields.ParameterSetList); - jobListField.Size = Header.Length - jobListField.Index - 2; - base.Parse(package); - ParameterSetList = ParameterSet.ParseAll(jobListField.Value, Header.Revision).ToList(); - return this; + NumberOfParameterSets = ParameterSetList?.Count ?? 0; + GetField(nameof(ParameterSetList)).Size = NumberOfParameterSets * ParameterSet.Size(Header.StandardizedRevision); + var builder = new StringBuilder(BuildHeader()); + int prefixIndex = 1; + var fields = DataFieldsByRevision().ToList(); + builder.Append(base.Pack(fields, ref prefixIndex)); + return builder.ToString(); } - protected virtual string PackParameterSetList() + protected override void ProcessDataFields(List dataFields, ReadOnlySpan package) { - var packages = new List(); - foreach (var pset in ParameterSetList) - { - packages.Add(pset.Pack(Header.Revision)); - } - - return string.Join(";", packages) + ";"; + foreach (var dataField in DataFieldsByRevision()) + ProcessDataField(dataField, package); } - protected override Dictionary> RegisterDatafields() + private IEnumerable DataFieldsByRevision() { - return new Dictionary>() + var fromRevision = Header.StandardizedRevision > 1 ? 2 : 1; + for (int i = fromRevision; i <= Header.StandardizedRevision; i++) + { + if (RevisionsByFields.TryGetValue(i, out var dataFields)) { + foreach (var dataField in dataFields) { - 1, new List() - { - DataField.Number(DataFields.JobId, 20, 2), - DataField.String(DataFields.JobName, 24, 25), - DataField.Number(DataFields.ForcedOrder, 51, 1), - DataField.Number(DataFields.MaxTimeForFirstTightening, 54, 4), - DataField.Number(DataFields.MaxTimeToCompleteJob, 60, 5), - DataField.Number(DataFields.JobBatchDone, 67, 1), - DataField.Boolean(DataFields.LockAtJobDone, 70), - DataField.Boolean(DataFields.UseLineControl, 73), - DataField.Boolean(DataFields.RepeatJob, 76), - DataField.Number(DataFields.ToolLoosening, 79, 1), - DataField.Number(DataFields.Reserved, 82, 1), - DataField.Number(DataFields.NumberOfParameterSets, 85, 2), - DataField.Volatile(DataFields.ParameterSetList, 89) // defined at runtime - } - }, - }; - } + if (dataField.Field == 13) //ParameterSetList + continue; - private void HandleRevisions() - { - var jobIdField = GetField(1, DataFields.JobId); - if (Header.Revision > 1) - { - jobIdField.Size = 4; - } - else - { - jobIdField.Size = 2; + yield return dataField; + } + } } - int index = jobIdField.Index + jobIdField.Size; - for (int i = (int)DataFields.JobName; i < RevisionsByFields[1].Count; i++) + yield return GetField(nameof(ParameterSetList)); + } + + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) + { + if (dataField.Field == 13) //ParameterSetList { - var field = GetField(1, i); - field.Index = 2 + index; - index = field.Index + field.Size; + dataField.Size = NumberOfParameterSets * ParameterSet.Size(Header.StandardizedRevision); } + base.ProcessDataField(dataField, package); } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { JobId, @@ -191,4 +173,4 @@ protected enum DataFields ParameterSetList } } -} \ No newline at end of file +} diff --git a/src/OpenProtocolInterpreter/Job/Mid0035.cs b/src/OpenProtocolInterpreter/Job/Mid0035.cs index a390d6ee..27298cd0 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0035.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0035.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; -using System.Dynamic; +using System.Linq; +using System.Text; namespace OpenProtocolInterpreter.Job { @@ -16,93 +17,63 @@ public class Mid0035 : Mid, IJob, IController, IAcknowledgeable { public const int MID = 35; - public int JobId - { - get => GetField(1, DataFields.JobId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.JobId).SetValue(OpenProtocolConvert.ToString, value); - } - public JobStatus JobStatus - { - get => (JobStatus)GetField(1, DataFields.JobStatus).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.JobStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public JobBatchMode JobBatchMode - { - get => (JobBatchMode)GetField(1, DataFields.JobBatchMode).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.JobBatchMode).SetValue(OpenProtocolConvert.ToString, value); - } - public int JobBatchSize - { - get => GetField(1, DataFields.JobBatchSize).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.JobBatchSize).SetValue(OpenProtocolConvert.ToString, value); - } - public int JobBatchCounter - { - get => GetField(1, DataFields.JobBatchCounter).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.JobBatchCounter).SetValue(OpenProtocolConvert.ToString, value); - } - public DateTime TimeStamp - { - get => GetField(1, DataFields.Timestamp).GetValue(OpenProtocolConvert.ToDateTime); - set => GetField(1, DataFields.Timestamp).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 2)] + [Int32DataFieldDefinition(revision: 2, field: 1, Index = 20, Size = 4)] + public int JobId { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 24, Size = 1)] + [Int32DataFieldDefinition(revision: 2, field: 2, Index = 26, Size = 1)] + public JobStatus JobStatus { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 3, Index = 27, Size = 1)] + [Int32DataFieldDefinition(revision: 2, field: 3, Index = 29, Size = 1)] + public JobBatchMode JobBatchMode { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 4, Index = 30, Size = 4)] + [Int32DataFieldDefinition(revision: 2, field: 4, Index = 32, Size = 4)] + public int JobBatchSize { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 5, Index = 36, Size = 4)] + [Int32DataFieldDefinition(revision: 2, field: 5, Index = 38, Size = 4)] + public int JobBatchCounter { get; set; } + + [TimestampDataFieldDefinition(revision: 1, field: 6, Index = 42)] + [TimestampDataFieldDefinition(revision: 2, field: 6, Index = 44)] + public DateTime TimeStamp { get; set; } + //Rev 3 - public int JobCurrentStep - { - get => GetField(3, DataFields.JobCurrentStep).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(3, DataFields.JobCurrentStep).SetValue(OpenProtocolConvert.ToString, value); - } - public int JobTotalNumberOfSteps - { - get => GetField(3, DataFields.JobTotalNumberOfSteps).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(3, DataFields.JobTotalNumberOfSteps).SetValue(OpenProtocolConvert.ToString, value); - } - public int JobStepType - { - get => GetField(3, DataFields.JobStepType).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(3, DataFields.JobStepType).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 3, field: 7, Index = 65, Size = 3)] + public int JobCurrentStep { get; set; } + + [Int32DataFieldDefinition(revision: 3, field: 8, Index = 70, Size = 3)] + public int JobTotalNumberOfSteps { get; set; } + + [Int32DataFieldDefinition(revision: 3, field: 9, Index = 75, Size = 2)] + public int JobStepType { get; set; } + //Rev 4 - public JobTighteningStatus JobTighteningStatus - { - get => (JobTighteningStatus)GetField(4, DataFields.JobTighteningStatus).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(4, DataFields.JobTighteningStatus).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 4, field: 10, Index = 79, Size = 2)] + public JobTighteningStatus JobTighteningStatus { get; set; } + //Rev 5 - public int JobSequenceNumber - { - get => GetField(5, DataFields.JobSequenceNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(5, DataFields.JobSequenceNumber).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 5, field: 11, Index = 83, Size = 5)] + public int JobSequenceNumber { get; set; } - public string VinNumber - { - get => GetField(5, DataFields.VinNumber).Value; - set => GetField(5, DataFields.VinNumber).SetValue(value); - } + [StringDataFieldDefinition(revision: 5, field: 12, Index = 90, Size = 25)] + public string VinNumber { get; set; } - public string IdentifierResultPart2 - { - get => GetField(5, DataFields.IdentifierResultPart2).Value; - set => GetField(5, DataFields.IdentifierResultPart2).SetValue(value); - } + [StringDataFieldDefinition(revision: 5, field: 13, Index = 117, Size = 25)] + public string IdentifierResultPart2 { get; set; } - public string IdentifierResultPart3 - { - get => GetField(5, DataFields.IdentifierResultPart3).Value; - set => GetField(5, DataFields.IdentifierResultPart3).SetValue(value); - } + [StringDataFieldDefinition(revision: 5, field: 14, Index = 144, Size = 25)] + public string IdentifierResultPart3 { get; set; } - public string IdentifierResultPart4 - { - get => GetField(5, DataFields.IdentifierResultPart4).Value; - set => GetField(5, DataFields.IdentifierResultPart4).SetValue(value); - } - public string JointId - { - get => GetField(6, DataFields.JointId).Value; - set => GetField(6, DataFields.JointId).SetValue(value); - } + [StringDataFieldDefinition(revision: 5, field: 15, Index = 171, Size = 25)] + public string IdentifierResultPart4 { get; set; } + + //Rev 6 + [StringDataFieldDefinition(revision: 6, field: 16, Index = 198, Size = 25)] + public string JointId { get; set; } public Mid0035() : this(DEFAULT_REVISION) { @@ -122,90 +93,41 @@ public Mid0035(int revision) : this(new Header() } - public override string Pack() + protected override string BuildHeader() { - HandleRevision(); - return base.Pack(); + Header.Length = Header.DefaultSize + DataFieldsByRevision().Sum(x => x.TotalSize); + return Header.ToString(); } - public override Mid Parse(ReadOnlySpan package) + public override string Pack() { - Header = ProcessHeader(package); - HandleRevision(); - ProcessDataFields(package); - return this; + var builder = new StringBuilder(); + var fields = DataFieldsByRevision().OrderBy(x => x.Index).ToList(); + int prefixIndex = 1; + builder.Append(BuildHeader()); + builder.Append(base.Pack(fields, ref prefixIndex)); + return builder.ToString(); } - protected override Dictionary> RegisterDatafields() + protected override void ProcessDataFields(ReadOnlySpan package) { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.JobId, 20, 2), - DataField.Number(DataFields.JobStatus, 24, 1), - DataField.Number(DataFields.JobBatchMode, 27, 1), - DataField.Number(DataFields.JobBatchSize, 30, 4), - DataField.Number(DataFields.JobBatchCounter, 36, 4), - DataField.Timestamp(DataFields.Timestamp, 42) - } - }, - { - 3, new List() - { - DataField.Number(DataFields.JobCurrentStep, 65, 3), - DataField.Number(DataFields.JobTotalNumberOfSteps, 70, 3), - DataField.Number(DataFields.JobStepType, 75, 2) - } - }, - { - 4, new List() - { - DataField.Number(DataFields.JobTighteningStatus, 79, 2) - } - }, - { - 5, new List() - { - DataField.Number(DataFields.JobSequenceNumber, 83, 5), - DataField.String(DataFields.VinNumber, 90, 25), - DataField.String(DataFields.IdentifierResultPart2, 117, 25), - DataField.String(DataFields.IdentifierResultPart3, 144, 25), - DataField.String(DataFields.IdentifierResultPart4, 171, 25), - } - }, - { - 6, new List() - { - DataField.String(DataFields.JointId, 198, 25) - } - } - - }; + var fields = DataFieldsByRevision().OrderBy(x => x.Index).ToList(); + base.ProcessDataFields(fields, package); } - private void HandleRevision() + private IEnumerable DataFieldsByRevision() { - var jobIdField = GetField(1, DataFields.JobId); - if (Header.Revision > 1) - { - jobIdField.Size = 4; - } - else - { - jobIdField.Size = 2; - } + var fromRevision = Header.StandardizedRevision > 1 ? 2 : 1; + var toRevision = Header.StandardizedRevision; - int index = jobIdField.Index + jobIdField.Size; - for (int i = (int)DataFields.JobStatus; i < RevisionsByFields[1].Count; i++) + for (int i = fromRevision; i <= toRevision; i++) { - var field = GetField(1, i); - field.Index = 2 + index; - index = field.Index + field.Size; + foreach (var dataField in RevisionsByFields[i]) + yield return dataField; } } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { //rev 1 and 2 diff --git a/src/OpenProtocolInterpreter/Job/Mid0038.cs b/src/OpenProtocolInterpreter/Job/Mid0038.cs index c2722e8a..142fff0d 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0038.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0038.cs @@ -1,5 +1,7 @@ using System; -using System.Collections.Generic; +using System.Collections.Generic; +using System.Linq; +using System.Text; namespace OpenProtocolInterpreter.Job { @@ -15,20 +17,18 @@ public class Mid0038 : Mid, IJob, IIntegrator, IAcceptableCommand, IDeclinableCo public IEnumerable DocumentedPossibleErrors => new Error[] { Error.JobCannotBeSet, Error.InvalidData }; - public int JobId - { - get => GetField(1, DataFields.JobId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.JobId).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 2, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 2, field: 1, Index = 20, Size = 4, HasPrefix = false)] + public int JobId { get; set; } public Mid0038() : this(DEFAULT_REVISION) { } - + public Mid0038(Header header) : base(header) { - + } public Mid0038(int revision) : this(new Header() @@ -39,48 +39,34 @@ public Mid0038(int revision) : this(new Header() { } - public override string Pack() + protected override string BuildHeader() { - HandleRevision(); - return base.Pack(); - } + Header.Length = Header.DefaultSize; + if (RevisionsByFields.TryGetValue(Header.StandardizedRevision, out var dataFields)) + { + Header.Length += dataFields.Sum(x => x.TotalSize); + } - public override Mid Parse(ReadOnlySpan package) - { - Header = ProcessHeader(package); - HandleRevision(); - ProcessDataFields(package); - return this; + return Header.ToString(); } - protected override Dictionary> RegisterDatafields() + public override string Pack() { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.JobId, 20, GetJobIdSize(), false), - } - } - }; - } + var builder = new StringBuilder(BuildHeader()); - private void HandleRevision() - { - var dataField = GetField(1, DataFields.JobId); - dataField.Size = GetJobIdSize(); + int prefixIndex = 1; + builder.Append(base.Pack(RevisionsByFields[Header.StandardizedRevision], ref prefixIndex)); + return builder.ToString(); } - private int GetJobIdSize() + public override Mid Parse(ReadOnlySpan package) { - return Header.StandardizedRevision switch - { - <= 1 => 2, - _ => 4 - }; + Header = ProcessHeader(package); + ProcessDataFields(RevisionsByFields[Header.StandardizedRevision], package); + return this; } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { JobId diff --git a/src/OpenProtocolInterpreter/Job/Mid0039.cs b/src/OpenProtocolInterpreter/Job/Mid0039.cs index 4b9490b0..016ad35a 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0039.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0039.cs @@ -1,5 +1,7 @@ using System; -using System.Collections.Generic; +using System.Collections.Generic; +using System.Linq; +using System.Text; namespace OpenProtocolInterpreter.Job { @@ -15,11 +17,9 @@ public class Mid0039 : Mid, IJob, IIntegrator, IAcceptableCommand, IDeclinableCo public IEnumerable DocumentedPossibleErrors => new Error[] { Error.JobNotRunning, Error.InvalidData }; - public int JobId - { - get => GetField(1, DataFields.JobId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.JobId).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 2, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 2, field: 1, Index = 20, Size = 4, HasPrefix = false)] + public int JobId { get; set; } public Mid0039() : this(DEFAULT_REVISION) { @@ -28,7 +28,7 @@ public Mid0039() : this(DEFAULT_REVISION) public Mid0039(Header header) : base(header) { - + } public Mid0039(int revision) : this(new Header() @@ -39,45 +39,34 @@ public Mid0039(int revision) : this(new Header() { } - public override string Pack() + protected override string BuildHeader() { - HandleRevisions(); - return base.Pack(); - } + Header.Length = Header.DefaultSize; + if (RevisionsByFields.TryGetValue(Header.StandardizedRevision, out var dataFields)) + { + Header.Length += dataFields.Sum(x => x.TotalSize); + } - public override Mid Parse(ReadOnlySpan package) - { - Header = ProcessHeader(package); - HandleRevisions(); - ProcessDataFields(package); - return this; + return Header.ToString(); } - protected override Dictionary> RegisterDatafields() + public override string Pack() { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.JobId, 20, 2, false), - } - } - }; + var builder = new StringBuilder(BuildHeader()); + + int prefixIndex = 1; + builder.Append(base.Pack(RevisionsByFields[Header.StandardizedRevision], ref prefixIndex)); + return builder.ToString(); } - private void HandleRevisions() + public override Mid Parse(ReadOnlySpan package) { - if (Header.Revision > 1) - { - GetField(1, DataFields.JobId).Size = 4; - } - else - { - GetField(1, DataFields.JobId).Size = 2; - } + Header = ProcessHeader(package); + ProcessDataFields(RevisionsByFields[Header.StandardizedRevision], package); + return this; } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { JobId diff --git a/src/OpenProtocolInterpreter/Job/ParameterSet.cs b/src/OpenProtocolInterpreter/Job/ParameterSet.cs index 813eb9eb..d42e6764 100644 --- a/src/OpenProtocolInterpreter/Job/ParameterSet.cs +++ b/src/OpenProtocolInterpreter/Job/ParameterSet.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; +using System.Text; namespace OpenProtocolInterpreter.Job { @@ -101,10 +103,45 @@ public static IEnumerable ParseAll(string section, int revision) public static int Size(int revision) => revision switch { + 1 => 12, + 2 => 12, 3 => 44, 4 => 49, 5 => 51, - _ => 12, + _ => 51, //Default will always be the last }; } + + public class ParameterSetCollectionDefinitionAttribute : DataFieldDefinitionAttribute + { + public ParameterSetCollectionDefinitionAttribute(int revision) : base(revision) + { + + } + public ParameterSetCollectionDefinitionAttribute(int field, int revision) : base(field, revision) + { + + } + + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + { + return new DataField>(Field, index, Size, HasPrefix) + { + DefaultConverter = PackParameterSets, + DefaultParser = ParseParameterSets + }.Bind(mid, propertyInfo); + } + + private string PackParameterSets(char paddingChar, int size, PaddingOrientation orientation, List parameterSets) + { + var list = new List(); + foreach (var parameterSet in parameterSets) + list.Add(parameterSet.Pack(Revision)); + + return string.Concat(string.Join(";", list), ";"); + } + + private List ParseParameterSets(string value) + => ParameterSet.ParseAll(value, Revision).ToList(); + } } diff --git a/src/OpenProtocolInterpreter/MID.cs b/src/OpenProtocolInterpreter/MID.cs index 706f3a63..5c06fba2 100644 --- a/src/OpenProtocolInterpreter/MID.cs +++ b/src/OpenProtocolInterpreter/MID.cs @@ -311,7 +311,7 @@ protected DataField GetField(string propertyName) } var dataField = metadata.First(x => x.Property.Name == propertyName); - return RevisionsByFields[dataField.Attribute.Revision].First(x => x.Field == dataField.Attribute.Field); + return RevisionsByFields[Header.StandardizedRevision].First(x => x.Field == dataField.Attribute.Field); } protected DataField GetField(int revision, TEnum field) where TEnum : struct, Enum diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0065.cs b/src/OpenProtocolInterpreter/Tightening/Mid0065.cs index aa54e102..28c54c76 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0065.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0065.cs @@ -246,7 +246,7 @@ protected override string BuildHeader() { if (RevisionsByFields.Any()) { - Header.Length = 20; + Header.Length = Header.DefaultSize; if (Header.Revision == 998) { var stageResultField = GetField(nameof(StageResults)); @@ -273,7 +273,7 @@ public override string Pack() protected override void ProcessDataFields(ReadOnlySpan package) { var fields = DataFieldsByRevision().OrderBy(f => f.Index).ToList(); - ProcessDataFields(fields, package); + base.ProcessDataFields(fields, package); } protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) From 178f3a2f789e262cdbd1916098c330d3bedd3e24 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Mon, 13 Jul 2026 20:24:21 -0300 Subject: [PATCH 40/78] updated advanced job mids to use datafields --- .../Job/Advanced/TestMid0140.cs | 63 ++- .../IOInterface/Mid0215.cs | 2 - .../Job/Advanced/AdvancedJob.cs | 37 +- .../Job/Advanced/Mid0129.cs | 35 +- .../Job/Advanced/Mid0130.cs | 28 +- .../Job/Advanced/Mid0140.cs | 400 +++++++----------- src/OpenProtocolInterpreter/MID.cs | 12 +- .../Tightening/Mid0061.cs | 2 +- 8 files changed, 278 insertions(+), 301 deletions(-) diff --git a/src/MIDTesters.Core/Job/Advanced/TestMid0140.cs b/src/MIDTesters.Core/Job/Advanced/TestMid0140.cs index 4383307b..2f7531cf 100644 --- a/src/MIDTesters.Core/Job/Advanced/TestMid0140.cs +++ b/src/MIDTesters.Core/Job/Advanced/TestMid0140.cs @@ -3,7 +3,7 @@ using OpenProtocolInterpreter.Job.Advanced; using System.Threading; -namespace MIDTesters.Core.Job.Advanced +namespace MIDTesters.Job.Advanced { [TestClass] [TestCategory("Job"), TestCategory("Advanced Job")] @@ -74,7 +74,7 @@ public void Mid0140Revision2() { string package = "02250140002 01000102Job 1 03020415:045:0:22:02:10:0107:Job Action 1 :01;15:045:0:12:12:13:0407:Job Action 2 :02;05106107208109110111112010013100001400901511611711811912000120"; var mid = _midInterpreter.Parse(package); - + Assert.AreEqual(typeof(Mid0140), mid.GetType()); Assert.AreEqual(1, mid.JobId); Assert.AreEqual("Job 1", mid.JobName.TrimEnd()); @@ -222,5 +222,64 @@ public void Mid0140ByteRevision4() Assert.AreEqual(5000, mid.JobSequenceNumber); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 999"), TestCategory("ASCII")] + public void Mid0140Revision999() + { + string package = "01500140999 01000102Job 1 03020414:045:0:22:02:10;01:013:1:10:01:05;0510610720810911011111201001310000140090151161171181191"; + var mid = _midInterpreter.Parse(package); + + Assert.AreEqual(typeof(Mid0140), mid.GetType()); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual("Job 1", mid.JobName.TrimEnd()); + Assert.AreEqual(2, mid.NumberOfParameterSets); + Assert.AreEqual(2, mid.JobList.Count); + Assert.AreEqual(ForcedOrder.ForcedOrder, mid.ForcedOrder); + Assert.IsTrue(mid.LockAtJobDone); + Assert.AreEqual(ToolLoosening.EnableOnlyOnNokTightenings, mid.ToolLoosening); + Assert.IsTrue(mid.RepeatJob); + Assert.AreEqual(BatchMode.BothOkAndNok, mid.BatchMode); + Assert.IsTrue(mid.DecrementBatchAtOkLoosening); + Assert.AreEqual(100, mid.MaxTimeForFirstTightening); + Assert.AreEqual(10000, mid.MaxTimeToCompleteJob); + Assert.AreEqual(90, mid.DisplayResultAtAutoSelect); + Assert.IsTrue(mid.UsingLineControl); + Assert.AreEqual(IdentifierPart.Other, mid.IdentifierResultPart); + Assert.IsTrue(mid.ResultOfNonTightenings); + Assert.IsTrue(mid.ResetAllIdentifiersAtJobDone); + Assert.AreEqual(Reserved.G, mid.Reserved); + AssertEqualPackages(package, mid); + } + + [TestMethod] + [TestCategory("Revision 999"), TestCategory("ByteArray")] + public void Mid0140ByteRevision999() + { + string package = "01500140999 01000102Job 1 03020414:045:0:22:02:10;01:013:1:10:01:05;0510610720810911011111201001310000140090151161171181191"; + byte[] bytes = GetAsciiBytes(package); + var mid = _midInterpreter.Parse(bytes); + + Assert.AreEqual(typeof(Mid0140), mid.GetType()); + Assert.AreEqual(1, mid.JobId); + Assert.AreEqual("Job 1", mid.JobName.TrimEnd()); + Assert.AreEqual(2, mid.NumberOfParameterSets); + Assert.AreEqual(2, mid.JobList.Count); + Assert.AreEqual(ForcedOrder.ForcedOrder, mid.ForcedOrder); + Assert.IsTrue(mid.LockAtJobDone); + Assert.AreEqual(ToolLoosening.EnableOnlyOnNokTightenings, mid.ToolLoosening); + Assert.IsTrue(mid.RepeatJob); + Assert.AreEqual(BatchMode.BothOkAndNok, mid.BatchMode); + Assert.IsTrue(mid.DecrementBatchAtOkLoosening); + Assert.AreEqual(100, mid.MaxTimeForFirstTightening); + Assert.AreEqual(10000, mid.MaxTimeToCompleteJob); + Assert.AreEqual(90, mid.DisplayResultAtAutoSelect); + Assert.IsTrue(mid.UsingLineControl); + Assert.AreEqual(IdentifierPart.Other, mid.IdentifierResultPart); + Assert.IsTrue(mid.ResultOfNonTightenings); + Assert.IsTrue(mid.ResetAllIdentifiersAtJobDone); + Assert.AreEqual(Reserved.G, mid.Reserved); + AssertEqualPackages(bytes, mid); + } } } diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs index 801e5323..e260e55a 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using System.Text; -using System.Threading.Tasks.Dataflow; namespace OpenProtocolInterpreter.IOInterface { diff --git a/src/OpenProtocolInterpreter/Job/Advanced/AdvancedJob.cs b/src/OpenProtocolInterpreter/Job/Advanced/AdvancedJob.cs index 9a196bab..66e7e8f2 100644 --- a/src/OpenProtocolInterpreter/Job/Advanced/AdvancedJob.cs +++ b/src/OpenProtocolInterpreter/Job/Advanced/AdvancedJob.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Reflection; namespace OpenProtocolInterpreter.Job.Advanced { @@ -112,12 +113,46 @@ internal static int GetDefaultSize(int revision) { return revision switch { + 1 => 15, 2 => 52, 3 => 63, 4 => 67, 999 => 18, - _ => 15, + _ => 67, }; } } + + public class AdvancedJobCollectionDefinitionAttribute : DataFieldDefinitionAttribute + { + public AdvancedJobCollectionDefinitionAttribute(int revision) : base(revision) + { + + } + public AdvancedJobCollectionDefinitionAttribute(int field, int revision) : base(field, revision) + { + + } + + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + { + return new DataField>(Field, index, Size, HasPrefix) + { + DefaultConverter = PackAdvancedJobs, + DefaultParser = ParseAdvancedJobs + }.Bind(mid, propertyInfo); + } + + private string PackAdvancedJobs(char paddingChar, int size, PaddingOrientation orientation, List advancedJobs) + { + var list = new List(); + foreach (var advancedJob in advancedJobs) + list.Add(advancedJob.Pack(Revision)); + + return string.Concat(string.Join(";", list), ";"); + } + + private List ParseAdvancedJobs(string value) + => AdvancedJob.ParseAll(value, Revision).ToList(); + } } diff --git a/src/OpenProtocolInterpreter/Job/Advanced/Mid0129.cs b/src/OpenProtocolInterpreter/Job/Advanced/Mid0129.cs index 833064f4..c22a5c0f 100644 --- a/src/OpenProtocolInterpreter/Job/Advanced/Mid0129.cs +++ b/src/OpenProtocolInterpreter/Job/Advanced/Mid0129.cs @@ -1,11 +1,12 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.Job.Advanced { /// /// Job batch increment /// - /// Decrement the Job batch if there is a current running Job. + /// Decrement the Job batch if there is a current running Job. /// Two revisions are available for this MID. /// The default revision or revision 1 does not contain any argument and always decrement the last /// tightening completed in a Job. @@ -21,16 +22,11 @@ public class Mid0129 : Mid, IAdvancedJob, IIntegrator, IAcceptableCommand, IDecl public IEnumerable DocumentedPossibleErrors => new Error[] { Error.JobBatchDecrementFailed }; - public int ChannelId - { - get => GetField(2, DataFields.ChannelId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.ChannelId).SetValue(OpenProtocolConvert.ToString, value); - } - public int ParameterSetId - { - get => GetField(2, DataFields.ParameterSetId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.ParameterSetId).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 2, field: 1, Index = 20, Size = 2)] + public int ChannelId { get; set; } + + [Int32DataFieldDefinition(revision: 2, field: 2, Index = 24, Size = 3)] + public int ParameterSetId { get; set; } public Mid0129() : this(DEFAULT_REVISION) { @@ -47,23 +43,10 @@ public Mid0129(int revision) : this(new Header() Revision = revision }) { - - } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 2, new List() - { - DataField.Number(DataFields.ChannelId, 20, 2), - DataField.Number(DataFields.ParameterSetId, 24, 3) - } - } - }; } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { ChannelId, diff --git a/src/OpenProtocolInterpreter/Job/Advanced/Mid0130.cs b/src/OpenProtocolInterpreter/Job/Advanced/Mid0130.cs index 17d80b40..8961cd2f 100644 --- a/src/OpenProtocolInterpreter/Job/Advanced/Mid0130.cs +++ b/src/OpenProtocolInterpreter/Job/Advanced/Mid0130.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.Job.Advanced { @@ -14,13 +15,10 @@ public class Mid0130 : Mid, IAdvancedJob, IIntegrator, IAcceptableCommand /// /// False => Set Job Off - /// True => Reset Job Off + /// True => Reset Job Off /// - public bool JobOffStatus - { - get => GetField(1, DataFields.JobOffStatus).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.JobOffStatus).SetValue(OpenProtocolConvert.ToString, value); - } + [BooleanDataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 1, HasPrefix = false)] + public bool JobOffStatus { get; set; } public Mid0130() : this(new Header() { @@ -35,22 +33,10 @@ public Mid0130(Header header) : base(header) { } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Boolean(DataFields.JobOffStatus, 20, false), - } - } - }; - } - + [Obsolete("Use the constructor that takes a Header as parameter instead")] protected enum DataFields { JobOffStatus } } -} \ No newline at end of file +} diff --git a/src/OpenProtocolInterpreter/Job/Advanced/Mid0140.cs b/src/OpenProtocolInterpreter/Job/Advanced/Mid0140.cs index 6733f135..890379c0 100644 --- a/src/OpenProtocolInterpreter/Job/Advanced/Mid0140.cs +++ b/src/OpenProtocolInterpreter/Job/Advanced/Mid0140.cs @@ -1,6 +1,7 @@ using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; +using System.Text; namespace OpenProtocolInterpreter.Job.Advanced { @@ -21,103 +22,135 @@ public class Mid0140 : Mid, IAdvancedJob, IIntegrator, IAcceptableCommand { public const int MID = 140; - public int JobId - { - get => GetField(Header.StandardizedRevision, DataFields.JobId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(Header.StandardizedRevision, DataFields.JobId).SetValue(OpenProtocolConvert.ToString, value); - } - public string JobName - { - get => GetField(Header.StandardizedRevision, DataFields.JobName).Value; - set => GetField(Header.StandardizedRevision, DataFields.JobName).SetValue(value); - } - public int NumberOfParameterSets - { - get => GetField(Header.StandardizedRevision, DataFields.NumberOfParameterSets).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(Header.StandardizedRevision, DataFields.NumberOfParameterSets).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 4)] + [Int32DataFieldDefinition(revision: 2, field: 1, Index = 20, Size = 4)] + [Int32DataFieldDefinition(revision: 3, field: 1, Index = 20, Size = 4)] + [Int32DataFieldDefinition(revision: 4, field: 1, Index = 20, Size = 4)] + [Int32DataFieldDefinition(revision: 999, field: 1, Index = 20, Size = 4)] + public int JobId { get; set; } + + [StringDataFieldDefinition(revision: 1, field: 2, Index = 26, Size = 25)] + [StringDataFieldDefinition(revision: 2, field: 2, Index = 26, Size = 25)] + [StringDataFieldDefinition(revision: 3, field: 2, Index = 26, Size = 25)] + [StringDataFieldDefinition(revision: 4, field: 2, Index = 26, Size = 25)] + [StringDataFieldDefinition(revision: 999, field: 2, Index = 26, Size = 25)] + public string JobName { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 3, Index = 53, Size = 2)] + [Int32DataFieldDefinition(revision: 2, field: 3, Index = 53, Size = 2)] + [Int32DataFieldDefinition(revision: 3, field: 3, Index = 53, Size = 2)] + [Int32DataFieldDefinition(revision: 4, field: 3, Index = 53, Size = 2)] + [Int32DataFieldDefinition(revision: 999, field: 3, Index = 53, Size = 2)] + public int NumberOfParameterSets { get; set; } + + [AdvancedJobCollectionDefinition(revision: 1, field: 4, Index = 57, Size = 0)] + [AdvancedJobCollectionDefinition(revision: 2, field: 4, Index = 57, Size = 0)] + [AdvancedJobCollectionDefinition(revision: 3, field: 4, Index = 57, Size = 0)] + [AdvancedJobCollectionDefinition(revision: 4, field: 4, Index = 57, Size = 0)] + [AdvancedJobCollectionDefinition(revision: 999, field: 4, Index = 57, Size = 0)] public List JobList { get; set; } - public ForcedOrder ForcedOrder - { - get => (ForcedOrder)GetField(Header.StandardizedRevision, DataFields.ForcedOrder).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(Header.StandardizedRevision, DataFields.ForcedOrder).SetValue(OpenProtocolConvert.ToString, value); - } - public bool LockAtJobDone - { - get => GetField(Header.StandardizedRevision, DataFields.LockAtJobDone).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(Header.StandardizedRevision, DataFields.LockAtJobDone).SetValue(OpenProtocolConvert.ToString, value); - } - public ToolLoosening ToolLoosening - { - get => (ToolLoosening)GetField(Header.StandardizedRevision, DataFields.ToolLoosening).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(Header.StandardizedRevision, DataFields.ToolLoosening).SetValue(OpenProtocolConvert.ToString, value); - } - public bool RepeatJob - { - get => GetField(Header.StandardizedRevision, DataFields.RepeatJob).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(Header.StandardizedRevision, DataFields.RepeatJob).SetValue(OpenProtocolConvert.ToString, value); - } - public BatchMode BatchMode - { - get => (BatchMode)GetField(Header.StandardizedRevision, DataFields.JobBatchDone).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(Header.StandardizedRevision, DataFields.JobBatchDone).SetValue(OpenProtocolConvert.ToString, value); - } - public bool BatchStatusAtIncrement - { - get => GetField(Header.StandardizedRevision, DataFields.BatchStatusAtIncrement).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(Header.StandardizedRevision, DataFields.BatchStatusAtIncrement).SetValue(OpenProtocolConvert.ToString, value); - } - public bool DecrementBatchAtOkLoosening - { - get => GetField(Header.StandardizedRevision, DataFields.DecrementBatchAtOkLoosening).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(Header.StandardizedRevision, DataFields.DecrementBatchAtOkLoosening).SetValue(OpenProtocolConvert.ToString, value); - } - public int MaxTimeForFirstTightening - { - get => GetField(Header.StandardizedRevision, DataFields.MaxTimeForFirstTightening).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(Header.StandardizedRevision, DataFields.MaxTimeForFirstTightening).SetValue(OpenProtocolConvert.ToString, value); - } - public int MaxTimeToCompleteJob - { - get => GetField(Header.StandardizedRevision, DataFields.MaxTimeToCompleteJob).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(Header.StandardizedRevision, DataFields.MaxTimeToCompleteJob).SetValue(OpenProtocolConvert.ToString, value); - } - public int DisplayResultAtAutoSelect - { - get => GetField(Header.StandardizedRevision, DataFields.DisplayResultAtAutoSelect).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(Header.StandardizedRevision, DataFields.DisplayResultAtAutoSelect).SetValue(OpenProtocolConvert.ToString, value); - } - public bool UsingLineControl - { - get => GetField(Header.StandardizedRevision, DataFields.UseLineControl).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(Header.StandardizedRevision, DataFields.UseLineControl).SetValue(OpenProtocolConvert.ToString, value); - } - public IdentifierPart IdentifierResultPart - { - get => (IdentifierPart)GetField(Header.StandardizedRevision, DataFields.IdentifierResultPart).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(Header.StandardizedRevision, DataFields.IdentifierResultPart).SetValue(OpenProtocolConvert.ToString, value); - } - public bool ResultOfNonTightenings - { - get => GetField(Header.StandardizedRevision, DataFields.ResultOfNonTightenings).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(Header.StandardizedRevision, DataFields.ResultOfNonTightenings).SetValue(OpenProtocolConvert.ToString, value); - } - public bool ResetAllIdentifiersAtJobDone - { - get => GetField(Header.StandardizedRevision, DataFields.ResetAllIdentifiersAtJobDone).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(Header.StandardizedRevision, DataFields.ResetAllIdentifiersAtJobDone).SetValue(OpenProtocolConvert.ToString, value); - } - public Reserved Reserved - { - get => (Reserved)GetField(Header.StandardizedRevision, DataFields.Reserved).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(Header.StandardizedRevision, DataFields.Reserved).SetValue(OpenProtocolConvert.ToString, value); - } - public int JobSequenceNumber - { - get => GetField(Header.StandardizedRevision, DataFields.JobSequenceNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(Header.StandardizedRevision, DataFields.JobSequenceNumber).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 5, Index = 0, Size = 1)] + [Int32DataFieldDefinition(revision: 2, field: 5, Index = 0, Size = 1)] + [Int32DataFieldDefinition(revision: 3, field: 5, Index = 0, Size = 1)] + [Int32DataFieldDefinition(revision: 4, field: 5, Index = 0, Size = 1)] + [Int32DataFieldDefinition(revision: 999, field: 5, Index = 0, Size = 1)] + public ForcedOrder ForcedOrder { get; set; } + + [BooleanDataFieldDefinition(revision: 1, field: 6, Index = 0)] + [BooleanDataFieldDefinition(revision: 2, field: 6, Index = 0)] + [BooleanDataFieldDefinition(revision: 3, field: 6, Index = 0)] + [BooleanDataFieldDefinition(revision: 4, field: 6, Index = 0)] + [BooleanDataFieldDefinition(revision: 999, field: 6, Index = 0)] + public bool LockAtJobDone { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 7, Index = 0, Size = 1)] + [Int32DataFieldDefinition(revision: 2, field: 7, Index = 0, Size = 1)] + [Int32DataFieldDefinition(revision: 999, field: 7, Index = 0, Size = 1)] + public ToolLoosening ToolLoosening { get; set; } + + [BooleanDataFieldDefinition(revision: 1, field: 8, Index = 0)] + [BooleanDataFieldDefinition(revision: 2, field: 8, Index = 0)] + [BooleanDataFieldDefinition(revision: 3, field: 7, Index = 0)] + [BooleanDataFieldDefinition(revision: 4, field: 7, Index = 0)] + [BooleanDataFieldDefinition(revision: 999, field: 8, Index = 0)] + public bool RepeatJob { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 9, Index = 0, Size = 1)] + [Int32DataFieldDefinition(revision: 2, field: 9, Index = 0, Size = 1)] + [Int32DataFieldDefinition(revision: 999, field: 9, Index = 0, Size = 1)] + public BatchMode BatchMode { get; set; } + + [BooleanDataFieldDefinition(revision: 1, field: 10, Index = 0)] + [BooleanDataFieldDefinition(revision: 2, field: 10, Index = 0)] + [BooleanDataFieldDefinition(revision: 999, field: 10, Index = 0)] + public bool BatchStatusAtIncrement { get; set; } + + [BooleanDataFieldDefinition(revision: 1, field: 11, Index = 0)] + [BooleanDataFieldDefinition(revision: 2, field: 11, Index = 0)] + [BooleanDataFieldDefinition(revision: 999, field: 11, Index = 0)] + public bool DecrementBatchAtOkLoosening { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 12, Index = 0, Size = 4)] + [Int32DataFieldDefinition(revision: 2, field: 12, Index = 0, Size = 4)] + [Int32DataFieldDefinition(revision: 3, field: 8, Index = 0, Size = 4)] + [Int32DataFieldDefinition(revision: 4, field: 8, Index = 0, Size = 4)] + [Int32DataFieldDefinition(revision: 999, field: 12, Index = 0, Size = 4)] + public int MaxTimeForFirstTightening { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 13, Index = 0, Size = 5)] + [Int32DataFieldDefinition(revision: 2, field: 13, Index = 0, Size = 5)] + [Int32DataFieldDefinition(revision: 3, field: 9, Index = 0, Size = 5)] + [Int32DataFieldDefinition(revision: 4, field: 9, Index = 0, Size = 5)] + [Int32DataFieldDefinition(revision: 999, field: 13, Index = 0, Size = 5)] + public int MaxTimeToCompleteJob { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 14, Index = 0, Size = 4)] + [Int32DataFieldDefinition(revision: 2, field: 14, Index = 0, Size = 4)] + [Int32DataFieldDefinition(revision: 3, field: 10, Index = 0, Size = 4)] + [Int32DataFieldDefinition(revision: 4, field: 10, Index = 0, Size = 4)] + [Int32DataFieldDefinition(revision: 999, field: 14, Index = 0, Size = 4)] + public int DisplayResultAtAutoSelect { get; set; } + + [BooleanDataFieldDefinition(revision: 1, field: 15, Index = 0)] + [BooleanDataFieldDefinition(revision: 2, field: 15, Index = 0)] + [BooleanDataFieldDefinition(revision: 3, field: 11, Index = 0)] + [BooleanDataFieldDefinition(revision: 4, field: 11, Index = 0)] + [BooleanDataFieldDefinition(revision: 999, field: 15, Index = 0)] + public bool UsingLineControl { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 16, Index = 0, Size = 1)] + [Int32DataFieldDefinition(revision: 2, field: 16, Index = 0, Size = 1)] + [Int32DataFieldDefinition(revision: 3, field: 12, Index = 0, Size = 1)] + [Int32DataFieldDefinition(revision: 4, field: 12, Index = 0, Size = 1)] + [Int32DataFieldDefinition(revision: 999, field: 16, Index = 0, Size = 1)] + public IdentifierPart IdentifierResultPart { get; set; } + + [BooleanDataFieldDefinition(revision: 1, field: 17, Index = 0)] + [BooleanDataFieldDefinition(revision: 2, field: 17, Index = 0)] + [BooleanDataFieldDefinition(revision: 3, field: 13, Index = 0)] + [BooleanDataFieldDefinition(revision: 4, field: 13, Index = 0)] + [BooleanDataFieldDefinition(revision: 999, field: 17, Index = 0)] + public bool ResultOfNonTightenings { get; set; } + + [BooleanDataFieldDefinition(revision: 1, field: 18, Index = 0)] + [BooleanDataFieldDefinition(revision: 2, field: 18, Index = 0)] + [BooleanDataFieldDefinition(revision: 3, field: 14, Index = 0)] + [BooleanDataFieldDefinition(revision: 4, field: 14, Index = 0)] + [BooleanDataFieldDefinition(revision: 999, field: 18, Index = 0)] + public bool ResetAllIdentifiersAtJobDone { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 19, Index = 0, Size = 1)] + [Int32DataFieldDefinition(revision: 2, field: 19, Index = 0, Size = 1)] + [Int32DataFieldDefinition(revision: 3, field: 15, Index = 0, Size = 1)] + [Int32DataFieldDefinition(revision: 4, field: 15, Index = 0, Size = 1)] + [Int32DataFieldDefinition(revision: 999, field: 19, Index = 0, Size = 1)] + public Reserved Reserved { get; set; } + + [Int32DataFieldDefinition(revision: 2, field: 20, Index = 0, Size = 5)] + [Int32DataFieldDefinition(revision: 3, field: 16, Index = 0, Size = 5)] + [Int32DataFieldDefinition(revision: 4, field: 16, Index = 0, Size = 5)] + public int JobSequenceNumber { get; set; } public Mid0140() : this(DEFAULT_REVISION) { @@ -137,174 +170,63 @@ public Mid0140(int revision) : this(new Header() { } + protected override string BuildHeader() { - var revision = Header.StandardizedRevision; - Header.Length = 20 + RevisionsByFields[revision].Sum(x=> x.Size + (x.HasPrefix ? 2 : 0)); + Header.Length = Header.DefaultSize; + if (RevisionsByFields.Any()) + { + var fields = DataFieldsByRevision(); + Header.Length += fields.Sum(x => x.TotalSize); + } + return Header.ToString(); } public override string Pack() { - var revision = Header.StandardizedRevision; - ResetDataFields(revision); - var jobListField = GetField(revision, DataFields.JobList); - jobListField.Size = JobList.Count * AdvancedJob.GetDefaultSize(revision); - AdjustDataFieldsIndexes(jobListField.Index + jobListField.Size + 2, revision); - jobListField.Value = PackJobList(revision); + var builder = new StringBuilder(); + var fields = DataFieldsByRevision().OrderBy(f => f.Index).ToList(); int prefixIndex = 1; - return string.Concat(BuildHeader(), base.Pack(revision, ref prefixIndex)); - } + builder.Append(BuildHeader()); + builder.Append(Pack(fields, ref prefixIndex)); - public override Mid Parse(ReadOnlySpan package) - { - Header = ProcessHeader(package); - int length = Header.Length; - var revision = Header.StandardizedRevision; - ResetDataFields(revision); - var numberOfParameterSetsField = RevisionsByFields[revision].First(x => x.Field == (int)DataFields.NumberOfParameterSets); - var numberOfParameterSets = int.Parse(package.Slice(numberOfParameterSetsField.Index + 2, numberOfParameterSetsField.Size).ToString()); - var jobListField = GetField(revision, DataFields.JobList); - jobListField.Size = numberOfParameterSets * AdvancedJob.GetDefaultSize(revision); - AdjustDataFieldsIndexes(jobListField.Index + jobListField.Size + 2, revision); - base.ProcessDataFields(revision, package); - JobList = AdvancedJob.ParseAll(jobListField.Value, revision).ToList(); - return this; + return builder.ToString(); } - protected virtual string PackJobList(int revision) + protected override void ProcessDataFields(ReadOnlySpan package) { - var advancedJobsList = new List(); - - foreach (var job in JobList) + foreach (var field in DataFieldsByRevision()) { - advancedJobsList.Add(job.Pack(revision)); + ProcessDataField(field, package); } - - return string.Join(";", advancedJobsList) + ";"; } - private void AdjustDataFieldsIndexes(int currentIndex, int revision) + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - foreach (var field in RevisionsByFields[revision]) + if (dataField.Field == 4) //JobList { - if (field.Field > (int)DataFields.JobList) - { - field.Index = currentIndex; - currentIndex += 2 + field.Size; - } + dataField.Size = NumberOfParameterSets * AdvancedJob.GetDefaultSize(Header.StandardizedRevision); } - - } - - private void ResetDataFields(int revision) - { - //TODO: think of a better approach in case user repeats packing or parsing - var fields = RevisionsByFields[revision].Where(x => x.Field > (int)DataFields.JobList); - foreach (var field in fields) - field.Index = 0; + base.ProcessDataField(dataField, package); } - protected override Dictionary> RegisterDatafields() + private IEnumerable DataFieldsByRevision() { - //All the workarounds are made due to inconsistent "additions" from protocol, since there are many fields moved - //to JobList/Parameter list, the DataFields start to shuffle every revision - return new Dictionary>() + var previousField = default(DataField); + foreach (var dataField in RevisionsByFields[Header.StandardizedRevision].OrderBy(x => x.Field)) + { + if (previousField != null && dataField.Index == 0) { - { - 1, new List() - { - DataField.Number(DataFields.JobId, 20, 4), - DataField.String(DataFields.JobName, 26, 25), - DataField.Number(DataFields.NumberOfParameterSets, 53, 2), - DataField.Volatile(DataFields.JobList, 57), - DataField.Number(DataFields.ForcedOrder, 0, 1), - DataField.Boolean(DataFields.LockAtJobDone, 0), - DataField.Number(DataFields.ToolLoosening, 0, 1), - DataField.Boolean(DataFields.RepeatJob, 0), - DataField.Number(DataFields.JobBatchDone, 0, 1), - DataField.Boolean(DataFields.BatchStatusAtIncrement, 0), - DataField.Boolean(DataFields.DecrementBatchAtOkLoosening, 0), - DataField.Number(DataFields.MaxTimeForFirstTightening, 0, 4), - DataField.Number(DataFields.MaxTimeToCompleteJob, 0, 5), - DataField.Number(DataFields.DisplayResultAtAutoSelect, 0, 4), - DataField.Boolean(DataFields.UseLineControl, 0), - DataField.Number(DataFields.IdentifierResultPart, 0, 1), - DataField.Boolean(DataFields.ResultOfNonTightenings, 0), - DataField.Boolean(DataFields.ResetAllIdentifiersAtJobDone, 0), - DataField.Number(DataFields.Reserved, 0, 1) - } - }, - { - 2, new List() - { - DataField.Number(DataFields.JobId, 20, 4), - DataField.String(DataFields.JobName, 26, 25), - DataField.Number(DataFields.NumberOfParameterSets, 53, 2), - DataField.Volatile(DataFields.JobList, 57), - DataField.Number(DataFields.ForcedOrder, 0, 1), - DataField.Boolean(DataFields.LockAtJobDone, 0), - DataField.Number(DataFields.ToolLoosening, 0, 1), - DataField.Boolean(DataFields.RepeatJob, 0), - DataField.Number(DataFields.JobBatchDone, 0, 1), - DataField.Boolean(DataFields.BatchStatusAtIncrement, 0), - DataField.Boolean(DataFields.DecrementBatchAtOkLoosening, 0), - DataField.Number(DataFields.MaxTimeForFirstTightening, 0, 4), - DataField.Number(DataFields.MaxTimeToCompleteJob, 0, 5), - DataField.Number(DataFields.DisplayResultAtAutoSelect, 0, 4), - DataField.Boolean(DataFields.UseLineControl, 0), - DataField.Number(DataFields.IdentifierResultPart, 0, 1), - DataField.Boolean(DataFields.ResultOfNonTightenings, 0), - DataField.Boolean(DataFields.ResetAllIdentifiersAtJobDone, 0), - DataField.Number(DataFields.Reserved, 0, 1), - DataField.Number(DataFields.JobSequenceNumber, 0, 5), - } - }, - { - 3, new List() - { - DataField.Number(DataFields.JobId, 20, 4), - DataField.String(DataFields.JobName, 26, 25), - DataField.Number(DataFields.NumberOfParameterSets, 53, 2), - DataField.Volatile(DataFields.JobList, 57), - DataField.Number(DataFields.ForcedOrder, 0, 1), - DataField.Boolean(DataFields.LockAtJobDone, 0), - DataField.Boolean(DataFields.RepeatJob, 0), - DataField.Number(DataFields.MaxTimeForFirstTightening, 0, 4), - DataField.Number(DataFields.MaxTimeToCompleteJob, 0, 5), - DataField.Number(DataFields.DisplayResultAtAutoSelect, 0, 4), - DataField.Boolean(DataFields.UseLineControl, 0), - DataField.Number(DataFields.IdentifierResultPart, 0, 1), - DataField.Boolean(DataFields.ResultOfNonTightenings, 0), - DataField.Boolean(DataFields.ResetAllIdentifiersAtJobDone, 0), - DataField.Number(DataFields.Reserved, 0, 1), - DataField.Number(DataFields.JobSequenceNumber, 0, 5) - } - }, - { - 4, new List() - { - DataField.Number(DataFields.JobId, 20, 4), - DataField.String(DataFields.JobName, 26, 25), - DataField.Number(DataFields.NumberOfParameterSets, 53, 2), - DataField.Volatile(DataFields.JobList, 57), - DataField.Number(DataFields.ForcedOrder, 0, 1), - DataField.Boolean(DataFields.LockAtJobDone, 0), - DataField.Boolean(DataFields.RepeatJob, 0), - DataField.Number(DataFields.MaxTimeForFirstTightening, 0, 4), - DataField.Number(DataFields.MaxTimeToCompleteJob, 0, 5), - DataField.Number(DataFields.DisplayResultAtAutoSelect, 0, 4), - DataField.Boolean(DataFields.UseLineControl, 0), - DataField.Number(DataFields.IdentifierResultPart, 0, 1), - DataField.Boolean(DataFields.ResultOfNonTightenings, 0), - DataField.Boolean(DataFields.ResetAllIdentifiersAtJobDone, 0), - DataField.Number(DataFields.Reserved, 0, 1), - DataField.Number(DataFields.JobSequenceNumber, 0, 5) - } - } - }; + dataField.Index = previousField.Index + previousField.TotalSize; + } + + previousField = dataField; + yield return dataField; + } } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { JobId, diff --git a/src/OpenProtocolInterpreter/MID.cs b/src/OpenProtocolInterpreter/MID.cs index 5c06fba2..3b686b50 100644 --- a/src/OpenProtocolInterpreter/MID.cs +++ b/src/OpenProtocolInterpreter/MID.cs @@ -121,11 +121,8 @@ protected virtual Dictionary> RegisterDatafields() { var result = new List(); var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(x => - { - return x.CustomAttributes.Any(a => a.AttributeType.IsAssignableTo(typeof(DataFieldDefinitionAttribute))); - }); - int fieldIndex = 20; + .Where(x => x.CustomAttributes.Any(a => typeof(DataFieldDefinitionAttribute).IsAssignableFrom(a.AttributeType))); + foreach (var prop in properties) { var attributes = prop.GetCustomAttributes(); @@ -134,10 +131,7 @@ protected virtual Dictionary> RegisterDatafields() foreach (var attr in attributes) { - fieldIndex = attr.Index > 0 ? attr.Index : fieldIndex; //enforced index if defined in attribute - attr.Index = fieldIndex; - result.Add(new DataFieldMetadata(fieldIndex, attr, prop)); - fieldIndex += attr.Size + (attr.HasPrefix ? 2 : 0); + result.Add(new DataFieldMetadata(attr.Index, attr, prop)); } } return result.ToArray(); diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0061.cs b/src/OpenProtocolInterpreter/Tightening/Mid0061.cs index 1f274da1..4cae027a 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0061.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0061.cs @@ -329,7 +329,7 @@ protected override string BuildHeader() { if (RevisionsByFields.Any()) { - Header.Length = 20; + Header.Length = Header.DefaultSize; if (Header.Revision == 998) { var stageResultField = GetField(nameof(StageResults)); From 33905753ef8f7441d59841963422ab1f1b8c0367 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Mon, 13 Jul 2026 20:26:28 -0300 Subject: [PATCH 41/78] Updated LinkCommunication mids to use datafields attribute --- .../LinkCommunication/Mid9997.cs | 24 +++---------- .../LinkCommunication/Mid9998.cs | 34 +++++-------------- 2 files changed, 13 insertions(+), 45 deletions(-) diff --git a/src/OpenProtocolInterpreter/LinkCommunication/Mid9997.cs b/src/OpenProtocolInterpreter/LinkCommunication/Mid9997.cs index 61b8a32a..8d776116 100644 --- a/src/OpenProtocolInterpreter/LinkCommunication/Mid9997.cs +++ b/src/OpenProtocolInterpreter/LinkCommunication/Mid9997.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.LinkCommunication { @@ -18,11 +19,8 @@ public class Mid9997 : Mid, ILinkCommunication, IIntegrator, IController { public const int MID = 9997; - public int MidNumber - { - get => GetField(1, DataFields.MidNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.MidNumber).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 4, HasPrefix = false)] + public int MidNumber { get; set; } public Mid9997(Header header) : base(header) { @@ -36,19 +34,7 @@ public Mid9997() : this(new Header() { } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.MidNumber, 20, 4, false) - } - } - }; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { MidNumber diff --git a/src/OpenProtocolInterpreter/LinkCommunication/Mid9998.cs b/src/OpenProtocolInterpreter/LinkCommunication/Mid9998.cs index 372e133a..abda77a7 100644 --- a/src/OpenProtocolInterpreter/LinkCommunication/Mid9998.cs +++ b/src/OpenProtocolInterpreter/LinkCommunication/Mid9998.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.LinkCommunication { @@ -18,16 +19,10 @@ public class Mid9998 : Mid, ILinkCommunication, IController, IIntegrator { public const int MID = 9998; - public int MidNumber - { - get => GetField(1, DataFields.MidNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.MidNumber).SetValue(OpenProtocolConvert.ToString, value); - } - public LinkCommunicationError ErrorCode - { - get => (LinkCommunicationError)GetField(1, DataFields.ErrorCode).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.ErrorCode).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 4, HasPrefix = false)] + public int MidNumber { get; set; } + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 24, Size = 4, HasPrefix = false)] + public LinkCommunicationError ErrorCode { get; set; } public Mid9998() : this(new Header() { @@ -35,27 +30,14 @@ public Mid9998() : this(new Header() Revision = DEFAULT_REVISION }) { - - } - public Mid9998(Header header) : base(header) - { } - protected override Dictionary> RegisterDatafields() + public Mid9998(Header header) : base(header) { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.MidNumber, 20, 4, false), - DataField.Number(DataFields.ErrorCode, 24, 4, false) - } - } - }; } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { MidNumber, From 215aafb168ed3dc4c9316d27f261b6cc8aebb945 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Mon, 13 Jul 2026 20:28:08 -0300 Subject: [PATCH 42/78] Updated MotorTuning mids to use datafield attribute --- .../MotorTuning/Mid0501.cs | 24 ++++--------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/src/OpenProtocolInterpreter/MotorTuning/Mid0501.cs b/src/OpenProtocolInterpreter/MotorTuning/Mid0501.cs index 74a958c8..702ddb3d 100644 --- a/src/OpenProtocolInterpreter/MotorTuning/Mid0501.cs +++ b/src/OpenProtocolInterpreter/MotorTuning/Mid0501.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.MotorTuning { @@ -16,11 +17,8 @@ public class Mid0501 : Mid, IMotorTuning, IController, IAcknowledgeable /// Motor Tune Failed = false (0) /// Motor Tune Success = true (1) /// - public bool MotorTuneResult - { - get => GetField(1, DataFields.MotorTuneResult).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.MotorTuneResult).SetValue(OpenProtocolConvert.ToString, value); - } + [BooleanDataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 1)] + public bool MotorTuneResult { get; set; } public Mid0501() : this(new Header() { @@ -34,19 +32,7 @@ public Mid0501(Header header) : base(header) { } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Boolean(DataFields.MotorTuneResult, 20) - } - } - }; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { MotorTuneResult From 499657849bb0ace1fdaa7257aadb27f5e6f03d2b Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Tue, 14 Jul 2026 23:00:34 -0300 Subject: [PATCH 43/78] updated multiple identifiers to use datafields attributes --- .../MultipleIdentifiers/TestMid0151.cs | 33 +++++++++++++ .../MultipleIdentifiers/IdentifierStatus.cs | 25 ++++++++++ .../MultipleIdentifiers/Mid0150.cs | 44 +++++++----------- .../MultipleIdentifiers/Mid0152.cs | 46 +++---------------- 4 files changed, 82 insertions(+), 66 deletions(-) create mode 100644 src/MIDTesters.Core/MultipleIdentifiers/TestMid0151.cs diff --git a/src/MIDTesters.Core/MultipleIdentifiers/TestMid0151.cs b/src/MIDTesters.Core/MultipleIdentifiers/TestMid0151.cs new file mode 100644 index 00000000..6153e7aa --- /dev/null +++ b/src/MIDTesters.Core/MultipleIdentifiers/TestMid0151.cs @@ -0,0 +1,33 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter.MultipleIdentifiers; + +namespace MIDTesters.MultipleIdentifiers +{ + [TestClass] + [TestCategory("MultipleIdentifiers")] + public class TestMid0151 : DefaultMidTests + { + [TestMethod] + [TestCategory("Revision 1"), TestCategory("ASCII")] + public void Mid0151Revision1() + { + string package = "00200151001 "; + var mid = _midInterpreter.Parse(package); + + Assert.AreEqual(typeof(Mid0151), mid.GetType()); + AssertEqualPackages(package, mid); + } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("ByteArray")] + public void Mid0151ByteRevision1() + { + string package = "00200151001 "; + byte[] bytes = GetAsciiBytes(package); + var mid = _midInterpreter.Parse(bytes); + + Assert.AreEqual(typeof(Mid0151), mid.GetType()); + AssertEqualPackages(bytes, mid); + } + } +} diff --git a/src/OpenProtocolInterpreter/MultipleIdentifiers/IdentifierStatus.cs b/src/OpenProtocolInterpreter/MultipleIdentifiers/IdentifierStatus.cs index 5b9c9e0c..25bfd8c6 100644 --- a/src/OpenProtocolInterpreter/MultipleIdentifiers/IdentifierStatus.cs +++ b/src/OpenProtocolInterpreter/MultipleIdentifiers/IdentifierStatus.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; namespace OpenProtocolInterpreter.MultipleIdentifiers { @@ -50,4 +51,28 @@ public static IdentifierStatus Parse(ReadOnlySpan section) }; } } + + public class IdentifierStatusDefinitionAttribute : DataFieldDefinitionAttribute + { + public IdentifierStatusDefinitionAttribute(int revision) : base(revision) + { + + } + public IdentifierStatusDefinitionAttribute(int field, int revision) : base(field, revision) + { + + } + + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + { + return new DataField(Field, index, Size, HasPrefix) + { + DefaultConverter = Pack, + DefaultParser = IdentifierStatus.Parse + }.Bind(mid, propertyInfo); + } + + private static string Pack(char paddingChar, int size, PaddingOrientation orientation, IdentifierStatus identifierStatus) + => identifierStatus.Pack(); + } } diff --git a/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0150.cs b/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0150.cs index 477e5e0c..79cdb72b 100644 --- a/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0150.cs +++ b/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0150.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace OpenProtocolInterpreter.MultipleIdentifiers { @@ -7,7 +7,7 @@ namespace OpenProtocolInterpreter.MultipleIdentifiers /// Identifier download request /// Used by the integrator to send an identifier to the controller. /// Message sent by: Integrator - ///Answer: Command accepted or Command error, Identifier input source not granted + ///Answer: Command accepted or Command error, Identifier input source not granted /// public class Mid0150 : Mid, IMultipleIdentifier, IIntegrator, IAcceptableCommand, IDeclinableCommand { @@ -15,57 +15,47 @@ public class Mid0150 : Mid, IMultipleIdentifier, IIntegrator, IAcceptableCommand public IEnumerable DocumentedPossibleErrors => new Error[] { Error.IdentifierInputSourceNotGranted }; - public string IdentifierData - { - get => GetField(1, DataFields.IdentifierData).Value; - set => GetField(1, DataFields.IdentifierData).SetValue(value); - } + [StringDataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 0, HasPrefix = false)] + public string IdentifierData { get; set; } public Mid0150() : this(new Header() { Mid = MID, Revision = DEFAULT_REVISION - }) - { + }) + { } public Mid0150(Header header) : base(header) { } + protected override string BuildHeader() + { + + Header.Length = Header.DefaultSize + IdentifierData?.Length ?? 0; + return Header.ToString(); + } + public override string Pack() { - var identifierDataField = GetField(1, DataFields.IdentifierData); - if(identifierDataField.Value.Length > 100) + if (IdentifierData?.Length > 100) { - identifierDataField.Value = identifierDataField.Value.Substring(0, 100); + IdentifierData = IdentifierData.Substring(0, 100); } - identifierDataField.Size = identifierDataField.Value.Length; return base.Pack(); } public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); - GetField(1, DataFields.IdentifierData).Size = Header.Length - 20; + GetField(nameof(IdentifierData)).Size = Header.Length - Header.DefaultSize; ProcessDataFields(package); return this; } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Volatile(DataFields.IdentifierData, 20, false) - } - } - }; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { IdentifierData diff --git a/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0152.cs b/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0152.cs index cc54846f..63c07b25 100644 --- a/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0152.cs +++ b/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0152.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace OpenProtocolInterpreter.MultipleIdentifiers { @@ -10,7 +10,7 @@ namespace OpenProtocolInterpreter.MultipleIdentifiers /// by the controller to the subscriber. /// /// - /// The identifier contains the status of the maximum four identifier result parts that could + /// The identifier contains the status of the maximum four identifier result parts that could /// be extracted from one or more valid identifiers. /// /// Message sent by: Controller @@ -20,9 +20,13 @@ public class Mid0152 : Mid, IMultipleIdentifier, IController, IAcknowledgeable package) - { - base.Parse(package); - - FirstIdentifierStatus = IdentifierStatus.Parse(GetField(1, DataFields.FirstIdentifierStatus).Value); - SecondIdentifierStatus = IdentifierStatus.Parse(GetField(1, DataFields.SecondIdentifierStatus).Value); - ThirdIdentifierStatus = IdentifierStatus.Parse(GetField(1, DataFields.ThirdIdentifierStatus).Value); - FourthIdentifierStatus = IdentifierStatus.Parse(GetField(1, DataFields.FourthIdentifierStatus).Value); - return this; - } - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - new(DataFields.FirstIdentifierStatus, 20, 30), - new(DataFields.SecondIdentifierStatus, 52, 30), - new(DataFields.ThirdIdentifierStatus, 84, 30), - new(DataFields.FourthIdentifierStatus, 116, 30) - } - } - }; } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { FirstIdentifierStatus, From ad6badb587b5f2bbfea5ebcdde22b2ea53c9b5f1 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Tue, 14 Jul 2026 23:55:27 -0300 Subject: [PATCH 44/78] Updated multispindle mids to use datafield attributes --- .../MultiSpindle/TestMid0101.cs | 48 ++-- .../MultiSpindle/TestMid0104.cs | 2 +- .../MultiSpindle/Mid0091.cs | 90 ++----- .../MultiSpindle/Mid0100.cs | 57 ++-- .../MultiSpindle/Mid0101.cs | 254 +++++------------- .../MultiSpindle/Mid0104.cs | 23 +- .../MultiSpindle/SpindleOrPressStatus.cs | 84 +++++- .../MultiSpindle/SpindleStatus.cs | 76 +++++- .../MultipleIdentifiers/Mid0150.cs | 1 - .../OpenProtocolConvert.cs | 13 +- 10 files changed, 299 insertions(+), 349 deletions(-) diff --git a/src/MIDTesters.Core/MultiSpindle/TestMid0101.cs b/src/MIDTesters.Core/MultiSpindle/TestMid0101.cs index f0a843f6..4454df2e 100644 --- a/src/MIDTesters.Core/MultiSpindle/TestMid0101.cs +++ b/src/MIDTesters.Core/MultiSpindle/TestMid0101.cs @@ -13,11 +13,11 @@ public class TestMid0101 : DefaultMidTests [TestCategory("Revision 1"), TestCategory("Revision 2"), TestCategory("Revision 3"), TestCategory("ASCII")] public void Mid0101Revisions1To3() { - var pack = "01920101001 010102BM3GA02111900601 030304003050001060001071080006800900092010000800110000012000151300000142019-11-14:14:08:05152019-11-25:11:22:41160091317118010111000809100000"; + var pack = "02100101001 010202BM3GA02111900601 030304003050001060001071080006800900092010000800110000012000151300000142019-11-14:14:08:05152019-11-25:11:22:41160091317118010111000809100000020111000809100000"; var mid = _midInterpreter.Parse(pack); Assert.AreEqual(typeof(Mid0101), mid.GetType()); - Assert.AreEqual(1, mid.NumberOfSpindlesOrPresses); + Assert.AreEqual(2, mid.NumberOfSpindlesOrPresses); Assert.AreEqual("BM3GA02111900601", mid.VinNumber.TrimEnd()); Assert.AreEqual(3, mid.JobId); Assert.AreEqual(3, mid.ParameterSetId); @@ -34,7 +34,7 @@ public void Mid0101Revisions1To3() Assert.AreEqual(new DateTime(2019, 11, 25, 11, 22, 41), mid.TimeStamp); Assert.AreEqual(913, mid.SyncTighteningId); Assert.IsTrue(mid.SyncOverallStatus); - Assert.AreEqual(1, mid.SpindlesOrPressesStatus.Count); + Assert.AreEqual(2, mid.SpindlesOrPressesStatus.Count); AssertEqualPackages(pack, mid); } @@ -42,12 +42,12 @@ public void Mid0101Revisions1To3() [TestCategory("Revision 1"), TestCategory("Revision 2"), TestCategory("Revision 3"), TestCategory("ByteArray")] public void Mid0101ByteRevisions1To3() { - string package = "01920101001 010102BM3GA02111900601 030304003050001060001071080006800900092010000800110000012000151300000142019-11-14:14:08:05152019-11-25:11:22:41160091317118010111000809100000"; + string package = "02100101001 010202BM3GA02111900601 030304003050001060001071080006800900092010000800110000012000151300000142019-11-14:14:08:05152019-11-25:11:22:41160091317118010111000809100000020111000809100000"; byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); Assert.AreEqual(typeof(Mid0101), mid.GetType()); - Assert.AreEqual(1, mid.NumberOfSpindlesOrPresses); + Assert.AreEqual(2, mid.NumberOfSpindlesOrPresses); Assert.AreEqual("BM3GA02111900601", mid.VinNumber.TrimEnd()); Assert.AreEqual(3, mid.JobId); Assert.AreEqual(3, mid.ParameterSetId); @@ -64,7 +64,7 @@ public void Mid0101ByteRevisions1To3() Assert.AreEqual(new DateTime(2019, 11, 25, 11, 22, 41), mid.TimeStamp); Assert.AreEqual(913, mid.SyncTighteningId); Assert.IsTrue(mid.SyncOverallStatus); - Assert.AreEqual(1, mid.SpindlesOrPressesStatus.Count); + Assert.AreEqual(2, mid.SpindlesOrPressesStatus.Count); AssertEqualPackages(bytes, mid); } @@ -72,11 +72,11 @@ public void Mid0101ByteRevisions1To3() [TestCategory("Revision 4"), TestCategory("ASCII")] public void Mid0101Revision4() { - var pack = "01970101004 010102BM3GA02111900601 030304003050001060001071080006800900092010000800110000012000151300000142019-11-14:14:08:05152019-11-25:11:22:4116009131711801011100080910000019002"; + var pack = "02150101004 010202BM3GA02111900601 030304003050001060001071080006800900092010000800110000012000151300000142019-11-14:14:08:05152019-11-25:11:22:4116009131711801011100080910000002011100080910000019002"; var mid = _midInterpreter.Parse(pack); Assert.AreEqual(typeof(Mid0101), mid.GetType()); - Assert.AreEqual(1, mid.NumberOfSpindlesOrPresses); + Assert.AreEqual(2, mid.NumberOfSpindlesOrPresses); Assert.AreEqual("BM3GA02111900601", mid.VinNumber.TrimEnd()); Assert.AreEqual(3, mid.JobId); Assert.AreEqual(3, mid.ParameterSetId); @@ -93,7 +93,7 @@ public void Mid0101Revision4() Assert.AreEqual(new DateTime(2019, 11, 25, 11, 22, 41), mid.TimeStamp); Assert.AreEqual(913, mid.SyncTighteningId); Assert.IsTrue(mid.SyncOverallStatus); - Assert.AreEqual(1, mid.SpindlesOrPressesStatus.Count); + Assert.AreEqual(2, mid.SpindlesOrPressesStatus.Count); Assert.AreEqual(SystemSubType.SystemRunningPresses, mid.SystemSubType); AssertEqualPackages(pack, mid); } @@ -102,12 +102,12 @@ public void Mid0101Revision4() [TestCategory("Revision 4"), TestCategory("ByteArray")] public void Mid0101ByteRevision4() { - var package = "01970101004 010102BM3GA02111900601 030304003050001060001071080006800900092010000800110000012000151300000142019-11-14:14:08:05152019-11-25:11:22:4116009131711801011100080910000019002"; + var package = "02150101004 010202BM3GA02111900601 030304003050001060001071080006800900092010000800110000012000151300000142019-11-14:14:08:05152019-11-25:11:22:4116009131711801011100080910000002011100080910000019002"; byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); Assert.AreEqual(typeof(Mid0101), mid.GetType()); - Assert.AreEqual(1, mid.NumberOfSpindlesOrPresses); + Assert.AreEqual(2, mid.NumberOfSpindlesOrPresses); Assert.AreEqual("BM3GA02111900601", mid.VinNumber.TrimEnd()); Assert.AreEqual(3, mid.JobId); Assert.AreEqual(3, mid.ParameterSetId); @@ -124,7 +124,7 @@ public void Mid0101ByteRevision4() Assert.AreEqual(new DateTime(2019, 11, 25, 11, 22, 41), mid.TimeStamp); Assert.AreEqual(913, mid.SyncTighteningId); Assert.IsTrue(mid.SyncOverallStatus); - Assert.AreEqual(1, mid.SpindlesOrPressesStatus.Count); + Assert.AreEqual(2, mid.SpindlesOrPressesStatus.Count); Assert.AreEqual(SystemSubType.SystemRunningPresses, mid.SystemSubType); AssertEqualPackages(bytes, mid); } @@ -133,11 +133,11 @@ public void Mid0101ByteRevision4() [TestCategory("Revision 5"), TestCategory("ASCII")] public void Mid0101Revision5() { - var pack = "02040101005 010102BM3GA02111900601 030304003050001060001071080006800900092010000800110000012000151300000142019-11-14:14:08:05152019-11-25:11:22:41160091317118010111000809100000190022000021"; + var pack = "02220101005 010202BM3GA02111900601 030304003050001060001071080006800900092010000800110000012000151300000142019-11-14:14:08:05152019-11-25:11:22:41160091317118010111000809100000020111000809100000190022000021"; var mid = _midInterpreter.Parse(pack); Assert.AreEqual(typeof(Mid0101), mid.GetType()); - Assert.AreEqual(1, mid.NumberOfSpindlesOrPresses); + Assert.AreEqual(2, mid.NumberOfSpindlesOrPresses); Assert.AreEqual("BM3GA02111900601", mid.VinNumber.TrimEnd()); Assert.AreEqual(3, mid.JobId); Assert.AreEqual(3, mid.ParameterSetId); @@ -154,7 +154,7 @@ public void Mid0101Revision5() Assert.AreEqual(new DateTime(2019, 11, 25, 11, 22, 41), mid.TimeStamp); Assert.AreEqual(913, mid.SyncTighteningId); Assert.IsTrue(mid.SyncOverallStatus); - Assert.AreEqual(1, mid.SpindlesOrPressesStatus.Count); + Assert.AreEqual(2, mid.SpindlesOrPressesStatus.Count); Assert.AreEqual(SystemSubType.SystemRunningPresses, mid.SystemSubType); Assert.AreEqual(21, mid.JobSequenceNumber); AssertEqualPackages(pack, mid); @@ -164,12 +164,12 @@ public void Mid0101Revision5() [TestCategory("Revision 5"), TestCategory("ByteArray")] public void Mid0101ByteRevision5() { - var package = "02040101005 010102BM3GA02111900601 030304003050001060001071080006800900092010000800110000012000151300000142019-11-14:14:08:05152019-11-25:11:22:41160091317118010111000809100000190022000021"; + var package = "02220101005 010202BM3GA02111900601 030304003050001060001071080006800900092010000800110000012000151300000142019-11-14:14:08:05152019-11-25:11:22:41160091317118010111000809100000020111000809100000190022000021"; byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); Assert.AreEqual(typeof(Mid0101), mid.GetType()); - Assert.AreEqual(1, mid.NumberOfSpindlesOrPresses); + Assert.AreEqual(2, mid.NumberOfSpindlesOrPresses); Assert.AreEqual("BM3GA02111900601", mid.VinNumber.TrimEnd()); Assert.AreEqual(3, mid.JobId); Assert.AreEqual(3, mid.ParameterSetId); @@ -186,7 +186,7 @@ public void Mid0101ByteRevision5() Assert.AreEqual(new DateTime(2019, 11, 25, 11, 22, 41), mid.TimeStamp); Assert.AreEqual(913, mid.SyncTighteningId); Assert.IsTrue(mid.SyncOverallStatus); - Assert.AreEqual(1, mid.SpindlesOrPressesStatus.Count); + Assert.AreEqual(2, mid.SpindlesOrPressesStatus.Count); Assert.AreEqual(SystemSubType.SystemRunningPresses, mid.SystemSubType); Assert.AreEqual(21, mid.JobSequenceNumber); AssertEqualPackages(bytes, mid); @@ -196,11 +196,11 @@ public void Mid0101ByteRevision5() [TestCategory("Revision 6"), TestCategory("ASCII")] public void Mid0101Revision6() { - var pack = "02040101006 010102BM3GA02111900601 030304003050001060001071080006800900092010000800110000012-00151300000142019-11-14:14:08:05152019-11-25:11:22:41160091317118010111000809100000190022000021"; + var pack = "02220101006 010202BM3GA02111900601 030304003050001060001071080006800900092010000800110000012-00151300000142019-11-14:14:08:05152019-11-25:11:22:41160091317118010111000809100000020111000809100000190022000021"; var mid = _midInterpreter.Parse(pack); Assert.AreEqual(typeof(Mid0101), mid.GetType()); - Assert.AreEqual(1, mid.NumberOfSpindlesOrPresses); + Assert.AreEqual(2, mid.NumberOfSpindlesOrPresses); Assert.AreEqual("BM3GA02111900601", mid.VinNumber.TrimEnd()); Assert.AreEqual(3, mid.JobId); Assert.AreEqual(3, mid.ParameterSetId); @@ -217,7 +217,7 @@ public void Mid0101Revision6() Assert.AreEqual(new DateTime(2019, 11, 25, 11, 22, 41), mid.TimeStamp); Assert.AreEqual(913, mid.SyncTighteningId); Assert.IsTrue(mid.SyncOverallStatus); - Assert.AreEqual(1, mid.SpindlesOrPressesStatus.Count); + Assert.AreEqual(2, mid.SpindlesOrPressesStatus.Count); Assert.AreEqual(SystemSubType.SystemRunningPresses, mid.SystemSubType); Assert.AreEqual(21, mid.JobSequenceNumber); AssertEqualPackages(pack, mid); @@ -227,12 +227,12 @@ public void Mid0101Revision6() [TestCategory("Revision 6"), TestCategory("ByteArray")] public void Mid0101ByteRevision6() { - var package = "02040101006 010102BM3GA02111900601 030304003050001060001071080006800900092010000800110000012-00151300000142019-11-14:14:08:05152019-11-25:11:22:41160091317118010111000809100000190022000021"; + var package = "02220101006 010202BM3GA02111900601 030304003050001060001071080006800900092010000800110000012-00151300000142019-11-14:14:08:05152019-11-25:11:22:41160091317118010111000809100000020111000809100000190022000021"; byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); Assert.AreEqual(typeof(Mid0101), mid.GetType()); - Assert.AreEqual(1, mid.NumberOfSpindlesOrPresses); + Assert.AreEqual(2, mid.NumberOfSpindlesOrPresses); Assert.AreEqual("BM3GA02111900601", mid.VinNumber.TrimEnd()); Assert.AreEqual(3, mid.JobId); Assert.AreEqual(3, mid.ParameterSetId); @@ -249,7 +249,7 @@ public void Mid0101ByteRevision6() Assert.AreEqual(new DateTime(2019, 11, 25, 11, 22, 41), mid.TimeStamp); Assert.AreEqual(913, mid.SyncTighteningId); Assert.IsTrue(mid.SyncOverallStatus); - Assert.AreEqual(1, mid.SpindlesOrPressesStatus.Count); + Assert.AreEqual(2, mid.SpindlesOrPressesStatus.Count); Assert.AreEqual(SystemSubType.SystemRunningPresses, mid.SystemSubType); Assert.AreEqual(21, mid.JobSequenceNumber); AssertEqualPackages(bytes, mid); diff --git a/src/MIDTesters.Core/MultiSpindle/TestMid0104.cs b/src/MIDTesters.Core/MultiSpindle/TestMid0104.cs index 123c3b2d..06e2a21e 100644 --- a/src/MIDTesters.Core/MultiSpindle/TestMid0104.cs +++ b/src/MIDTesters.Core/MultiSpindle/TestMid0104.cs @@ -1,7 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter.MultiSpindle; -namespace MIDTesters.Core.MultiSpindle +namespace MIDTesters.MultiSpindle { [TestClass] [TestCategory("MultiSpindle")] diff --git a/src/OpenProtocolInterpreter/MultiSpindle/Mid0091.cs b/src/OpenProtocolInterpreter/MultiSpindle/Mid0091.cs index e4d663be..1503798f 100644 --- a/src/OpenProtocolInterpreter/MultiSpindle/Mid0091.cs +++ b/src/OpenProtocolInterpreter/MultiSpindle/Mid0091.cs @@ -17,26 +17,15 @@ public class Mid0091 : Mid, IMultiSpindle, IController, IAcknowledgeable GetField(1, DataFields.NumberOfSpindles).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.NumberOfSpindles).SetValue(OpenProtocolConvert.ToString, value); - } - public int SyncTighteningId - { - get => GetField(1, DataFields.SyncTighteningId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.SyncTighteningId).SetValue(OpenProtocolConvert.ToString, value); - } - public DateTime Time - { - get => GetField(1, DataFields.Time).GetValue(OpenProtocolConvert.ToDateTime); - set => GetField(1, DataFields.Time).SetValue(OpenProtocolConvert.ToString, value); - } - public bool SyncOverallStatus - { - get => GetField(1, DataFields.SyncOverallStatus).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.SyncOverallStatus).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 2)] + public int NumberOfSpindles { get; set; } + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 24, Size = 5)] + public int SyncTighteningId { get; set; } + [TimestampDataFieldDefinition(revision: 1, field: 3, Index = 31)] + public DateTime Time { get; set; } + [BooleanDataFieldDefinition(revision: 1, field: 4, Index = 52)] + public bool SyncOverallStatus { get; set; } + [SpindleStatusCollectionDefinition(revision: 1, field: 5, Index = 55, Size = 5)] public List SpindlesStatus { get; set; } public Mid0091() : this(new Header() @@ -53,69 +42,24 @@ public Mid0091(Header header) : base(header) SpindlesStatus ??= []; } + public override string Pack() { - GetField(1, DataFields.SpindleStatus).Value = PackSpindlesStatus(); + NumberOfSpindles = SpindlesStatus?.Count ?? 0; //Enforce the number of spindles to match the list count + GetField(nameof(SpindlesStatus)).Size = NumberOfSpindles * SpindleStatus.DefaultSize; return base.Pack(); } - public override Mid Parse(ReadOnlySpan package) - { - Header = ProcessHeader(package); - var spindleField = GetField(1, DataFields.SpindleStatus); - spindleField.Size = Header.Length - spindleField.Index - 2; - base.Parse(package); - SpindlesStatus = ParseSpindlesStatus(spindleField.Value); - return this; - } - - //TODO: move to SpindleStatus class - protected virtual string PackSpindlesStatus() - { - var builder = new StringBuilder(); - foreach (var spindle in SpindlesStatus) - builder.Append(OpenProtocolConvert.ToString('0', 2, PaddingOrientation.LeftPadded, spindle.SpindleNumber) + - OpenProtocolConvert.ToString('0', 2, PaddingOrientation.LeftPadded, spindle.ChannelId) + - OpenProtocolConvert.ToString(spindle.SyncOverallStatus)); - - return builder.ToString(); - } - - protected virtual List ParseSpindlesStatus(string section) + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - var list = new List(); - for (int i = 0; i < section.Length; i += 5) + if (dataField.Field == 5) { - var obj = new SpindleStatus() - { - SpindleNumber = OpenProtocolConvert.ToInt32(section.Substring(i, 2)), - ChannelId = OpenProtocolConvert.ToInt32(section.Substring(i + 2, 2)), - SyncOverallStatus = OpenProtocolConvert.ToBoolean(section.Substring(i + 4, 1)) - }; - - list.Add(obj); + dataField.Size = NumberOfSpindles * SpindleStatus.DefaultSize; } - - return list; - } - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.NumberOfSpindles, 20, 2), - DataField.Number(DataFields.SyncTighteningId, 24, 5), - DataField.Timestamp(DataFields.Time, 31), - DataField.Boolean(DataFields.SyncOverallStatus, 52), - new(DataFields.SpindleStatus, 55, 5) - } - } - }; + base.ProcessDataField(dataField, package); } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { NumberOfSpindles, diff --git a/src/OpenProtocolInterpreter/MultiSpindle/Mid0100.cs b/src/OpenProtocolInterpreter/MultiSpindle/Mid0100.cs index b4d76698..687b0ce2 100644 --- a/src/OpenProtocolInterpreter/MultiSpindle/Mid0100.cs +++ b/src/OpenProtocolInterpreter/MultiSpindle/Mid0100.cs @@ -1,23 +1,24 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.MultiSpindle { /// /// Multi-spindle result subscribe /// - /// A subscription for the multi-spindle status. For Power Focus, the subscription must - /// be addressed to a sync Master. - /// + /// A subscription for the multi-spindle status. For Power Focus, the subscription must + /// be addressed to a sync Master. + /// /// - /// This telegram is also used for a PowerMACS 4000 system - /// running a press instead of a spindle. A press system only supports revision 4 and higher - /// of the telegram and will answer with , MID revision unsupported if a subscription + /// This telegram is also used for a PowerMACS 4000 system + /// running a press instead of a spindle. A press system only supports revision 4 and higher + /// of the telegram and will answer with , MID revision unsupported if a subscription /// is made with a lower revision. /// /// Message sent by: Integrator /// - /// Answer: Command accepted or - /// Command error, Controller is not a sync master/station controller, + /// Answer: Command accepted or + /// Command error, Controller is not a sync master/station controller, /// Multi-spindle result subscription already exists or MID revision unsupported /// /// @@ -25,24 +26,18 @@ public class Mid0100 : Mid, IMultiSpindle, IIntegrator, ISubscription, IAcceptab { public const int MID = 100; - public IEnumerable DocumentedPossibleErrors => new Error[] - { + public IEnumerable DocumentedPossibleErrors => new Error[] + { Error.ControllerIsNotASyncMasterOrStationController, Error.MultiSpindleResultSubscriptionAlreadyExists, Error.MidRevisionUnsupported }; - public long DataNumberSystem - { - get => GetField(2, DataFields.DataNumberSystem).GetValue(OpenProtocolConvert.ToInt64); - set => GetField(2, DataFields.DataNumberSystem).SetValue(OpenProtocolConvert.ToString, value); - } + [Int64DataFieldDefinition(revision: 2, field: 1, Index = 20, Size = 10, HasPrefix = false)] + public long DataNumberSystem { get; set; } - public bool SendOnlyNewData - { - get => GetField(3, DataFields.SendOnlyNewData).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(3, DataFields.SendOnlyNewData).SetValue(OpenProtocolConvert.ToString, value); - } + [BooleanDataFieldDefinition(revision: 3, field: 2, Index = 30, HasPrefix = false)] + public bool SendOnlyNewData { get; set; } public Mid0100() : this(DEFAULT_REVISION) { @@ -67,25 +62,7 @@ public Mid0100(int revision, bool noAckFlag = false) : this(new Header() { } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 2, new List() - { - DataField.Number(DataFields.DataNumberSystem, 20, 10, false), - } - }, - { - 3, new List() - { - DataField.Boolean(DataFields.SendOnlyNewData, 30, false), - } - } - }; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { DataNumberSystem, diff --git a/src/OpenProtocolInterpreter/MultiSpindle/Mid0101.cs b/src/OpenProtocolInterpreter/MultiSpindle/Mid0101.cs index 1476a961..0dbac0af 100644 --- a/src/OpenProtocolInterpreter/MultiSpindle/Mid0101.cs +++ b/src/OpenProtocolInterpreter/MultiSpindle/Mid0101.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; namespace OpenProtocolInterpreter.MultiSpindle { @@ -15,7 +16,7 @@ namespace OpenProtocolInterpreter.MultiSpindle /// exactly the same but some of the fields have slightly different definitions.The fields for Torque are /// used for Force values and the fields for Angle are used for Stroke values. A press system always uses /// revision 4 or higher of the telegram. - /// + /// /// Message sent by: Controller /// Answer: Multi-spindle result acknowledge /// @@ -23,102 +24,46 @@ public class Mid0101 : Mid, IMultiSpindle, IController, IAcknowledgeable GetField(1, DataFields.NumberOfSpindlesOrPresses).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.NumberOfSpindlesOrPresses).SetValue(OpenProtocolConvert.ToString, value); - } - public string VinNumber - { - get => GetField(1, DataFields.VinNumber).Value; - set => GetField(1, DataFields.VinNumber).SetValue(value); - } - public int JobId - { - get => GetField(1, DataFields.JobId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.JobId).SetValue(OpenProtocolConvert.ToString, value); - } - public int ParameterSetId - { - get => GetField(1, DataFields.ParameterSetId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.ParameterSetId).SetValue(OpenProtocolConvert.ToString, value); - } - public int BatchSize - { - get => GetField(1, DataFields.BatchSize).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.BatchSize).SetValue(OpenProtocolConvert.ToString, value); - } - public int BatchCounter - { - get => GetField(1, DataFields.BatchCounter).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.BatchCounter).SetValue(OpenProtocolConvert.ToString, value); - } - public BatchStatus BatchStatus - { - get => (BatchStatus)GetField(1, DataFields.BatchStatus).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.BatchStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public decimal TorqueOrForceMinLimit - { - get => GetField(1, DataFields.TorqueOrForceMinLimit).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(1, DataFields.TorqueOrForceMinLimit).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal TorqueOrForceMaxLimit - { - get => GetField(1, DataFields.TorqueOrForceMaxLimit).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(1, DataFields.TorqueOrForceMaxLimit).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal TorqueOrForceFinalTarget - { - get => GetField(1, DataFields.TorqueOrForceFinalTarget).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(1, DataFields.TorqueOrForceFinalTarget).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal AngleOrStrokeMinLimit - { - get => GetField(1, DataFields.AngleOrStrokeMinLimit).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(1, DataFields.AngleOrStrokeMinLimit).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal AngleOrStrokeMaxLimit - { - get => GetField(1, DataFields.AngleOrStrokeMaxLimit).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(1, DataFields.AngleOrStrokeMaxLimit).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal FinalAngleOrStrokeTarget - { - get => GetField(1, DataFields.FinalAngleOrStrokeTarget).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(1, DataFields.FinalAngleOrStrokeTarget).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public DateTime LastChangeInParameterSet - { - get => GetField(1, DataFields.LastChangeInParameterSet).GetValue(OpenProtocolConvert.ToDateTime); - set => GetField(1, DataFields.LastChangeInParameterSet).SetValue(OpenProtocolConvert.ToString, value); - } - public DateTime TimeStamp - { - get => GetField(1, DataFields.Timestamp).GetValue(OpenProtocolConvert.ToDateTime); - set => GetField(1, DataFields.Timestamp).SetValue(OpenProtocolConvert.ToString, value); - } - public int SyncTighteningId - { - get => GetField(1, DataFields.SyncTighteningId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.SyncTighteningId).SetValue(OpenProtocolConvert.ToString, value); - } - public bool SyncOverallStatus - { - get => GetField(1, DataFields.SyncOverallStatus).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.SyncOverallStatus).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 2)] + public int NumberOfSpindlesOrPresses { get; set; } + [StringDataFieldDefinition(revision: 1, field: 2, Index = 24, Size = 25)] + public string VinNumber { get; set; } + [Int32DataFieldDefinition(revision: 1, field: 3, Index = 51, Size = 2)] + public int JobId { get; set; } + [Int32DataFieldDefinition(revision: 1, field: 4, Index = 55, Size = 3)] + public int ParameterSetId { get; set; } + [Int32DataFieldDefinition(revision: 1, field: 5, Index = 60, Size = 4)] + public int BatchSize { get; set; } + [Int32DataFieldDefinition(revision: 1, field: 6, Index = 66, Size = 4)] + public int BatchCounter { get; set; } + [Int32DataFieldDefinition(revision: 1, field: 7, Index = 72, Size = 1)] + public BatchStatus BatchStatus { get; set; } + [TruncatedDecimalDataFieldDefinition(revision: 1, field: 8, Index = 75, Size = 6)] + public decimal TorqueOrForceMinLimit { get; set; } + [TruncatedDecimalDataFieldDefinition(revision: 1, field: 9, Index = 83, Size = 6)] + public decimal TorqueOrForceMaxLimit { get; set; } + [TruncatedDecimalDataFieldDefinition(revision: 1, field: 10, Index = 91, Size = 6)] + public decimal TorqueOrForceFinalTarget { get; set; } + [TruncatedDecimalDataFieldDefinition(revision: 1, field: 11, Index = 99, Size = 5)] + public decimal AngleOrStrokeMinLimit { get; set; } + [TruncatedDecimalDataFieldDefinition(revision: 1, field: 12, Index = 106, Size = 5)] + public decimal AngleOrStrokeMaxLimit { get; set; } + [TruncatedDecimalDataFieldDefinition(revision: 1, field: 13, Index = 113, Size = 5)] + public decimal FinalAngleOrStrokeTarget { get; set; } + [TimestampDataFieldDefinition(revision: 1, field: 14, Index = 120)] + public DateTime LastChangeInParameterSet { get; set; } + [TimestampDataFieldDefinition(revision: 1, field: 15, Index = 141)] + public DateTime TimeStamp { get; set; } + [Int32DataFieldDefinition(revision: 1, field: 16, Index = 162, Size = 5)] + public int SyncTighteningId { get; set; } + [BooleanDataFieldDefinition(revision: 1, field: 17, Index = 169)] + public bool SyncOverallStatus { get; set; } + [SpindleOrPressStatusCollectionDefinition(revision: 1, field: 18, Index = 172)] public List SpindlesOrPressesStatus { get; set; } - public SystemSubType SystemSubType - { - get => (SystemSubType)GetField(4, DataFields.SystemSubType).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(4, DataFields.SystemSubType).SetValue(OpenProtocolConvert.ToString, value); - } - public int JobSequenceNumber - { - get => GetField(5, DataFields.JobSequenceNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(5, DataFields.JobSequenceNumber).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 4, field: 19, Index = 0, Size = 3)] + public SystemSubType SystemSubType { get; set; } + [Int32DataFieldDefinition(revision: 5, field: 20, Index = 0, Size = 5)] + public int JobSequenceNumber { get; set; } public Mid0101() : this(DEFAULT_REVISION) { @@ -140,115 +85,58 @@ public Mid0101(int revision) : this(new Header() public override string Pack() { - var spindesOrPressesStatusField = GetField(1, DataFields.SpindesOrPressesStatus); - spindesOrPressesStatusField.Size = SpindlesOrPressesStatus.Count * 18; - spindesOrPressesStatusField.Value = PackSpindlesOrPressesStatus(); - return base.Pack(); - } - - public override Mid Parse(ReadOnlySpan package) - { - Header = ProcessHeader(package); - var spindleOrPressesField = GetField(1, DataFields.NumberOfSpindlesOrPresses); - int spindleOrPresses = OpenProtocolConvert.ToInt32(GetValue(spindleOrPressesField, package).ToString()); - var spindesOrPressesStatusField = GetField(1, DataFields.SpindesOrPressesStatus); - spindesOrPressesStatusField.Size = spindleOrPresses * 18; - if(Header.Revision > 3) + NumberOfSpindlesOrPresses = SpindlesOrPressesStatus?.Count ?? 0; //Enforce the number of spindles to match the list count + var spindlesOrPressesStatusField = GetField(revision: 1, field: 18); + spindlesOrPressesStatusField.Size = NumberOfSpindlesOrPresses * SpindleOrPressStatus.DefaultSize; + if (Header.Revision > 3) { - var systemSubTypeField = GetField(4, DataFields.SystemSubType); - systemSubTypeField.Index = spindesOrPressesStatusField.Index + spindesOrPressesStatusField.Size + 2; - if(Header.Revision > 4) + var systemSubTypeField = GetField(revision: 4, field: 19); + systemSubTypeField.Index = spindlesOrPressesStatusField.Index + spindlesOrPressesStatusField.TotalSize; + if (Header.Revision > 4) { - GetField(5, DataFields.JobSequenceNumber).Index = systemSubTypeField.Index + systemSubTypeField.Size + 2; + GetField(revision: 5, field: 20).Index = systemSubTypeField.Index + systemSubTypeField.TotalSize; } } - ProcessDataFields(package); - SpindlesOrPressesStatus = ParseSpindlesOrPressesStatus(spindesOrPressesStatusField.Value); - return this; + return base.Pack(); } - //TODO: move to SpindleOrPressStatus class - protected virtual string PackSpindlesOrPressesStatus() + + protected override void ProcessDataFields(ReadOnlySpan package) { - string package = string.Empty; - foreach (var v in SpindlesOrPressesStatus) + foreach (var field in DataFieldsByRevision()) { - package += OpenProtocolConvert.ToString('0', 2, PaddingOrientation.LeftPadded, v.SpindleOrPressNumber) + - OpenProtocolConvert.ToString('0', 2, PaddingOrientation.LeftPadded, v.ChannelId) + - OpenProtocolConvert.ToString(v.OverallStatus) + - OpenProtocolConvert.ToString(v.TorqueOrForceStatus) + - OpenProtocolConvert.TruncatedDecimalToString('0', 6, PaddingOrientation.LeftPadded, v.TorqueOrForce) + - OpenProtocolConvert.ToString(v.AngleOrStrokeStatus) + - OpenProtocolConvert.ToString('0', 5, PaddingOrientation.LeftPadded, v.AngleOrStroke); + ProcessDataField(field, package); } - - return package; } - protected virtual List ParseSpindlesOrPressesStatus(string section) + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - var list = new List(); - for (int i = 0; i < section.Length; i += 18) + if (dataField.Field == 18) { - var spindleValue = section.Substring(i, 18); - var obj = new SpindleOrPressStatus() - { - SpindleOrPressNumber = OpenProtocolConvert.ToInt32(spindleValue.Substring(0, 2)), - ChannelId = OpenProtocolConvert.ToInt32(spindleValue.Substring(2, 2)), - OverallStatus = OpenProtocolConvert.ToBoolean(spindleValue.Substring(4, 1)), - TorqueOrForceStatus = (TighteningValueStatus)OpenProtocolConvert.ToInt32(spindleValue.Substring(5, 1)), - TorqueOrForce = OpenProtocolConvert.ToTruncatedDecimal(spindleValue.Substring(6, 6)), - AngleOrStrokeStatus = OpenProtocolConvert.ToBoolean(spindleValue.Substring(12, 1)), - AngleOrStroke = OpenProtocolConvert.ToInt32(spindleValue.Substring(13, 5)) - }; - list.Add(obj); + dataField.Size = NumberOfSpindlesOrPresses * SpindleOrPressStatus.DefaultSize; } - - return list; + base.ProcessDataField(dataField, package); } - protected override Dictionary> RegisterDatafields() + private IEnumerable DataFieldsByRevision() { - return new Dictionary>() + var previousField = default(DataField); + foreach (var fields in RevisionsByFields) { + foreach (var dataField in fields.Value) { - 1, new List() - { - DataField.Number(DataFields.NumberOfSpindlesOrPresses, 20, 2), - DataField.String(DataFields.VinNumber, 24, 25), - DataField.Number(DataFields.JobId, 51, 2), - DataField.Number(DataFields.ParameterSetId, 55, 3), - DataField.Number(DataFields.BatchSize, 60, 4), - DataField.Number(DataFields.BatchCounter, 66, 4), - DataField.Number(DataFields.BatchStatus, 72, 1), - DataField.Number(DataFields.TorqueOrForceMinLimit, 75, 6), - DataField.Number(DataFields.TorqueOrForceMaxLimit, 83, 6), - DataField.Number(DataFields.TorqueOrForceFinalTarget, 91, 6), - DataField.Number(DataFields.AngleOrStrokeMinLimit, 99, 5), - DataField.Number(DataFields.AngleOrStrokeMaxLimit, 106, 5), - DataField.Number(DataFields.FinalAngleOrStrokeTarget, 113, 5), - DataField.Timestamp(DataFields.LastChangeInParameterSet, 120), - DataField.Timestamp(DataFields.Timestamp, 141), - DataField.Number(DataFields.SyncTighteningId, 162, 5), - DataField.Boolean(DataFields.SyncOverallStatus, 169), - DataField.Volatile(DataFields.SpindesOrPressesStatus, 172) - } - }, - { - 4, new List() - { - DataField.Number(DataFields.SystemSubType, 0, 3) - } - }, - { - 5, new List() - { - DataField.Number(DataFields.JobSequenceNumber, 0, 5) - } + if (previousField != null && dataField.Index == 0) + { + dataField.Index = previousField.Index + previousField.TotalSize; + } + + previousField = dataField; + yield return dataField; } - }; + } } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { NumberOfSpindlesOrPresses, diff --git a/src/OpenProtocolInterpreter/MultiSpindle/Mid0104.cs b/src/OpenProtocolInterpreter/MultiSpindle/Mid0104.cs index 8c5e5b88..5ec9c059 100644 --- a/src/OpenProtocolInterpreter/MultiSpindle/Mid0104.cs +++ b/src/OpenProtocolInterpreter/MultiSpindle/Mid0104.cs @@ -10,7 +10,7 @@ namespace OpenProtocolInterpreter.MultiSpindle /// Note: MID 0104 can’t be used if there is an active subscription for multiple spindle results. /// Message sent by: Integrator /// - /// Answer: Command accepted or + /// Answer: Command accepted or /// Command error, Multi spindle result subscription already exists /// /// @@ -20,11 +20,8 @@ public class Mid0104 : Mid, IMultiSpindle, IIntegrator, IAnswerableBy, public IEnumerable DocumentedPossibleErrors => new Error[] { Error.MultiSpindleResultSubscriptionAlreadyExists }; - public long RequestedResultIndex - { - get => GetField(1, DataFields.RequestedResultIndex).GetValue(OpenProtocolConvert.ToInt64); - set => GetField(1, DataFields.RequestedResultIndex).SetValue(OpenProtocolConvert.ToString, value); - } + [Int64DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 10)] + public long RequestedResultIndex { get; set; } public Mid0104() : base(MID, DEFAULT_REVISION) { } @@ -32,19 +29,7 @@ public Mid0104(Header header) : base(header) { } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.RequestedResultIndex, 20, 10), - } - } - }; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { RequestedResultIndex diff --git a/src/OpenProtocolInterpreter/MultiSpindle/SpindleOrPressStatus.cs b/src/OpenProtocolInterpreter/MultiSpindle/SpindleOrPressStatus.cs index f6b2cd87..bf8dbd21 100644 --- a/src/OpenProtocolInterpreter/MultiSpindle/SpindleOrPressStatus.cs +++ b/src/OpenProtocolInterpreter/MultiSpindle/SpindleOrPressStatus.cs @@ -1,10 +1,17 @@ -namespace OpenProtocolInterpreter.MultiSpindle +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +namespace OpenProtocolInterpreter.MultiSpindle { /// /// Represents a Spindle or a Press status entity, depending on /// public class SpindleOrPressStatus { + internal const int DefaultSize = 18; + public int SpindleOrPressNumber { get; set; } public int ChannelId { get; set; } public bool OverallStatus { get; set; } @@ -12,5 +19,80 @@ public class SpindleOrPressStatus public decimal TorqueOrForce { get; set; } public bool AngleOrStrokeStatus { get; set; } public int AngleOrStroke { get; set; } + + public string Pack() + { + return string.Concat( + OpenProtocolConvert.ToString('0', 2, PaddingOrientation.LeftPadded, SpindleOrPressNumber), + OpenProtocolConvert.ToString('0', 2, PaddingOrientation.LeftPadded, ChannelId), + OpenProtocolConvert.ToString(OverallStatus), + OpenProtocolConvert.ToString(TorqueOrForceStatus), + OpenProtocolConvert.TruncatedDecimalToString('0', 6, PaddingOrientation.LeftPadded, TorqueOrForce), + OpenProtocolConvert.ToString(AngleOrStrokeStatus), + OpenProtocolConvert.ToString('0', 5, PaddingOrientation.LeftPadded, AngleOrStroke) + ); + } + + public static SpindleOrPressStatus Parse(string section) + => Parse(section.AsSpan()); + + public static SpindleOrPressStatus Parse(ReadOnlySpan section) + { + return new SpindleOrPressStatus() + { + SpindleOrPressNumber = OpenProtocolConvert.ToInt32(section.Slice(0, 2)), + ChannelId = OpenProtocolConvert.ToInt32(section.Slice(2, 2)), + OverallStatus = OpenProtocolConvert.ToBoolean(section.Slice(4, 1)), + TorqueOrForceStatus = (TighteningValueStatus)OpenProtocolConvert.ToInt32(section.Slice(5, 1)), + TorqueOrForce = OpenProtocolConvert.ToTruncatedDecimal(section.Slice(6, 6)), + AngleOrStrokeStatus = OpenProtocolConvert.ToBoolean(section.Slice(12, 1)), + AngleOrStroke = OpenProtocolConvert.ToInt32(section.Slice(13, 5)) + }; + } + + public static List ParseAll(string section) + => ParseAll(section.AsSpan()); + + public static List ParseAll(ReadOnlySpan section) + { + var list = new List(); + for (int i = 0; i < section.Length; i += DefaultSize) + { + list.Add(Parse(section.Slice(i, DefaultSize))); + } + + return list; + } + } + + public class SpindleOrPressStatusCollectionDefinitionAttribute : DataFieldDefinitionAttribute + { + public SpindleOrPressStatusCollectionDefinitionAttribute(int revision) : base(revision) + { + + } + public SpindleOrPressStatusCollectionDefinitionAttribute(int field, int revision) : base(field, revision) + { + + } + + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + { + return new DataField>(Field, index, Size, HasPrefix) + { + DefaultConverter = Pack, + DefaultParser = SpindleOrPressStatus.ParseAll + }.Bind(mid, propertyInfo); + } + + private static string Pack(char paddingChar, int size, PaddingOrientation orientation, List spindleOrPressStatus) + { + var sb = new StringBuilder(); + foreach (var spindle in spindleOrPressStatus) + { + sb.Append(spindle.Pack()); + } + return sb.ToString(); + } } } diff --git a/src/OpenProtocolInterpreter/MultiSpindle/SpindleStatus.cs b/src/OpenProtocolInterpreter/MultiSpindle/SpindleStatus.cs index 45cc9dea..88d7a373 100644 --- a/src/OpenProtocolInterpreter/MultiSpindle/SpindleStatus.cs +++ b/src/OpenProtocolInterpreter/MultiSpindle/SpindleStatus.cs @@ -1,12 +1,86 @@ -namespace OpenProtocolInterpreter.MultiSpindle +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +namespace OpenProtocolInterpreter.MultiSpindle { /// /// Represents a Spindle Status entity /// public class SpindleStatus { + internal const int DefaultSize = 5; + public int SpindleNumber { get; set; } public int ChannelId { get; set; } public bool SyncOverallStatus { get; set; } + + public string Pack() + { + return string.Concat( + OpenProtocolConvert.ToString('0', 2, PaddingOrientation.LeftPadded, SpindleNumber), + OpenProtocolConvert.ToString('0', 2, PaddingOrientation.LeftPadded, ChannelId), + OpenProtocolConvert.ToString(SyncOverallStatus) + ); + } + + public static SpindleStatus Parse(string section) + => Parse(section.AsSpan()); + + public static SpindleStatus Parse(ReadOnlySpan section) + { + return new SpindleStatus() + { + SpindleNumber = OpenProtocolConvert.ToInt32(section.Slice(0, 2)), + ChannelId = OpenProtocolConvert.ToInt32(section.Slice(2, 2)), + SyncOverallStatus = OpenProtocolConvert.ToBoolean(section.Slice(4, 1)) + }; + } + + public static List ParseAll(string section) + => ParseAll(section.AsSpan()); + + public static List ParseAll(ReadOnlySpan section) + { + var list = new List(); + for (int i = 0; i < section.Length; i += DefaultSize) + { + list.Add(Parse(section.Slice(i, DefaultSize))); + } + + return list; + } + } + + public class SpindleStatusCollectionDefinitionAttribute : DataFieldDefinitionAttribute + { + public SpindleStatusCollectionDefinitionAttribute(int revision) : base(revision) + { + + } + public SpindleStatusCollectionDefinitionAttribute(int field, int revision) : base(field, revision) + { + + } + + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + { + return new DataField>(Field, index, Size, HasPrefix) + { + DefaultConverter = Pack, + DefaultParser = SpindleStatus.ParseAll + }.Bind(mid, propertyInfo); + } + + private static string Pack(char paddingChar, int size, PaddingOrientation orientation, List spindleStatus) + { + var sb = new StringBuilder(); + foreach (var spindle in spindleStatus) + { + sb.Append(spindle.Pack()); + } + return sb.ToString(); + } } } diff --git a/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0150.cs b/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0150.cs index 79cdb72b..60d11c22 100644 --- a/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0150.cs +++ b/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0150.cs @@ -32,7 +32,6 @@ public Mid0150(Header header) : base(header) protected override string BuildHeader() { - Header.Length = Header.DefaultSize + IdentifierData?.Length ?? 0; return Header.ToString(); } diff --git a/src/OpenProtocolInterpreter/OpenProtocolConvert.cs b/src/OpenProtocolInterpreter/OpenProtocolConvert.cs index 4b3990ff..d54f327a 100644 --- a/src/OpenProtocolInterpreter/OpenProtocolConvert.cs +++ b/src/OpenProtocolInterpreter/OpenProtocolConvert.cs @@ -86,15 +86,16 @@ public static string TruncatedDecimalToString(decimal value, int decimalDigits) } public static string TruncatedDecimalToString(char paddingChar, int size, PaddingOrientation orientation, decimal value) - { - var str = TruncatedDecimalToString(value); - return TruncatePadded(paddingChar, size, orientation, str); - } + => TruncatedDecimalToString(paddingChar, size, orientation, value, 2); public static string TruncatedDecimalToString(char paddingChar, int size, PaddingOrientation orientation, decimal value, int decimalDigits) { - var str = TruncatedDecimalToString(value, decimalDigits); - return TruncatePadded(paddingChar, size, orientation, str); + if (value < 0) + { + return string.Concat("-", TruncatePadded(paddingChar, size - 1, orientation, TruncatedDecimalToString(-value, decimalDigits))); + } + + return TruncatePadded(paddingChar, size, orientation, TruncatedDecimalToString(value, decimalDigits)); } public static decimal ToTruncatedDecimal(string value) From 235a3e04302fb9ce35767196756554a439857754 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Tue, 14 Jul 2026 23:56:58 -0300 Subject: [PATCH 45/78] updated OpenProtocolCommandsDisable to use datafield attributes --- .../OpenProtocolCommandsDisabled/Mid0421.cs | 33 +++++-------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/src/OpenProtocolInterpreter/OpenProtocolCommandsDisabled/Mid0421.cs b/src/OpenProtocolInterpreter/OpenProtocolCommandsDisabled/Mid0421.cs index 8628a69e..a4b82253 100644 --- a/src/OpenProtocolInterpreter/OpenProtocolCommandsDisabled/Mid0421.cs +++ b/src/OpenProtocolInterpreter/OpenProtocolCommandsDisabled/Mid0421.cs @@ -1,15 +1,16 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.OpenProtocolCommandsDisabled { /// /// Open Protocol commands disabled /// - /// Upload the status of the Open Protocol commands disable digital input. - /// The data upload consists of one byte delivering the digital input status. - /// The status is uploaded each time the “Open Protocol commands disable” digital + /// Upload the status of the Open Protocol commands disable digital input. + /// The data upload consists of one byte delivering the digital input status. + /// The status is uploaded each time the “Open Protocol commands disable” digital /// input changes (push function). - /// + /// /// Message sent by: Controller /// Answer: Open Protocol commands disabled acknowledge /// @@ -17,12 +18,8 @@ public class Mid0421 : Mid, IOpenProtocolCommandsDisabled, IController, IAcknowl { public const int MID = 421; - public bool DigitalInputStatus - { - get => GetField(1, DataFields.DigitalInputStatus).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.DigitalInputStatus).SetValue(OpenProtocolConvert.ToString, value); - } - + [BooleanDataFieldDefinition(revision: 1, field: 1, Index = 20, HasPrefix = false)] + public bool DigitalInputStatus { get; set; } public Mid0421() : this(new Header() { Mid = MID, @@ -35,19 +32,7 @@ public Mid0421(Header header) : base(header) { } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Boolean(DataFields.DigitalInputStatus, 20, false) - } - } - }; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { DigitalInputStatus From b8ef46053eb5ac3d59ee2c2f060906a8cfd31d04 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Wed, 15 Jul 2026 23:55:20 -0300 Subject: [PATCH 46/78] Updated parameter set messages to use datafield attribute --- .../ParameterSet/TestMid2505.cs | 2 +- .../ParameterSet/TestMid2506.cs | 2 +- src/OpenProtocolInterpreter/Alarm/Mid1000.cs | 1 + .../Job/ParameterSet.cs | 1 - .../ParameterSet/Mid0011.cs | 70 ++---- .../ParameterSet/Mid0012.cs | 39 +--- .../ParameterSet/Mid0013.cs | 131 +++-------- .../ParameterSet/Mid0015.cs | 212 ++++++++---------- .../ParameterSet/Mid0018.cs | 29 +-- .../ParameterSet/Mid0019.cs | 79 +++---- .../ParameterSet/Mid0020.cs | 26 +-- .../ParameterSet/Mid0022.cs | 25 +-- .../ParameterSet/Mid2504.cs | 26 +-- .../ParameterSet/Mid2505.cs | 52 ++--- .../ParameterSet/Mid2506.cs | 33 +-- .../Tightening/Mid0902.cs | 2 + .../VariableDataField.cs | 4 + 17 files changed, 238 insertions(+), 496 deletions(-) diff --git a/src/MIDTesters.Core/ParameterSet/TestMid2505.cs b/src/MIDTesters.Core/ParameterSet/TestMid2505.cs index 62abeda0..0e5af0ba 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid2505.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid2505.cs @@ -1,7 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter.ParameterSet; -namespace MIDTesters.Core.ParameterSet +namespace MIDTesters.ParameterSet { [TestClass] [TestCategory("ParameterSet")] diff --git a/src/MIDTesters.Core/ParameterSet/TestMid2506.cs b/src/MIDTesters.Core/ParameterSet/TestMid2506.cs index c6975e58..9b03e35f 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid2506.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid2506.cs @@ -2,7 +2,7 @@ using OpenProtocolInterpreter; using OpenProtocolInterpreter.ParameterSet; -namespace MIDTesters.Core.ParameterSet +namespace MIDTesters.ParameterSet { [TestClass] [TestCategory("ParameterSet")] diff --git a/src/OpenProtocolInterpreter/Alarm/Mid1000.cs b/src/OpenProtocolInterpreter/Alarm/Mid1000.cs index eaaf9046..842f062a 100644 --- a/src/OpenProtocolInterpreter/Alarm/Mid1000.cs +++ b/src/OpenProtocolInterpreter/Alarm/Mid1000.cs @@ -46,6 +46,7 @@ public Mid1000(int revision) : this(new Header() public override string Pack() { NumberOfDataFields = AlarmDataFields?.Count ?? 0; //Enforce list size even if modified + GetField(revision: 1, field: 3).Size = AlarmDataFields?.Sum(x => x.TotalSize) ?? 0; //Enforce size of variable data fields return base.Pack(); } diff --git a/src/OpenProtocolInterpreter/Job/ParameterSet.cs b/src/OpenProtocolInterpreter/Job/ParameterSet.cs index d42e6764..7735445c 100644 --- a/src/OpenProtocolInterpreter/Job/ParameterSet.cs +++ b/src/OpenProtocolInterpreter/Job/ParameterSet.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using System.Text; namespace OpenProtocolInterpreter.Job { diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid0011.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid0011.cs index b5d7a867..b3b9d7ad 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid0011.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid0011.cs @@ -1,6 +1,7 @@ using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; namespace OpenProtocolInterpreter.ParameterSet { @@ -10,7 +11,7 @@ namespace OpenProtocolInterpreter.ParameterSet /// The transmission of all the valid parameter set IDs of the controller. In the revision 000-001 the data /// field contains the number of valid parameter sets currently present in the controller, and the ID of each /// parameter set present.In revision 2 is the number of stages on each Pset/Mset added. - /// + /// /// Message sent by: Controller /// Answer: None /// @@ -18,16 +19,15 @@ public class Mid0011 : Mid, IParameterSet, IController { public const int MID = 11; - public int TotalParameterSets - { - get => ParameterSets.Count; - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 3, HasPrefix = false)] + public int TotalParameterSets { get; set; } + [Int32CollectionDefinition(revision: 1, field: 2, Index = 23, EachFieldSize = 3, HasPrefix = false)] public List ParameterSets { get; set; } public Mid0011() : this(new Header() { - Mid = MID, + Mid = MID, Revision = DEFAULT_REVISION }) { @@ -38,56 +38,22 @@ public Mid0011(Header header) : base(header) ParameterSets ??= []; } - public override string Pack() - { - GetField(1, DataFields.TotalParameterSets).SetValue(OpenProtocolConvert.ToString, TotalParameterSets); - var eachParameterField = GetField(1, DataFields.EachParameterSet); - eachParameterField.Value = PackParameterSetIdList(); - eachParameterField.Size = eachParameterField.Value.Length; - return base.Pack(); - } - - public override Mid Parse(ReadOnlySpan package) - { - Header = ProcessHeader(package); - - GetField(1, DataFields.EachParameterSet).Size = Header.Length - GetField(1, DataFields.EachParameterSet).Index; - ProcessDataFields(package); - ParameterSets = ParseParameterSetIdList(GetField(1, DataFields.EachParameterSet).Value).ToList(); - return this; - } - - protected virtual string PackParameterSetIdList() + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - string pack = string.Empty; - foreach (var v in ParameterSets) - pack += OpenProtocolConvert.ToString('0', 3, PaddingOrientation.LeftPadded, v); - return pack; - } - - protected virtual List ParseParameterSetIdList(string section) - { - var list = new List(); - for (int i = 0; i < section.Length; i += 3) - list.Add(OpenProtocolConvert.ToInt32(section.Substring(i, 3))); - - return list; + if (dataField.Field == 2) //ParameterSets + { + dataField.Size = TotalParameterSets * 3; + } + base.ProcessDataField(dataField, package); } - - protected override Dictionary> RegisterDatafields() + public override string Pack() { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.TotalParameterSets, 20, 3, false), - new(DataFields.EachParameterSet, 23, 3, false) - } - } - }; + TotalParameterSets = ParameterSets?.Count ?? 0; //Enforce the number of parameter sets to match the list count + GetField(nameof(ParameterSets)).Size = TotalParameterSets * 3; + return base.Pack(); } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { TotalParameterSets, diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid0012.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid0012.cs index 4ce48402..a3986e52 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid0012.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid0012.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.ParameterSet { @@ -7,7 +8,7 @@ namespace OpenProtocolInterpreter.ParameterSet /// Request to upload parameter set data from the controller. /// Message sent by: Integrator /// - /// Answer: Parameter set data upload reply, or + /// Answer: Parameter set data upload reply, or /// Command error, Parameter set not present /// /// @@ -17,16 +18,10 @@ public class Mid0012 : Mid, IParameterSet, IIntegrator, IAnswerableBy, public IEnumerable DocumentedPossibleErrors => new Error[] { Error.ParameterSetIdNotPresent }; - public int ParameterSetId - { - get => GetField(1, DataFields.ParameterSetId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.ParameterSetId).SetValue(OpenProtocolConvert.ToString, value); - } - public int ParameterSetFileVersion - { - get => GetField(3, DataFields.PSetFileVersion).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(3, DataFields.PSetFileVersion).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 3, HasPrefix = false)] + public int ParameterSetId { get; set; } + [Int32DataFieldDefinition(revision: 3, field: 3, Index = 23, Size = 8, HasPrefix = false)] + public int ParameterSetFileVersion { get; set; } public Mid0012() : this(DEFAULT_REVISION) { @@ -45,25 +40,7 @@ public Mid0012(Header header) : base(header) { } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.ParameterSetId, 20, 3, false) - } - }, - { - 3, new List() - { - DataField.Number(DataFields.PSetFileVersion, 23, 8, false) - } - }, - }; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { //Revision 1-2 diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid0013.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid0013.cs index 06bfba2f..9cdfb588 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid0013.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid0013.cs @@ -13,73 +13,34 @@ public class Mid0013 : Mid, IParameterSet, IController { public const int MID = 13; - public int ParameterSetId - { - get => GetField(1, DataFields.ParameterSetId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.ParameterSetId).SetValue(OpenProtocolConvert.ToString, value); - } - public string ParameterSetName - { - get => GetField(1, DataFields.ParameterSetName).Value; - set => GetField(1, DataFields.ParameterSetName).SetValue(value); - } - public RotationDirection RotationDirection - { - get => (RotationDirection)GetField(1, DataFields.RotationDirection).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.RotationDirection).SetValue(OpenProtocolConvert.ToString, value); - } - public int BatchSize - { - get => GetField(1, DataFields.BatchSize).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.BatchSize).SetValue(OpenProtocolConvert.ToString, value); - } - public decimal MinTorque - { - get => GetField(1, DataFields.MinTorque).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(1, DataFields.MinTorque).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal MaxTorque - { - get => GetField(1, DataFields.MaxTorque).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(1, DataFields.MaxTorque).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal TorqueFinalTarget - { - get => GetField(1, DataFields.TorqueFinalTarget).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(1, DataFields.TorqueFinalTarget).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public int MinAngle - { - get => GetField(1, DataFields.MinAngle).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.MinAngle).SetValue(OpenProtocolConvert.ToString, value); - } - public int MaxAngle - { - get => GetField(1, DataFields.MaxAngle).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.MaxAngle).SetValue(OpenProtocolConvert.ToString, value); - } - public int AngleFinalTarget - { - get => GetField(1, DataFields.AngleFinalTarget).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.AngleFinalTarget).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 3)] + public int ParameterSetId { get; set; } + [StringDataFieldDefinition(revision: 1, field: 2, Index = 25, Size = 25)] + public string ParameterSetName { get; set; } + [Int32DataFieldDefinition(revision: 1, field: 3, Index = 52, Size = 1)] + public RotationDirection RotationDirection { get; set; } + [Int32DataFieldDefinition(revision: 1, field: 4, Index = 55, Size = 2)] + public int BatchSize { get; set; } + [TruncatedDecimalDataFieldDefinition(revision: 1, field: 5, Index = 59, Size = 6)] + public decimal MinTorque { get; set; } + [TruncatedDecimalDataFieldDefinition(revision: 1, field: 6, Index = 67, Size = 6)] + public decimal MaxTorque { get; set; } + [TruncatedDecimalDataFieldDefinition(revision: 1, field: 7, Index = 75, Size = 6)] + public decimal TorqueFinalTarget { get; set; } + [Int32DataFieldDefinition(revision: 1, field: 8, Index = 83, Size = 5)] + public int MinAngle { get; set; } + [Int32DataFieldDefinition(revision: 1, field: 9, Index = 90, Size = 5)] + public int MaxAngle { get; set; } + [Int32DataFieldDefinition(revision: 1, field: 10, Index = 97, Size = 5)] + public int AngleFinalTarget { get; set; } //Rev 2 - public decimal FirstTarget - { - get => GetField(2, DataFields.FirstTarget).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(2, DataFields.FirstTarget).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal StartFinalAngle - { - get => GetField(2, DataFields.StartFinalTarget).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(2, DataFields.StartFinalTarget).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } + [TruncatedDecimalDataFieldDefinition(revision: 2, field: 1, Index = 104, Size = 6)] + public decimal FirstTarget { get; set; } + [TruncatedDecimalDataFieldDefinition(revision: 2, field: 2, Index = 112, Size = 6)] + public decimal StartFinalAngle { get; set; } //Rev 5 - public DateTime LastChangeInParameterSet - { - get => GetField(5, DataFields.LastChangeInParameterSet).GetValue(OpenProtocolConvert.ToDateTime); - set => GetField(5, DataFields.LastChangeInParameterSet).SetValue(OpenProtocolConvert.ToString, value); - } + [TimestampDataFieldDefinition(revision: 5, field: 1, Index = 120)] + public DateTime LastChangeInParameterSet { get; set; } public Mid0013() : this(DEFAULT_REVISION) { @@ -88,52 +49,18 @@ public Mid0013() : this(DEFAULT_REVISION) public Mid0013(Header header) : base(header) { - + } public Mid0013(int revision) : this(new Header() { - Mid = MID, + Mid = MID, Revision = revision }) { } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.ParameterSetId, 20, 3), - DataField.String(DataFields.ParameterSetName, 25, 25), - DataField.Number(DataFields.RotationDirection, 52, 1), - DataField.Number(DataFields.BatchSize, 55, 2), - DataField.Number(DataFields.MinTorque, 59, 6), - DataField.Number(DataFields.MaxTorque, 67, 6), - DataField.Number(DataFields.TorqueFinalTarget, 75, 6), - DataField.Number(DataFields.MinAngle, 83, 5), - DataField.Number(DataFields.MaxAngle, 90, 5), - DataField.Number(DataFields.AngleFinalTarget, 97, 5) - } - }, - { - 2, new List() - { - DataField.Number(DataFields.FirstTarget, 104, 6), - DataField.Number(DataFields.StartFinalTarget, 112, 6) - } - }, - { - 5, new List() - { - DataField.Timestamp(DataFields.LastChangeInParameterSet, 120), - } - } - }; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { ParameterSetId, diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid0015.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid0015.cs index 4ecfaa5b..a404c5a0 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid0015.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid0015.cs @@ -1,14 +1,16 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Text; namespace OpenProtocolInterpreter.ParameterSet { /// /// Parameter set selected /// - /// A new parameter set is selected in the controller. - /// The message includes the ID of the parameter set selected as well as the date and time of the - /// last change in the parameter set settings. This message is also sent as an immediate response to + /// A new parameter set is selected in the controller. + /// The message includes the ID of the parameter set selected as well as the date and time of the + /// last change in the parameter set settings. This message is also sent as an immediate response to /// Parameter set selected subscribe. /// /// Message sent by: Controller @@ -18,82 +20,66 @@ public class Mid0015 : Mid, IParameterSet, IController, IAcknowledgeable GetField(Header.StandardizedRevision, DataFields.ParameterSetId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(Header.StandardizedRevision, DataFields.ParameterSetId).SetValue(OpenProtocolConvert.ToString, value); - } - public DateTime LastChangeInParameterSet - { - get => GetField(Header.StandardizedRevision, DataFields.LastChangeInParameterSet).GetValue(OpenProtocolConvert.ToDateTime); - set => GetField(Header.StandardizedRevision, DataFields.LastChangeInParameterSet).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 2, field: 1, Index = 20, Size = 3)] + [Int32DataFieldDefinition(revision: 3, field: 1, Index = 20, Size = 3)] + public int ParameterSetId { get; set; } + + [TimestampDataFieldDefinition(revision: 1, field: 2, Index = 23, HasPrefix = false)] + [TimestampDataFieldDefinition(revision: 2, field: 3, Index = 52)] + [TimestampDataFieldDefinition(revision: 3, field: 3, Index = 52)] + public DateTime LastChangeInParameterSet { get; set; } + //Rev 2 - public string ParameterSetName - { - get => GetField(2, DataFields.ParameterSetName).Value; - set => GetField(2, DataFields.ParameterSetName).SetValue(value); - } - public RotationDirection RotationDirection - { - get => (RotationDirection)GetField(2, DataFields.RotationDirection).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.RotationDirection).SetValue(OpenProtocolConvert.ToString, value); - } - public int BatchSize - { - get => GetField(2, DataFields.BatchSize).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.BatchSize).SetValue(OpenProtocolConvert.ToString, value); - } - public decimal MinTorque - { - get => GetField(2, DataFields.TorqueMin).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(2, DataFields.TorqueMin).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal MaxTorque - { - get => GetField(2, DataFields.TorqueMax).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(2, DataFields.TorqueMax).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal TorqueFinalTarget - { - get => GetField(2, DataFields.TorqueFinalTarget).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(2, DataFields.TorqueFinalTarget).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public int MinAngle - { - get => GetField(2, DataFields.AngleMin).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.AngleMin).SetValue(OpenProtocolConvert.ToString, value); - } - public int MaxAngle - { - get => GetField(2, DataFields.AngleMax).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.AngleMax).SetValue(OpenProtocolConvert.ToString, value); - } - public int AngleFinalTarget - { - get => GetField(2, DataFields.FinalAngleTarget).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.FinalAngleTarget).SetValue(OpenProtocolConvert.ToString, value); - } - public decimal FirstTarget - { - get => GetField(2, DataFields.FirstTarget).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(2, DataFields.FirstTarget).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal StartFinalAngle - { - get => GetField(2, DataFields.StartFinalAngle).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(2, DataFields.StartFinalAngle).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public int SelectedIdentifierNumber - { - get => GetField(3, DataFields.SelectedIdentifierNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(3, DataFields.SelectedIdentifierNumber).SetValue(OpenProtocolConvert.ToString, value); - } - public string JointId - { - get => GetField(3, DataFields.JointId).Value; - set => GetField(3, DataFields.JointId).SetValue(value); - } + [StringDataFieldDefinition(revision: 2, field: 2, Index = 25, Size = 25)] + [StringDataFieldDefinition(revision: 3, field: 2, Index = 25, Size = 25)] + public string ParameterSetName { get; set; } + + [Int32DataFieldDefinition(revision: 2, field: 4, Index = 73, Size = 1)] + [Int32DataFieldDefinition(revision: 3, field: 4, Index = 73, Size = 1)] + public RotationDirection RotationDirection { get; set; } + + [Int32DataFieldDefinition(revision: 2, field: 5, Index = 76, Size = 2)] + [Int32DataFieldDefinition(revision: 3, field: 5, Index = 76, Size = 2)] + public int BatchSize { get; set; } + + [TruncatedDecimalDataFieldDefinition(revision: 2, field: 6, Index = 80, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 3, field: 6, Index = 80, Size = 6)] + public decimal MinTorque { get; set; } + + [TruncatedDecimalDataFieldDefinition(revision: 2, field: 7, Index = 88, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 3, field: 7, Index = 88, Size = 6)] + public decimal MaxTorque { get; set; } + + [TruncatedDecimalDataFieldDefinition(revision: 2, field: 8, Index = 96, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 3, field: 8, Index = 96, Size = 6)] + public decimal TorqueFinalTarget { get; set; } + + [Int32DataFieldDefinition(revision: 2, field: 9, Index = 104, Size = 5)] + [Int32DataFieldDefinition(revision: 3, field: 9, Index = 104, Size = 5)] + public int MinAngle { get; set; } + + [Int32DataFieldDefinition(revision: 2, field: 10, Index = 111, Size = 5)] + [Int32DataFieldDefinition(revision: 3, field: 10, Index = 111, Size = 5)] + public int MaxAngle { get; set; } + + [Int32DataFieldDefinition(revision: 2, field: 11, Index = 118, Size = 5)] + [Int32DataFieldDefinition(revision: 3, field: 11, Index = 118, Size = 5)] + public int AngleFinalTarget { get; set; } + + [TruncatedDecimalDataFieldDefinition(revision: 2, field: 12, Index = 125, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 3, field: 12, Index = 125, Size = 6)] + public decimal FirstTarget { get; set; } + + [TruncatedDecimalDataFieldDefinition(revision: 2, field: 13, Index = 133, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 3, field: 13, Index = 133, Size = 6)] + public decimal StartFinalAngle { get; set; } + + [Int32DataFieldDefinition(revision: 3, field: 14, Index = 141, Size = 4)] + public int SelectedIdentifierNumber { get; set; } + + [StringDataFieldDefinition(revision: 3, field: 15, Index = 147, Size = 25)] + public string JointId { get; set; } public Mid0015() : this(DEFAULT_REVISION) { @@ -118,65 +104,43 @@ public Mid0015(int revision) : this(new Header() protected override string BuildHeader() { - Header.Length = 20; - foreach (var dataField in RevisionsByFields[Header.StandardizedRevision]) - Header.Length += (dataField.HasPrefix ? 2 : 0) + dataField.Size; + Header.Length = Header.DefaultSize; + if (RevisionsByFields.Any()) + { + var fields = DataFieldsByRevision(); + Header.Length += fields.Sum(x => x.TotalSize); + } return Header.ToString(); } + public override string Pack() { - int index = 1; - return BuildHeader() + base.Pack(Header.StandardizedRevision, ref index); - } + var builder = new StringBuilder(); + var fields = DataFieldsByRevision().OrderBy(f => f.Field).ToList(); - public override Mid Parse(ReadOnlySpan package) - { - Header = ProcessHeader(package); - ProcessDataFields(Header.StandardizedRevision, package); - return this; + int prefixIndex = 1; + builder.Append(BuildHeader()); + builder.Append(Pack(fields, ref prefixIndex)); + + return builder.ToString(); } - protected override Dictionary> RegisterDatafields() + protected override void ProcessDataFields(ReadOnlySpan package) { - return new Dictionary>() + foreach (var field in DataFieldsByRevision()) { - { - 1, new List() - { - DataField.Number(DataFields.ParameterSetId, 20, 3, false), - DataField.Timestamp(DataFields.LastChangeInParameterSet, 23, false) - } - }, - { - 2, new List() - { - DataField.Number(DataFields.ParameterSetId, 20, 3), - DataField.String(DataFields.ParameterSetName, 25, 25), - DataField.Timestamp(DataFields.LastChangeInParameterSet, 52), - DataField.Number(DataFields.RotationDirection, 73, 1), - DataField.Number(DataFields.BatchSize, 76, 2), - DataField.Number(DataFields.TorqueMin, 80, 6), - DataField.Number(DataFields.TorqueMax, 88, 6), - DataField.Number(DataFields.TorqueFinalTarget, 96, 6), - DataField.Number(DataFields.AngleMin, 104, 5), - DataField.Number(DataFields.AngleMax, 111, 5), - DataField.Number(DataFields.FinalAngleTarget, 118, 5), - DataField.Number(DataFields.FirstTarget, 125, 6), - DataField.Number(DataFields.StartFinalAngle, 133, 6) - } - }, - { - 3, new List() - { - DataField.Number(DataFields.SelectedIdentifierNumber, 141, 4), - DataField.String(DataFields.JointId, 147, 25) - } - } - }; + ProcessDataField(field, package); + } + } + private IEnumerable DataFieldsByRevision() + { + foreach (var dataField in RevisionsByFields[Header.StandardizedRevision].OrderBy(x => x.Field)) + yield return dataField; } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { ParameterSetId, diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid0018.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid0018.cs index 80eda582..796126ef 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid0018.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid0018.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.ParameterSet { @@ -6,7 +7,7 @@ namespace OpenProtocolInterpreter.ParameterSet /// Select Parameter set /// Message sent by: Integrator /// - /// Answer: Command accepted or + /// Answer: Command accepted or /// Command error, Parameter set can not be set /// /// @@ -16,15 +17,12 @@ public class Mid0018 : Mid, IParameterSet, IIntegrator, IAcceptableCommand, IDec public IEnumerable DocumentedPossibleErrors => new Error[] { Error.ParameterSetCannotBeSet }; - public int ParameterSetId - { - get => GetField(1, DataFields.ParameterSetId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.ParameterSetId).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 3, HasPrefix = false)] + public int ParameterSetId { get; set; } public Mid0018() : this(new Header() { - Mid = MID, + Mid = MID, Revision = DEFAULT_REVISION }) { @@ -34,20 +32,7 @@ public Mid0018(Header header) : base(header) { } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.ParameterSetId, 20, 3, false) - } - } - }; - } - - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { ParameterSetId diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid0019.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid0019.cs index 46d8ac30..db347ddd 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid0019.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid0019.cs @@ -1,5 +1,7 @@ using System; -using System.Collections.Generic; +using System.Collections.Generic; +using System.Linq; +using System.Text; namespace OpenProtocolInterpreter.ParameterSet { @@ -8,7 +10,7 @@ namespace OpenProtocolInterpreter.ParameterSet /// This message gives the possibility to set the batch size of a parameter set at run time. /// Message sent by: Integrator /// - /// Answer: Command accepted or + /// Answer: Command accepted or /// Command error, Invalid data /// /// @@ -18,16 +20,13 @@ public class Mid0019 : Mid, IParameterSet, IIntegrator, IAcceptableCommand, IDec public IEnumerable DocumentedPossibleErrors => new Error[] { Error.InvalidData }; - public int ParameterSetId - { - get => GetField(1, DataFields.ParameterSetId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.ParameterSetId).SetValue(OpenProtocolConvert.ToString, value); - } - public int BatchSize - { - get => GetField(1, DataFields.BatchSize).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.BatchSize).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 2, field: 1, Index = 20, Size = 3, HasPrefix = false)] + public int ParameterSetId { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 23, Size = 2, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 2, field: 2, Index = 23, Size = 4, HasPrefix = false)] + public int BatchSize { get; set; } public Mid0019() : this(new Header() { @@ -41,47 +40,41 @@ public Mid0019(Header header) : base(header) { } - public override string Pack() + protected override string BuildHeader() { - HandleRevision(); - return base.Pack(); - } + Header.Length = Header.DefaultSize; + if (RevisionsByFields.Any()) + { + var fields = DataFieldsByRevision(); + Header.Length += fields.Sum(x => x.TotalSize); + } - public override Mid Parse(ReadOnlySpan package) - { - Header = ProcessHeader(package); - HandleRevision(); - ProcessDataFields(package); - return this; + return Header.ToString(); } - private void HandleRevision() + public override string Pack() { - var batchSizeField = GetField(1, DataFields.BatchSize); - if (Header.Revision > 1) - { - batchSizeField.Size = 4; - } - else - { - batchSizeField.Size = 2; - } + var builder = new StringBuilder(); + var fields = DataFieldsByRevision().ToList(); + + int prefixIndex = 1; + builder.Append(BuildHeader()); + builder.Append(Pack(fields, ref prefixIndex)); + + return builder.ToString(); } - protected override Dictionary> RegisterDatafields() + + protected override void ProcessDataFields(ReadOnlySpan package) { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.ParameterSetId, 20, 3, false), - DataField.Number(DataFields.BatchSize, 23, 2, false), - } - } - }; + var fields = DataFieldsByRevision().ToList(); + base.ProcessDataFields(fields, package); } + private IEnumerable DataFieldsByRevision() + => RevisionsByFields[Header.StandardizedRevision]; + + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { ParameterSetId, diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid0020.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid0020.cs index c50e0eaf..5efb8afb 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid0020.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid0020.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.ParameterSet { @@ -7,7 +8,7 @@ namespace OpenProtocolInterpreter.ParameterSet /// This message gives the possibility to reset the batch counter of the running parameter set, at run time. /// Message sent by: Integrator /// - /// Answer: Command accepted or + /// Answer: Command accepted or /// Command error, Invalid data, or Parameter set not running /// /// @@ -17,11 +18,8 @@ public class Mid0020 : Mid, IParameterSet, IIntegrator, IAcceptableCommand, IDec public IEnumerable DocumentedPossibleErrors => new Error[] { Error.InvalidData, Error.ParameterSetNotRunning }; - public int ParameterSetId - { - get => GetField(1, DataFields.ParameterSetId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.ParameterSetId).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 3, HasPrefix = false)] + public int ParameterSetId { get; set; } public Mid0020() : this(new Header() { @@ -35,19 +33,7 @@ public Mid0020(Header header) : base(header) { } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.ParameterSetId, 20, 3, false) - } - } - }; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { ParameterSetId diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid0022.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid0022.cs index 5a1419b7..7ad33ae3 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid0022.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid0022.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System; namespace OpenProtocolInterpreter.ParameterSet { @@ -12,15 +12,12 @@ public class Mid0022 : Mid, IParameterSet, IController, IAcknowledgeable GetField(1, DataFields.RelayStatus).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.RelayStatus).SetValue(OpenProtocolConvert.ToString, value); - } + [BooleanDataFieldDefinition(revision: 1, field: 1, Index = 20, HasPrefix = false)] + public bool RelayStatus { get; set; } public Mid0022() : this(new Header() { - Mid = MID, + Mid = MID, Revision = DEFAULT_REVISION }) { @@ -30,19 +27,7 @@ public Mid0022(Header header) : base(header) { } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Boolean(DataFields.RelayStatus, 20, false) - } - } - }; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { RelayStatus diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid2504.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid2504.cs index f71e07ef..e5621417 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid2504.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid2504.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.ParameterSet { @@ -16,15 +17,12 @@ public class Mid2504 : Mid, IParameterSet, IIntegrator, IAcceptableCommand, IDec public IEnumerable DocumentedPossibleErrors => new Error[] { Error.ParameterSetIdNotPresent }; - public int ParameterSetId - { - get => GetField(1, DataFields.ParameterSetId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.ParameterSetId).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 3, HasPrefix = false)] + public int ParameterSetId { get; set; } public Mid2504() : this(new Header() { - Mid = MID, + Mid = MID, Revision = DEFAULT_REVISION }) { @@ -35,19 +33,7 @@ public Mid2504(Header header) : base(header) { } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.ParameterSetId, 20, 3, false) - } - } - }; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { ParameterSetId diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid2505.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid2505.cs index f68a4779..52749130 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid2505.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid2505.cs @@ -1,6 +1,7 @@ using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; namespace OpenProtocolInterpreter.ParameterSet { @@ -12,7 +13,7 @@ namespace OpenProtocolInterpreter.ParameterSet /// Command error, Dynamic Pset cannot be created, non-existing pset /// /// - /// A dynamic pset is created from a preexisting Pset in the Controller and selected for tightenings. + /// A dynamic pset is created from a preexisting Pset in the Controller and selected for tightenings. /// The message can substitute Pset selection, Set Identifier, Reset All Identifier, Reset Latest Identifier, Set Batch Size, Disable Tool and Enable Tool. /// /// @@ -22,16 +23,13 @@ public class Mid2505 : Mid, IParameterSet, IIntegrator, IAcceptableCommand, IDec public IEnumerable DocumentedPossibleErrors => Enumerable.Empty(); - public int ParameterSetId - { - get => GetField(1, DataFields.ParameterSetId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.ParameterSetId).SetValue(OpenProtocolConvert.ToString, value); - } - public int NumberOfParameterDataFields - { - get => GetField(1, DataFields.NumberOfParameterDataFields).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.NumberOfParameterDataFields).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 3, HasPrefix = false)] + public int ParameterSetId { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 23, Size = 3, HasPrefix = false)] + public int NumberOfParameterDataFields { get; set; } + + [VariableDataFieldCollectionDefinition(revision: 1, field: 3, Index = 26, HasPrefix = false)] public List VariableDataFields { get; set; } public Mid2505() : this(DEFAULT_REVISION) @@ -54,35 +52,21 @@ public Mid2505(Header header) : base(header) public override string Pack() { - GetField(1, DataFields.DataFields).Value = OpenProtocolConvert.ToString(VariableDataFields); + NumberOfParameterDataFields = VariableDataFields?.Count ?? 0; //Enforce list size even if modified + GetField(revision: 1, field: 3).Size = VariableDataFields?.Sum(x => x.TotalSize) ?? 0; //Enforce size of variable data fields return base.Pack(); } - public override Mid Parse(ReadOnlySpan package) - { - Header = ProcessHeader(package); - var dataFieldsField = GetField(1, DataFields.DataFields); - dataFieldsField.Size = Header.Length - dataFieldsField.Index; - ProcessDataFields(package); - VariableDataFields = VariableDataField.ParseAll(dataFieldsField.Value).ToList(); - return this; - } - - protected override Dictionary> RegisterDatafields() + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - return new Dictionary>() + if (dataField.Field == 3) //VariableDataFields { - { - 1, new List() - { - DataField.Number(DataFields.ParameterSetId, 20, 3, false), - DataField.Number(DataFields.NumberOfParameterDataFields, 23, 3, false), - DataField.Volatile(DataFields.DataFields, 26, false) - } - } - }; + dataField.Size = Header.Length - dataField.Index; + } + base.ProcessDataField(dataField, package); } + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { ParameterSetId, diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid2506.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid2506.cs index 8bdc6a40..a34d743f 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid2506.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid2506.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.ParameterSet { @@ -19,16 +20,11 @@ public class Mid2506 : Mid, IParameterSet, IIntegrator, IAcceptableCommand, IDec public IEnumerable DocumentedPossibleErrors => []; - public int ProgramId - { - get => GetField(1, DataFields.ProgramId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.ProgramId).SetValue(OpenProtocolConvert.ToString, value); - } - public NodeType NodeType - { - get => (NodeType)GetField(1, DataFields.NodeType).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.NodeType).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 4, HasPrefix = false)] + public int ProgramId { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 24, Size = 3, HasPrefix = false)] + public NodeType NodeType { get; set; } public Mid2506() : this(new Header() { @@ -44,20 +40,7 @@ public Mid2506(Header header) : base(header) } - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.ProgramId, 20, 4, false), - DataField.Number(DataFields.NodeType, 24, 3, false), - } - } - }; - } - + [Obsolete("Use DataFieldDefinition attributes instead")] protected enum DataFields { ProgramId, diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0902.cs b/src/OpenProtocolInterpreter/Tightening/Mid0902.cs index 2f185633..1389eb7a 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0902.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0902.cs @@ -47,11 +47,13 @@ public Mid0902() : this(new Header() public Mid0902(Header header) : base(header) { + VariableDataFields = []; } public override string Pack() { NumberOfPIDs = VariableDataFields?.Count ?? 0; //Enforce list size even if modified + GetField(revision: 1, field: 7).Size = VariableDataFields?.Sum(x => x.TotalSize) ?? 0; //Enforce size of variable data fields return base.Pack(); } diff --git a/src/OpenProtocolInterpreter/VariableDataField.cs b/src/OpenProtocolInterpreter/VariableDataField.cs index 21c90593..eddaf5b0 100644 --- a/src/OpenProtocolInterpreter/VariableDataField.cs +++ b/src/OpenProtocolInterpreter/VariableDataField.cs @@ -9,6 +9,8 @@ namespace OpenProtocolInterpreter /// public class VariableDataField { + private const int DefaultSize = 17; + public int ParameterId { get; set; } public int Length { get; set; } public DataTypeDefinition DataType { get; set; } @@ -16,6 +18,8 @@ public class VariableDataField public int StepNumber { get; set; } public string DataValue { get; set; } + internal int TotalSize => DefaultSize + Length; + public object Convert() { switch (DataType) From 001de968b05acaad652b9ad21c84d8f1aad798c4 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Thu, 16 Jul 2026 00:02:46 -0300 Subject: [PATCH 47/78] Updated plc user data to use datafield attributes --- .../PLCUserData/Mid0240.cs | 45 ++++--------- .../PLCUserData/Mid0242.cs | 43 ++++-------- .../PLCUserData/Mid0245.cs | 65 +++++-------------- 3 files changed, 44 insertions(+), 109 deletions(-) diff --git a/src/OpenProtocolInterpreter/PLCUserData/Mid0240.cs b/src/OpenProtocolInterpreter/PLCUserData/Mid0240.cs index f71d7604..d99d79b3 100644 --- a/src/OpenProtocolInterpreter/PLCUserData/Mid0240.cs +++ b/src/OpenProtocolInterpreter/PLCUserData/Mid0240.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace OpenProtocolInterpreter.PLCUserData { @@ -8,7 +8,7 @@ namespace OpenProtocolInterpreter.PLCUserData /// Used by the integrator to send user data input to the PLC. /// Message sent by: Integrator /// - /// Answer: Command accepted or + /// Answer: Command accepted or /// Command error, Invalid data, or Controller is not a sync master/station controller /// /// @@ -18,11 +18,8 @@ public class Mid0240 : Mid, IPLCUserData, IIntegrator, IAcceptableCommand, IDecl public IEnumerable DocumentedPossibleErrors => new Error[] { Error.InvalidData, Error.ControllerIsNotASyncMasterOrStationController }; - public string UserData - { - get => GetField(1, DataFields.UserData).Value; - set => GetField(1, DataFields.UserData).SetValue(value); - } + [StringDataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 200, HasPrefix = false)] + public string UserData { get; set; } public Mid0240() : base(MID, DEFAULT_REVISION) { } @@ -32,40 +29,22 @@ public Mid0240(Header header) : base(header) public override string Pack() { - var userDataField = GetField(1, DataFields.UserData); - if (userDataField.Value.Length > 200) + if (UserData.Length > 200) { - userDataField.Value = userDataField.Value.Substring(0, 200); + UserData = UserData.SafeSubstring(0, 200); } - userDataField.Size = userDataField.Value.Length; + GetField(revision: 1, field: 1).Size = UserData.Length; //Enforce size of user data return base.Pack(); } - public override Mid Parse(ReadOnlySpan package) - { - Header = ProcessHeader(package); - GetField(1, DataFields.UserData).Size = Header.Length - 20; - ProcessDataFields(package); - return this; - } - - protected override Dictionary> RegisterDatafields() + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - return new Dictionary>() + if (dataField.Field == 1) //UserData { - { - 1, new List() - { - DataField.Volatile(DataFields.UserData, 20, false) - } - } - }; - } - - internal enum DataFields - { - UserData + dataField.Size = Header.Length - dataField.Index; + } + base.ProcessDataField(dataField, package); } } } diff --git a/src/OpenProtocolInterpreter/PLCUserData/Mid0242.cs b/src/OpenProtocolInterpreter/PLCUserData/Mid0242.cs index dd1e1ecb..6f27013e 100644 --- a/src/OpenProtocolInterpreter/PLCUserData/Mid0242.cs +++ b/src/OpenProtocolInterpreter/PLCUserData/Mid0242.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace OpenProtocolInterpreter.PLCUserData { @@ -13,11 +13,8 @@ public class Mid0242 : Mid, IPLCUserData, IController, IAcknowledgeable { public const int MID = 242; - public string UserData - { - get => GetField(1, DataFields.UserData).Value; - set => GetField(1, DataFields.UserData).SetValue(value); - } + [StringDataFieldDefinition(revision: 1, field: 1, Index = 20, HasPrefix = false)] + public string UserData { get; set; } public Mid0242() : base(MID, DEFAULT_REVISION) { @@ -30,34 +27,22 @@ public Mid0242(Header header) : base(header) public override string Pack() { - GetField(1, DataFields.UserData).Size = UserData.Length; - return base.Pack(); - } + if (UserData.Length > 200) + { + UserData = UserData.SafeSubstring(0, 200); + } - public override Mid Parse(ReadOnlySpan package) - { - Header = ProcessHeader(package); - GetField(1, DataFields.UserData).Size = Header.Length - 20; - ProcessDataFields(package); - return this; + GetField(revision: 1, field: 1).Size = UserData.Length; //Enforce size of user data + return base.Pack(); } - protected override Dictionary> RegisterDatafields() + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - return new Dictionary>() + if (dataField.Field == 1) //UserData { - { - 1, new List() - { - DataField.Volatile(DataFields.UserData, 20, false) - } - } - }; - } - - internal enum DataFields - { - UserData + dataField.Size = Header.Length - dataField.Index; + } + base.ProcessDataField(dataField, package); } } } diff --git a/src/OpenProtocolInterpreter/PLCUserData/Mid0245.cs b/src/OpenProtocolInterpreter/PLCUserData/Mid0245.cs index 88b239f9..48c11ffa 100644 --- a/src/OpenProtocolInterpreter/PLCUserData/Mid0245.cs +++ b/src/OpenProtocolInterpreter/PLCUserData/Mid0245.cs @@ -1,14 +1,14 @@ using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace OpenProtocolInterpreter.PLCUserData { /// /// User data download with offset /// - /// Used by the integrator to send user data input to the PLC. + /// Used by the integrator to send user data input to the PLC. /// The difference compared to User data download is that with this MID it is possible - /// to specify an offset for the data written in the PLC. This makes it possible to have more than + /// to specify an offset for the data written in the PLC. This makes it possible to have more than /// one device writing user data to the PLC on different data areas. /// /// @@ -19,14 +19,14 @@ namespace OpenProtocolInterpreter.PLCUserData /// characters in the telegram. /// /// - /// Only data that is sent in the user data field will be written to the PLC, the remaining data will + /// Only data that is sent in the user data field will be written to the PLC, the remaining data will /// be untouched. /// /// /// This means for example that if the offset is 10 and the user data is 1234 the bytes with /// address 13010 and 13011 will be updated (to 0x12 and 0x34) and the rest of the area will be /// unchanged. - /// + /// /// Message sent by: Integrator /// /// Answer: Command accepted or @@ -46,16 +46,11 @@ public class Mid0245 : Mid, IPLCUserData, IIntegrator, IAcceptableCommand, IDecl Error.MidRevisionUnsupported }; - public int Offset - { - get => GetField(1, DataFields.Offset).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.Offset).SetValue(OpenProtocolConvert.ToString, value); - } - public string UserData - { - get => GetField(1, DataFields.UserData).Value; - set => GetField(1, DataFields.UserData).SetValue(value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 3, HasPrefix = false)] + public int Offset { get; set; } + + [StringDataFieldDefinition(revision: 1, field: 2, Index = 23, Size = 2, HasPrefix = false)] + public string UserData { get; set; } public Mid0245() : this(DEFAULT_REVISION) { @@ -76,46 +71,22 @@ public Mid0245(Header header) : base(header) public override string Pack() { - var userDataField = GetField(1, DataFields.UserData); - if (string.IsNullOrEmpty(userDataField.Value)) - { - userDataField.Value = " "; - } - else if (userDataField.Value.Length > 200) + if (UserData.Length > 200) { - userDataField.Value = userDataField.Value.Substring(0, 200); + UserData = UserData.SafeSubstring(0, 200); } - userDataField.Size = userDataField.Value.Length; + GetField(revision: 1, field: 2).Size = UserData.Length; //Enforce size of user data return base.Pack(); } - public override Mid Parse(ReadOnlySpan package) + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - Header = ProcessHeader(package); - GetField(1, DataFields.UserData).Size = Header.Length - 23; - ProcessDataFields(package); - return this; - } - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() + if (dataField.Field == 2) //UserData { - { - 1, new List() - { - DataField.Number(DataFields.Offset, 20, 3, false), - DataField.Volatile(DataFields.UserData, 23, false) - } - } - }; - } - - internal enum DataFields - { - Offset, - UserData + dataField.Size = Header.Length - dataField.Index; + } + base.ProcessDataField(dataField, package); } } } From 7122d917e23ea30c8d9f71551fc1ad6dfe41dc5e Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Mon, 27 Jul 2026 22:27:33 -0300 Subject: [PATCH 48/78] PowerMACS mids using datafield attributes --- src/OpenProtocolInterpreter/Alarm/Mid0071.cs | 10 +- .../Communication/Mid0004.cs | 3 +- src/OpenProtocolInterpreter/DataTypes.cs | 3 +- .../IOInterface/Mid0215.cs | 3 +- .../Job/Advanced/Mid0140.cs | 3 +- src/OpenProtocolInterpreter/Job/Mid0031.cs | 3 +- src/OpenProtocolInterpreter/Job/Mid0032.cs | 3 +- src/OpenProtocolInterpreter/Job/Mid0033.cs | 3 +- src/OpenProtocolInterpreter/Job/Mid0035.cs | 3 +- src/OpenProtocolInterpreter/Job/Mid0038.cs | 3 +- src/OpenProtocolInterpreter/Job/Mid0039.cs | 3 +- src/OpenProtocolInterpreter/MID.cs | 24 +- .../OpenProtocolConvert.cs | 3 + .../ParameterSet/Mid0015.cs | 3 +- .../ParameterSet/Mid0019.cs | 3 +- .../PowerMACS/BoltData.cs | 83 +++- .../PowerMACS/BoltResult.cs | 84 +++- .../PowerMACS/Mid0105.cs | 44 +- .../PowerMACS/Mid0106.cs | 303 ++++---------- .../PowerMACS/Mid0107.cs | 378 +++++------------- .../PowerMACS/Mid0108.cs | 29 +- .../PowerMACS/SpecialValue.cs | 56 +++ .../PowerMACS/StepResult.cs | 86 +++- src/OpenProtocolInterpreter/Result/Mid1201.cs | 9 +- src/OpenProtocolInterpreter/Result/Mid1202.cs | 15 +- .../Tightening/Mid0061.cs | 3 +- .../Tightening/Mid0065.cs | 3 +- .../Tightening/Mid0066.cs | 3 +- src/OpenProtocolInterpreter/Tool/Mid0704.cs | 7 +- 29 files changed, 564 insertions(+), 612 deletions(-) diff --git a/src/OpenProtocolInterpreter/Alarm/Mid0071.cs b/src/OpenProtocolInterpreter/Alarm/Mid0071.cs index 48fb9df1..ce7380df 100644 --- a/src/OpenProtocolInterpreter/Alarm/Mid0071.cs +++ b/src/OpenProtocolInterpreter/Alarm/Mid0071.cs @@ -13,17 +13,17 @@ public class Mid0071 : Mid, IAlarm, IController, IAcknowledgeable { public const int MID = 71; - [StringDataFieldDefinition(field: 0, revision: 1, Size = 4, PaddingOrientation = PaddingOrientation.LeftPadded)] + [StringDataFieldDefinition(field: 1, revision: 1, Size = 4, PaddingOrientation = PaddingOrientation.LeftPadded)] public string ErrorCode { get; set; } - [BooleanDataFieldDefinition(field: 1, revision: 1)] - public bool ControllerReadyStatus { get; set; } [BooleanDataFieldDefinition(field: 2, revision: 1)] + public bool ControllerReadyStatus { get; set; } + [BooleanDataFieldDefinition(field: 3, revision: 1)] public bool ToolReadyStatus { get; set; } - [TimestampDataFieldDefinition(field: 3, revision: 1)] + [TimestampDataFieldDefinition(field: 4, revision: 1)] public DateTime Time { get; set; } - [StringDataFieldDefinition(field: 4, revision: 2, Index = 54, Size = 50)] //Always has index 54 due to error code field size change + [StringDataFieldDefinition(field: 5, revision: 2, Index = 54, Size = 50)] //Always has index 54 due to error code field size change public string AlarmText { get; set; } public Mid0071() : this(DEFAULT_REVISION) diff --git a/src/OpenProtocolInterpreter/Communication/Mid0004.cs b/src/OpenProtocolInterpreter/Communication/Mid0004.cs index 119da5cb..36178714 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0004.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0004.cs @@ -69,8 +69,7 @@ public override string Pack() } var builder = new StringBuilder(BuildHeader()); - int prefixIndex = 1; - builder.Append(base.Pack(dataFields, ref prefixIndex)); + builder.Append(base.Pack(dataFields)); return builder.ToString(); } diff --git a/src/OpenProtocolInterpreter/DataTypes.cs b/src/OpenProtocolInterpreter/DataTypes.cs index d758e8fb..fb4de338 100644 --- a/src/OpenProtocolInterpreter/DataTypes.cs +++ b/src/OpenProtocolInterpreter/DataTypes.cs @@ -38,6 +38,7 @@ public string ValueSentInTelegram return _valueSent.ToString().PadLeft(2, '0'); } } + public string Type { get; set; } public int Length { get; set; } @@ -50,7 +51,7 @@ private DataType(int valueSent, string type, int length) public static implicit operator DataType(string type) { - return _dataTypesDefinition.FirstOrDefault(x=> x == type); + return _dataTypesDefinition.FirstOrDefault(x => x == type); } public static bool operator ==(DataType type, string type2) diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs index e260e55a..6555c667 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs @@ -90,10 +90,9 @@ public override string Pack() digitalInputsField.Size = NumberOfDigitalInputs * 4; var builder = new StringBuilder(BuildHeader()); - int prefixIndex = 1; var fields = OrderedDataFieldsByRevision().ToList(); - builder.Append(base.Pack(fields, ref prefixIndex)); + builder.Append(base.Pack(fields)); return builder.ToString(); } else diff --git a/src/OpenProtocolInterpreter/Job/Advanced/Mid0140.cs b/src/OpenProtocolInterpreter/Job/Advanced/Mid0140.cs index 890379c0..d2fd6387 100644 --- a/src/OpenProtocolInterpreter/Job/Advanced/Mid0140.cs +++ b/src/OpenProtocolInterpreter/Job/Advanced/Mid0140.cs @@ -187,9 +187,8 @@ public override string Pack() { var builder = new StringBuilder(); var fields = DataFieldsByRevision().OrderBy(f => f.Index).ToList(); - int prefixIndex = 1; builder.Append(BuildHeader()); - builder.Append(Pack(fields, ref prefixIndex)); + builder.Append(Pack(fields)); return builder.ToString(); } diff --git a/src/OpenProtocolInterpreter/Job/Mid0031.cs b/src/OpenProtocolInterpreter/Job/Mid0031.cs index b7382a59..f8ff5389 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0031.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0031.cs @@ -77,8 +77,7 @@ public override string Pack() var fields = RevisionsByFields[Header.StandardizedRevision]; var builder = new StringBuilder(BuildHeader()); - int prefixIndex = 1; - builder.Append(Pack(fields, ref prefixIndex)); + builder.Append(Pack(fields)); return builder.ToString(); } diff --git a/src/OpenProtocolInterpreter/Job/Mid0032.cs b/src/OpenProtocolInterpreter/Job/Mid0032.cs index 0ce509bf..48d52bce 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0032.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0032.cs @@ -59,10 +59,9 @@ public override string Pack() var builder = new StringBuilder(BuildHeader()); var fromRevision = Header.StandardizedRevision > 1 ? 2 : 1; - int prefixIndex = 1; for (int i = fromRevision; i <= Header.StandardizedRevision; i++) { - builder.Append(Pack(i, ref prefixIndex)); + builder.Append(Pack(i)); } return builder.ToString(); } diff --git a/src/OpenProtocolInterpreter/Job/Mid0033.cs b/src/OpenProtocolInterpreter/Job/Mid0033.cs index 7f2a38b4..3386f355 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0033.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0033.cs @@ -114,9 +114,8 @@ public override string Pack() NumberOfParameterSets = ParameterSetList?.Count ?? 0; GetField(nameof(ParameterSetList)).Size = NumberOfParameterSets * ParameterSet.Size(Header.StandardizedRevision); var builder = new StringBuilder(BuildHeader()); - int prefixIndex = 1; var fields = DataFieldsByRevision().ToList(); - builder.Append(base.Pack(fields, ref prefixIndex)); + builder.Append(base.Pack(fields)); return builder.ToString(); } diff --git a/src/OpenProtocolInterpreter/Job/Mid0035.cs b/src/OpenProtocolInterpreter/Job/Mid0035.cs index 27298cd0..a8776114 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0035.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0035.cs @@ -103,9 +103,8 @@ public override string Pack() { var builder = new StringBuilder(); var fields = DataFieldsByRevision().OrderBy(x => x.Index).ToList(); - int prefixIndex = 1; builder.Append(BuildHeader()); - builder.Append(base.Pack(fields, ref prefixIndex)); + builder.Append(base.Pack(fields)); return builder.ToString(); } diff --git a/src/OpenProtocolInterpreter/Job/Mid0038.cs b/src/OpenProtocolInterpreter/Job/Mid0038.cs index 142fff0d..8f70a5a4 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0038.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0038.cs @@ -54,8 +54,7 @@ public override string Pack() { var builder = new StringBuilder(BuildHeader()); - int prefixIndex = 1; - builder.Append(base.Pack(RevisionsByFields[Header.StandardizedRevision], ref prefixIndex)); + builder.Append(base.Pack(RevisionsByFields[Header.StandardizedRevision])); return builder.ToString(); } diff --git a/src/OpenProtocolInterpreter/Job/Mid0039.cs b/src/OpenProtocolInterpreter/Job/Mid0039.cs index 016ad35a..63faef37 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0039.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0039.cs @@ -54,8 +54,7 @@ public override string Pack() { var builder = new StringBuilder(BuildHeader()); - int prefixIndex = 1; - builder.Append(base.Pack(RevisionsByFields[Header.StandardizedRevision], ref prefixIndex)); + builder.Append(base.Pack(RevisionsByFields[Header.StandardizedRevision])); return builder.ToString(); } diff --git a/src/OpenProtocolInterpreter/MID.cs b/src/OpenProtocolInterpreter/MID.cs index 3b686b50..a171bc6c 100644 --- a/src/OpenProtocolInterpreter/MID.cs +++ b/src/OpenProtocolInterpreter/MID.cs @@ -66,11 +66,10 @@ public virtual string Pack() return header; var builder = new StringBuilder(header); - int prefixIndex = 1; var revision = Header.StandardizedRevision; - for (int i = 1; i <= revision; i++) + for (int rev = 1; rev <= revision; rev++) { - builder.Append(Pack(i, ref prefixIndex)); + builder.Append(Pack(rev)); } return builder.ToString(); @@ -78,17 +77,17 @@ public virtual string Pack() public virtual byte[] PackBytes() => Encoding.ASCII.GetBytes(Pack()); - protected virtual string Pack(int revision, ref int prefixIndex) + protected virtual string Pack(int revision) { if (!RevisionsByFields.TryGetValue(revision, out var dataFields)) { return string.Empty; } - return Pack(dataFields, ref prefixIndex); + return Pack(dataFields); } - protected virtual string Pack(List dataFields, ref int prefixIndex) + protected virtual string Pack(IEnumerable dataFields) { var builder = new StringBuilder(); foreach (var dataField in dataFields) @@ -98,8 +97,7 @@ protected virtual string Pack(List dataFields, ref int prefixIndex) if (dataField.HasPrefix) { - builder.Append(prefixIndex.ToString("D2")); - prefixIndex++; + builder.Append(dataField.Field.ToString("D2")); } builder.Append(dataField.Value); @@ -295,6 +293,16 @@ protected DataField GetField(int revision, int field) return fields.FirstOrDefault(x => x.Field == field) ?? DataField.Default; } + protected DataField GetField(int revision, int field) + { + if (!RevisionsByFields.TryGetValue(revision, out var fields)) + { + throw new ArgumentException("No fields registered for the specified revision.", nameof(revision)); + } + + return (DataField)fields.FirstOrDefault(x => x.GetType() == typeof(DataField) && x.Field == field); + } + protected DataField GetField(string propertyName) { var type = GetType(); diff --git a/src/OpenProtocolInterpreter/OpenProtocolConvert.cs b/src/OpenProtocolInterpreter/OpenProtocolConvert.cs index d54f327a..8d2c4c51 100644 --- a/src/OpenProtocolInterpreter/OpenProtocolConvert.cs +++ b/src/OpenProtocolInterpreter/OpenProtocolConvert.cs @@ -66,6 +66,9 @@ public static string ToString(char paddingChar, int size, PaddingOrientation ori return TruncatePadded(paddingChar, size, orientation, str); } + public static decimal ToDecimal(ReadOnlySpan value) + => ToDecimal(value.ToString()); + public static decimal ToDecimal(string value) { decimal decimalValue = 0; diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid0015.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid0015.cs index a404c5a0..7337671c 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid0015.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid0015.cs @@ -120,9 +120,8 @@ public override string Pack() var builder = new StringBuilder(); var fields = DataFieldsByRevision().OrderBy(f => f.Field).ToList(); - int prefixIndex = 1; builder.Append(BuildHeader()); - builder.Append(Pack(fields, ref prefixIndex)); + builder.Append(Pack(fields)); return builder.ToString(); } diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid0019.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid0019.cs index db347ddd..50047a4a 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid0019.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid0019.cs @@ -57,9 +57,8 @@ public override string Pack() var builder = new StringBuilder(); var fields = DataFieldsByRevision().ToList(); - int prefixIndex = 1; builder.Append(BuildHeader()); - builder.Append(Pack(fields, ref prefixIndex)); + builder.Append(Pack(fields)); return builder.ToString(); } diff --git a/src/OpenProtocolInterpreter/PowerMACS/BoltData.cs b/src/OpenProtocolInterpreter/PowerMACS/BoltData.cs index 7cd69340..8ad4da59 100644 --- a/src/OpenProtocolInterpreter/PowerMACS/BoltData.cs +++ b/src/OpenProtocolInterpreter/PowerMACS/BoltData.cs @@ -1,4 +1,9 @@ -namespace OpenProtocolInterpreter.PowerMACS +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +namespace OpenProtocolInterpreter.PowerMACS { /// /// Represents a Bolt entity @@ -16,4 +21,80 @@ public class BoltData public decimal BoltAngleHighLimit { get; set; } public decimal BoltAngleLowLimit { get; set; } } + + public class BoltDataCollectionDefinitionAttribute : DataFieldDefinitionAttribute + { + public int EachFieldSize { get; set; } = 67; + + public BoltDataCollectionDefinitionAttribute(int revision) : base(revision) + { + + } + public BoltDataCollectionDefinitionAttribute(int field, int revision) : base(field, revision) + { + + } + + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + { + return new DataField>(Field, index, Size, HasPrefix) + { + DefaultConverter = PackBoltData, + DefaultParser = ParseBoltData + }.Bind(mid, propertyInfo); + } + + private static string PackBoltData(char paddingChar, int size, PaddingOrientation orientation, List boltsData) + { + var builder = new StringBuilder(); + foreach (var bolt in boltsData) + { + builder.Append($"13{OpenProtocolConvert.ToString('0', 2, PaddingOrientation.LeftPadded, bolt.OrdinalBoltNumber)}"); + builder.Append($"14{OpenProtocolConvert.ToString(bolt.SimpleBoltStatus)}"); + builder.Append($"15{OpenProtocolConvert.ToString(bolt.TorqueStatus)}"); + builder.Append($"16{OpenProtocolConvert.ToString(bolt.AngleStatus)}"); + builder.Append($"17{OpenProtocolConvert.ToString('0', 7, PaddingOrientation.RightPadded, bolt.BoltTorque)}"); + builder.Append($"18{OpenProtocolConvert.ToString('0', 7, PaddingOrientation.RightPadded, bolt.BoltAngle)}"); + builder.Append($"19{OpenProtocolConvert.ToString('0', 7, PaddingOrientation.RightPadded, bolt.BoltTorqueHighLimit)}"); + builder.Append($"20{OpenProtocolConvert.ToString('0', 7, PaddingOrientation.RightPadded, bolt.BoltTorqueLowLimit)}"); + builder.Append($"21{OpenProtocolConvert.ToString('0', 7, PaddingOrientation.RightPadded, bolt.BoltAngleHighLimit)}"); + builder.Append($"22{OpenProtocolConvert.ToString('0', 7, PaddingOrientation.RightPadded, bolt.BoltAngleLowLimit)}"); + } + + return builder.ToString(); + } + + private List ParseBoltData(string value) + { + var list = new List(); + if (string.IsNullOrWhiteSpace(value)) + { + return list; + } + + var numberOfBolts = value.Length / EachFieldSize; + var span = value.AsSpan(); + for (int i = 0; i < numberOfBolts; i++) + { + var bolt = span.Slice(i * EachFieldSize, EachFieldSize); + var obj = new BoltData() + { + OrdinalBoltNumber = OpenProtocolConvert.ToInt32(bolt.Slice(2, 2)), + SimpleBoltStatus = OpenProtocolConvert.ToBoolean(bolt.Slice(6, 1)), + TorqueStatus = (TorqueStatus)OpenProtocolConvert.ToInt32(bolt.Slice(9, 1)), + AngleStatus = (AngleStatus)OpenProtocolConvert.ToInt32(bolt.Slice(12, 1)), + BoltTorque = OpenProtocolConvert.ToDecimal(bolt.Slice(15, 7)), + BoltAngle = OpenProtocolConvert.ToDecimal(bolt.Slice(24, 7)), + BoltTorqueHighLimit = OpenProtocolConvert.ToDecimal(bolt.Slice(33, 7)), + BoltTorqueLowLimit = OpenProtocolConvert.ToDecimal(bolt.Slice(42, 7)), + BoltAngleHighLimit = OpenProtocolConvert.ToDecimal(bolt.Slice(51, 7)), + BoltAngleLowLimit = OpenProtocolConvert.ToDecimal(bolt.Slice(60, 7)), + }; + + list.Add(obj); + } + + return list; + } + } } diff --git a/src/OpenProtocolInterpreter/PowerMACS/BoltResult.cs b/src/OpenProtocolInterpreter/PowerMACS/BoltResult.cs index efbb3722..7f5d27b5 100644 --- a/src/OpenProtocolInterpreter/PowerMACS/BoltResult.cs +++ b/src/OpenProtocolInterpreter/PowerMACS/BoltResult.cs @@ -1,4 +1,10 @@ -namespace OpenProtocolInterpreter.PowerMACS +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace OpenProtocolInterpreter.PowerMACS { /// /// Represents a Bolt Result entity @@ -9,4 +15,80 @@ public class BoltResult public DataType Type { get; set; } public object Value { get; set; } } + + public class BoltResultCollectionDefinitionAttribute : DataFieldDefinitionAttribute + { + public BoltResultCollectionDefinitionAttribute(int revision) : base(revision) + { + + } + public BoltResultCollectionDefinitionAttribute(int field, int revision) : base(field, revision) + { + + } + + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + { + return new DataField>(Field, index, Size, HasPrefix) + { + DefaultConverter = PackBoltResult, + DefaultParser = ParseBoltResult + }.Bind(mid, propertyInfo); + } + + private static string PackBoltResult(char paddingChar, int size, PaddingOrientation orientation, List boltResults) + { + var builder = new StringBuilder(); + foreach (var bolt in boltResults) + { + builder.Append(OpenProtocolConvert.TruncatePadded(' ', 20, PaddingOrientation.RightPadded, bolt.VariableName)); + builder.Append(bolt.Type.Type); + if (DataType.DataTypes[1].Type.Equals(bolt.Type.Type)) // Integer + { + builder.Append(OpenProtocolConvert.ToString('0', 7, PaddingOrientation.LeftPadded, (int)bolt.Value)); + } + else if (DataType.DataTypes[2].Type.Equals(bolt.Type.Type)) // Decimal + { + builder.Append(OpenProtocolConvert.ToString('0', 7, PaddingOrientation.LeftPadded, (decimal)bolt.Value)); + } + } + + return builder.ToString(); + } + + + private static List ParseBoltResult(string value) + { + var list = new List(); + if (string.IsNullOrWhiteSpace(value)) + { + return list; + } + + var section = value.AsSpan(); + for (int i = 0; i < section.Length; i += 29) + { + var type = section.Slice(20 + i, 2).ToString(); + var result = new BoltResult() + { + VariableName = section.Slice(i, 20).ToString(), + Type = DataType.DataTypes.First(x => x == type) + }; + + var resultValue = section.Slice(22 + i, 7); + if (result.Type.Type == DataType.DataTypes[1].Type) // Integer + { + result.Value = OpenProtocolConvert.ToInt32(resultValue); + } + else if (result.Type.Type == DataType.DataTypes[2].Type) // Decimal + { + result.Value = OpenProtocolConvert.ToDecimal(resultValue); + } + + list.Add(result); + } + + return list; + } + } } diff --git a/src/OpenProtocolInterpreter/PowerMACS/Mid0105.cs b/src/OpenProtocolInterpreter/PowerMACS/Mid0105.cs index 13b2dc90..94125b76 100644 --- a/src/OpenProtocolInterpreter/PowerMACS/Mid0105.cs +++ b/src/OpenProtocolInterpreter/PowerMACS/Mid0105.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace OpenProtocolInterpreter.PowerMACS { @@ -15,7 +16,7 @@ namespace OpenProtocolInterpreter.PowerMACS /// /// Message sent by: Integrator /// - /// Answer: Command accepted or + /// Answer: Command accepted or /// Command error, Subscription already exists or MID revision unsupported /// /// @@ -25,16 +26,10 @@ public class Mid0105 : Mid, IPowerMACS, IIntegrator, ISubscription, IAcceptableC public IEnumerable DocumentedPossibleErrors => new Error[] { Error.SubscriptionAlreadyExists, Error.MidRevisionUnsupported }; - public int DataNumberSystem - { - get => GetField(2, DataFields.DataNumberSystem).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.DataNumberSystem).SetValue(OpenProtocolConvert.ToString, value); - } - public bool SendOnlyNewData - { - get => GetField(3, DataFields.SendOnlyNewData).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(3, DataFields.SendOnlyNewData).SetValue(OpenProtocolConvert.ToString, value); - } + [Int64DataFieldDefinition(revision: 2, field: 1, Index = 20, Size = 10, HasPrefix = false)] + public long DataNumberSystem { get; set; } + [BooleanDataFieldDefinition(revision: 3, field: 2, Index = 30, HasPrefix = false)] + public bool SendOnlyNewData { get; set; } public Mid0105() : this(DEFAULT_REVISION) { @@ -58,30 +53,5 @@ public Mid0105(int revision, bool noAckFlag = false) : this(new Header() }) { } - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 2, new List() - { - DataField.Number(DataFields.DataNumberSystem, 20, 10, false), - } - }, - { - 3, new List() - { - DataField.Boolean(DataFields.SendOnlyNewData, 30, false) - } - }, - }; - } - - protected enum DataFields - { - DataNumberSystem, - SendOnlyNewData - } } } diff --git a/src/OpenProtocolInterpreter/PowerMACS/Mid0106.cs b/src/OpenProtocolInterpreter/PowerMACS/Mid0106.cs index 1209b46a..1867250e 100644 --- a/src/OpenProtocolInterpreter/PowerMACS/Mid0106.cs +++ b/src/OpenProtocolInterpreter/PowerMACS/Mid0106.cs @@ -33,78 +33,55 @@ public class Mid0106 : Mid, IPowerMACS, IController, IAcknowledgeable { public const int MID = 106; - public int TotalNumberOfMessages - { - get => GetField(1, DataFields.TotalNumberOfMessages).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.TotalNumberOfMessages).SetValue(OpenProtocolConvert.ToString, value); - } - public int MessageNumber - { - get => GetField(1, DataFields.MessageNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.MessageNumber).SetValue(OpenProtocolConvert.ToString, value); - } - public int DataNumberSystem - { - get => GetField(1, DataFields.DataNumberSystem).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.DataNumberSystem).SetValue(OpenProtocolConvert.ToString, value); - } - public int StationNumber - { - get => GetField(1, DataFields.StationNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.StationNumber).SetValue(OpenProtocolConvert.ToString, value); - } - public string StationName - { - get => GetField(1, DataFields.StationName).Value; - set => GetField(1, DataFields.StationName).SetValue(value); - } - public DateTime Time - { - get => GetField(1, DataFields.Time).GetValue(OpenProtocolConvert.ToDateTime); - set => GetField(1, DataFields.Time).SetValue(OpenProtocolConvert.ToString, value); - } - public int ModeNumber - { - get => GetField(1, DataFields.ModeNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.ModeNumber).SetValue(OpenProtocolConvert.ToString, value); - } - public string ModeName - { - get => GetField(1, DataFields.ModeName).Value; - set => GetField(1, DataFields.ModeName).SetValue(value); - } - public bool SimpleStatus - { - get => GetField(1, DataFields.SimpleStatus).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.SimpleStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public PowerMacsStatus PMStatus - { - get => (PowerMacsStatus)GetField(1, DataFields.PMStatus).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.PMStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public string WpId - { - get => GetField(1, DataFields.WPId).Value; - set => GetField(1, DataFields.WPId).SetValue(value); - } - public int NumberOfBolts - { - get => GetField(1, DataFields.NumberOfBolts).GetValue(OpenProtocolConvert.ToInt32); - private set => GetField(1, DataFields.NumberOfBolts).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 2)] + public int TotalNumberOfMessages { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 24, Size = 2)] + public int MessageNumber { get; set; } + + [Int64DataFieldDefinition(revision: 1, field: 3, Index = 28, Size = 10)] + public long DataNumberSystem { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 4, Index = 40, Size = 2)] + public int StationNumber { get; set; } + + [StringDataFieldDefinition(revision: 1, field: 5, Index = 44, Size = 20)] + public string StationName { get; set; } + + [TimestampDataFieldDefinition(revision: 1, field: 6, Index = 66)] + public DateTime Time { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 7, Index = 87, Size = 2)] + public int ModeNumber { get; set; } + + [StringDataFieldDefinition(revision: 1, field: 8, Index = 91, Size = 20)] + public string ModeName { get; set; } + + [BooleanDataFieldDefinition(revision: 1, field: 9, Index = 113)] + public bool SimpleStatus { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 10, Index = 116, Size = 1)] + public PowerMacsStatus PMStatus { get; set; } + + [StringDataFieldDefinition(revision: 1, field: 11, Index = 119, Size = 40)] + public string WpId { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 12, Index = 161, Size = 2)] + public int NumberOfBolts { get; set; } + + [BoltDataCollectionDefinition(revision: 1, field: 13, Index = 165, HasPrefix = false)] public List BoltsData { get; set; } - public int TotalSpecialValues - { - get => GetField(1, DataFields.NumberOfSpecialValues).GetValue(OpenProtocolConvert.ToInt32); - private set => GetField(1, DataFields.NumberOfSpecialValues).SetValue(OpenProtocolConvert.ToString, value); - } + + //Total Special values has a weird pattern of being attached to the list of special values instead of being a separate field. + //So we do process is in a separate way with special values and set its value and have to do it together with list because each special value + //has a dynamic size, due to that, we cannot make the same processing as Bolt data list that has fixed length of 67 bytes for each bolt data. + public int TotalSpecialValues { get; set; } + + [SpecialValueCollectionDefinition(revision: 1, field: 23, Index = 0, Size = 67)] public List SpecialValues { get; set; } - public SystemSubType SystemSubType - { - get => (SystemSubType)GetField(4, DataFields.SystemSubType).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(4, DataFields.SystemSubType).SetValue(OpenProtocolConvert.ToString, value); - } + + [Int32DataFieldDefinition(revision: 4, field: 24, Index = 0, Size = 3)] + public SystemSubType SystemSubType { get; set; } public Mid0106() : this(DEFAULT_REVISION) { @@ -125,179 +102,67 @@ public Mid0106(int revision) : this(new Header() { } + protected override string BuildHeader() + { + Header.Length = Header.DefaultSize; + var fields = DataFieldsByRevision(); + Header.Length += fields.Sum(x => x.TotalSize); + return Header.ToString(); + } + public override string Pack() { NumberOfBolts = BoltsData.Count; TotalSpecialValues = SpecialValues.Count; - GetField(1, DataFields.BoltData).Value = PackBoltsData(); - GetField(1, DataFields.SpecialValues).Value = PackSpecialValues(); - - var builder = new StringBuilder(BuildHeader()); - int prefixIndex = 1; - for (int i = 1; i <= (Header.Revision > 0 ? Header.Revision : 1); i++) - foreach (var dataField in RevisionsByFields[i]) - { - if (dataField.Field == (int)DataFields.NumberOfSpecialValues) - prefixIndex = 23; - - if (dataField.HasPrefix) - { - builder.Append(prefixIndex.ToString("D2")); - prefixIndex++; - } - - builder.Append(dataField.Value); - } - - return builder.ToString(); + GetField(nameof(BoltsData)).Size = NumberOfBolts * 67; + GetField(nameof(SpecialValues)).Size = 2 + SpecialValues.Sum(x => x.TotalFieldLength); + return base.Pack(); } - public override Mid Parse(ReadOnlySpan package) + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - Header = ProcessHeader(package); - - int numberOfBolts = OpenProtocolConvert.ToInt32(GetValue(GetField(1, DataFields.NumberOfBolts), package).ToString()); - GetField(1, DataFields.BoltData).Size *= numberOfBolts; - - var numberOfSpecialValuesField = GetField(1, DataFields.NumberOfSpecialValues); - numberOfSpecialValuesField.Index = GetField(1, DataFields.BoltData).Index + GetField(1, DataFields.BoltData).Size; - - var specialValues = GetField(1, DataFields.SpecialValues); - specialValues.Index = numberOfSpecialValuesField.Index + numberOfSpecialValuesField.Size + 2; - if (Header.Revision > 3) + if (dataField.Field == 23) { - specialValues.Size = Header.Length - GetField(4, DataFields.SystemSubType).Size - 2; - GetField(4, DataFields.SystemSubType).Index = specialValues.Index + specialValues.Size; + if (Header.Revision > 3) + dataField.Size = Header.Length - GetField(revision: 4, field: 4).TotalSize - 2; + else + dataField.Size = Header.Length - dataField.Index - 2; + + base.ProcessDataField(dataField, package); + TotalSpecialValues = SpecialValues.Count; + return; } - else - { - specialValues.Size = Header.Length - specialValues.Index; - } - - ProcessDataFields(package); - - BoltsData = ParseBoltsData(GetField(1, DataFields.BoltData).Value); - SpecialValues = SpecialValue.ParseAll(GetField(1, DataFields.SpecialValues).Value, TotalSpecialValues, false).ToList(); - return this; - } - protected virtual string PackBoltsData() - { - var builder = new StringBuilder(); - foreach (var bolt in BoltsData) + if (dataField.Field == 13) { - builder.Append($"13{OpenProtocolConvert.ToString('0', 2, PaddingOrientation.LeftPadded, bolt.OrdinalBoltNumber)}"); - builder.Append($"14{OpenProtocolConvert.ToString(bolt.SimpleBoltStatus)}"); - builder.Append($"15{OpenProtocolConvert.ToString(bolt.TorqueStatus)}"); - builder.Append($"16{OpenProtocolConvert.ToString(bolt.AngleStatus)}"); - builder.Append($"17{OpenProtocolConvert.ToString('0', 7, PaddingOrientation.RightPadded, bolt.BoltTorque)}"); - builder.Append($"18{OpenProtocolConvert.ToString('0', 7, PaddingOrientation.RightPadded, bolt.BoltAngle)}"); - builder.Append($"19{OpenProtocolConvert.ToString('0', 7, PaddingOrientation.RightPadded, bolt.BoltTorqueHighLimit)}"); - builder.Append($"20{OpenProtocolConvert.ToString('0', 7, PaddingOrientation.RightPadded, bolt.BoltTorqueLowLimit)}"); - builder.Append($"21{OpenProtocolConvert.ToString('0', 7, PaddingOrientation.RightPadded, bolt.BoltAngleHighLimit)}"); - builder.Append($"22{OpenProtocolConvert.ToString('0', 7, PaddingOrientation.RightPadded, bolt.BoltAngleLowLimit)}"); + dataField.Size = NumberOfBolts * 67; } - return builder.ToString(); + base.ProcessDataField(dataField, package); } - protected virtual string PackSpecialValues() + protected override void ProcessDataFields(ReadOnlySpan package) { - var builder = new StringBuilder(); - foreach (var v in SpecialValues) - { - builder.Append(v.Pack(false)); - } - - return builder.ToString(); + foreach (var field in DataFieldsByRevision()) + ProcessDataField(field, package); } - protected virtual List ParseBoltsData(string value) + private IEnumerable DataFieldsByRevision() { - var list = new List(); - if (string.IsNullOrWhiteSpace(value)) + var previousField = default(DataField); + for (int i = 1; i <= Header.StandardizedRevision; i++) { - return list; - } - - var bolts = new List(); - for (int i = 0; i < NumberOfBolts; i++) - bolts.Add(value.Substring(i * 67, 67)); - - foreach (var bolt in bolts) - { - var obj = new BoltData() - { - OrdinalBoltNumber = OpenProtocolConvert.ToInt32(bolt.Substring(2, 2)), - SimpleBoltStatus = OpenProtocolConvert.ToBoolean(bolt.Substring(6, 1)), - TorqueStatus = (TorqueStatus)OpenProtocolConvert.ToInt32(bolt.Substring(9, 1)), - AngleStatus = (AngleStatus)OpenProtocolConvert.ToInt32(bolt.Substring(12, 1)), - BoltTorque = OpenProtocolConvert.ToDecimal(bolt.Substring(15, 7)), - BoltAngle = OpenProtocolConvert.ToDecimal(bolt.Substring(24, 7)), - BoltTorqueHighLimit = OpenProtocolConvert.ToDecimal(bolt.Substring(33, 7)), - BoltTorqueLowLimit = OpenProtocolConvert.ToDecimal(bolt.Substring(42, 7)), - BoltAngleHighLimit = OpenProtocolConvert.ToDecimal(bolt.Substring(51, 7)), - BoltAngleLowLimit = OpenProtocolConvert.ToDecimal(bolt.Substring(60, 7)), - }; - - list.Add(obj); - } - - return list; - } - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() + foreach (var dataField in RevisionsByFields[i]) { + if (previousField != null && dataField.Index == 0) { - 1, new List() - { - DataField.Number(DataFields.TotalNumberOfMessages, 20, 2), - DataField.Number(DataFields.MessageNumber, 24, 2), - DataField.Number(DataFields.DataNumberSystem, 28, 10), - DataField.Number(DataFields.StationNumber, 40, 2), - DataField.String(DataFields.StationName, 44, 20), - DataField.Timestamp(DataFields.Time, 66), - DataField.Number(DataFields.ModeNumber, 87, 2), - DataField.String(DataFields.ModeName, 91, 20), - DataField.Boolean(DataFields.SimpleStatus, 113), - DataField.Number(DataFields.PMStatus, 116, 1), - DataField.String(DataFields.WPId, 119, 40), - DataField.Number(DataFields.NumberOfBolts, 161, 2), - new(DataFields.BoltData, 165, 67, false), - DataField.Number(DataFields.NumberOfSpecialValues, 0, 2), - DataField.Volatile(DataFields.SpecialValues, false) - } - }, - { - 4, new List() - { - DataField.Number(DataFields.SystemSubType, 0, 3) - } + dataField.Index = previousField.Index + previousField.TotalSize; } - }; - } - protected enum DataFields - { - TotalNumberOfMessages, - MessageNumber, - DataNumberSystem, - StationNumber, - StationName, - Time, - ModeNumber, - ModeName, - SimpleStatus, - PMStatus, - WPId, - NumberOfBolts, - BoltData, - NumberOfSpecialValues, - SpecialValues, - //Rev 4 - SystemSubType + previousField = dataField; + yield return dataField; + } + } } } } diff --git a/src/OpenProtocolInterpreter/PowerMACS/Mid0107.cs b/src/OpenProtocolInterpreter/PowerMACS/Mid0107.cs index 05d8a1f3..b6882497 100644 --- a/src/OpenProtocolInterpreter/PowerMACS/Mid0107.cs +++ b/src/OpenProtocolInterpreter/PowerMACS/Mid0107.cs @@ -8,14 +8,14 @@ namespace OpenProtocolInterpreter.PowerMACS /// /// Last Power MACS tightening result Bolt data /// - /// This message contains the cycle data for one Bolt, both Bolt data and step data. It is only sent if - /// the acknowledgement of the message Last PowerMACS tightening result station data had the parameter + /// This message contains the cycle data for one Bolt, both Bolt data and step data. It is only sent if + /// the acknowledgement of the message Last PowerMACS tightening result station data had the parameter /// Bolt Data set to TRUE. The next Bolt data is sent if the acknowledgement has the parameter Bolt Data set to TRUE. - /// This telegram is also used for Power MACS systems running a Press.The layout of the telegram is exactly the - /// same but some of the fields have slightly different definitions. The fields for Torque are used for Force values - /// and the fields for Angle are used for Stroke values. Press systems also use different identifiers for the optional + /// This telegram is also used for Power MACS systems running a Press.The layout of the telegram is exactly the + /// same but some of the fields have slightly different definitions. The fields for Torque are used for Force values + /// and the fields for Angle are used for Stroke values. Press systems also use different identifiers for the optional /// data on bolt and step level. Press systems always use revision 4 or higher of the telegram.Values in the fixed part - /// that are undefined in the results will be sent as all spaces (ASCII 0x20). + /// that are undefined in the results will be sent as all spaces (ASCII 0x20). /// /// /// This can happen with the Customer Error Code if this function is not activated. @@ -24,7 +24,7 @@ namespace OpenProtocolInterpreter.PowerMACS /// Note 2: The Bolt results and step results are only sent when the value exists in the result. This means, /// for example, that if no high limit is programmed for Peak T, then the value Peak T + will not be sent /// even if limits for Peak T are defined in the reporter. - /// + /// /// Message sent by: Controller /// Answer: Last PowerMACS tightening result data acknowledge /// @@ -32,85 +32,65 @@ public class Mid0107 : Mid, IPowerMACS, IController, IAcknowledgeable { public const int MID = 107; - public int TotalNumberOfMessages - { - get => GetField(1, DataFields.TotalNumberOfMessages).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.TotalNumberOfMessages).SetValue(OpenProtocolConvert.ToString, value); - } - public int MessageNumber - { - get => GetField(1, DataFields.MessageNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.MessageNumber).SetValue(OpenProtocolConvert.ToString, value); - } - public int DataNumberSystem - { - get => GetField(1, DataFields.DataNumberSystem).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.DataNumberSystem).SetValue(OpenProtocolConvert.ToString, value); - } - public int StationNumber - { - get => GetField(1, DataFields.StationNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.StationNumber).SetValue(OpenProtocolConvert.ToString, value); - } - public DateTime Time - { - get => GetField(1, DataFields.Time).GetValue(OpenProtocolConvert.ToDateTime); - set => GetField(1, DataFields.Time).SetValue(OpenProtocolConvert.ToString, value); - } - public int BoltNumber - { - get => GetField(1, DataFields.BoltNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.BoltNumber).SetValue(OpenProtocolConvert.ToString, value); - } - public string BoltName - { - get => GetField(1, DataFields.BoltName).Value; - set => GetField(1, DataFields.BoltName).SetValue(value); - } - public string ProgramName - { - get => GetField(1, DataFields.ProgramName).Value; - set => GetField(1, DataFields.ProgramName).SetValue(value); - } - public PowerMacsStatus PowerMacsStatus - { - get => (PowerMacsStatus)GetField(1, DataFields.PMStatus).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.PMStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public string Errors - { - get => GetField(1, DataFields.Errors).Value; - set => GetField(1, DataFields.Errors).SetValue(value); - } - public string CustomerErrorCode - { - get => GetField(1, DataFields.CustomerErrorCode).Value; - set => GetField(1, DataFields.CustomerErrorCode).SetValue(value); - } - public int NumberOfBoltResults - { - get => GetField(1, DataFields.NumberOfBoltResults).GetValue(OpenProtocolConvert.ToInt32); - private set => GetField(1, DataFields.NumberOfBoltResults).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 2)] + public int TotalNumberOfMessages { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 24, Size = 2)] + public int MessageNumber { get; set; } + + [Int64DataFieldDefinition(revision: 1, field: 3, Index = 28, Size = 10)] + public long DataNumberSystem { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 4, Index = 40, Size = 2)] + public int StationNumber { get; set; } + + [TimestampDataFieldDefinition(revision: 1, field: 5, Index = 44)] + public DateTime Time { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 6, Index = 65, Size = 4)] + public int BoltNumber { get; set; } + + [StringDataFieldDefinition(revision: 1, field: 7, Index = 71, Size = 20)] + public string BoltName { get; set; } + + [StringDataFieldDefinition(revision: 1, field: 8, Index = 93, Size = 20)] + public string ProgramName { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 9, Index = 115, Size = 1)] + public PowerMacsStatus PowerMacsStatus { get; set; } + + [StringDataFieldDefinition(revision: 1, field: 10, Index = 118, Size = 50)] + public string Errors { get; set; } + + [StringDataFieldDefinition(revision: 1, field: 11, Index = 170, Size = 4)] + public string CustomerErrorCode { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 12, Index = 176, Size = 2)] + public int NumberOfBoltResults { get; set; } + + [BoltResultCollectionDefinition(revision: 1, field: 12, Index = 180, Size = 0, HasPrefix = false)] public List BoltResults { get; set; } - public int NumberOfStepResults - { - get => GetField(1, DataFields.NumberOfStepResults).GetValue(OpenProtocolConvert.ToInt32); - private set => GetField(1, DataFields.NumberOfStepResults).SetValue(OpenProtocolConvert.ToString, value); - } - public bool AllStepDataSent - { - get => GetField(1, DataFields.AllStepDataSent).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.AllStepDataSent).SetValue(OpenProtocolConvert.ToString, value); - } + + [Int32DataFieldDefinition(revision: 1, field: 13, Index = 0, Size = 3)] + public int NumberOfStepResults { get; set; } + + [BooleanDataFieldDefinition(revision: 1, field: 14, Index = 0, Size = 1)] + public bool AllStepDataSent { get; set; } + + [StepResultCollectionDefinition(revision: 1, field: 14, Index = 0, Size = 0, HasPrefix = false)] public List StepResults { get; set; } - public int NumberOfSpecialValues - { - get => GetField(1, DataFields.NumberOfSpecialValues).GetValue(OpenProtocolConvert.ToInt32); - private set => GetField(1, DataFields.NumberOfSpecialValues).SetValue(OpenProtocolConvert.ToString, value); - } + + //Total Special values has a weird pattern of being attached to the list of special values instead of being a separate field. + //So we do process is in a separate way with special values and set its value and have to do it together with list because each special value + //has a dynamic size, due to that, we cannot make the same processing as Bolt result list that has fixed length of 31 bytes for each bolt result. + public int NumberOfSpecialValues { get; set; } + + [SpecialValueCollectionDefinition(revision: 1, field: 15, Index = 0, UseStepNumber = true)] public List SpecialValues { get; set; } + [Int32DataFieldDefinition(revision: 4, field: 16, Index = 0, Size = 3)] + public SystemSubType SystemSubType { get; set; } + public Mid0107() : this(new Header() { Mid = MID, @@ -127,219 +107,79 @@ public Mid0107(Header header) : base(header) SpecialValues ??= []; } + protected override string BuildHeader() + { + Header.Length = Header.DefaultSize; + var fields = DataFieldsByRevision(); + Header.Length += fields.Sum(x => x.TotalSize); + return Header.ToString(); + } + public override string Pack() { NumberOfBoltResults = BoltResults.Count; NumberOfStepResults = StepResults.Count; NumberOfSpecialValues = SpecialValues.Count; - GetField(1, DataFields.BoltResults).SetValue(PackBoltResultList()); - GetField(1, DataFields.StepResults).SetValue(PackStepResults()); - GetField(1, DataFields.SpecialValues).SetValue(PackSpecialValues()); - - return base.Pack(); - } - - public override Mid Parse(ReadOnlySpan package) - { - Header = ProcessHeader(package); - - int numberOfBoltResults = OpenProtocolConvert.ToInt32(GetValue(GetField(1, DataFields.NumberOfBoltResults), package).ToString()); - var boltResultField = GetField(1, DataFields.BoltResults); - boltResultField.Size = 29 * numberOfBoltResults; - - var numberOfStepResultsField = GetField(1, DataFields.NumberOfStepResults); - numberOfStepResultsField.Index = boltResultField.Index + boltResultField.Size; - - var allStepDataSentField = GetField(1, DataFields.AllStepDataSent); - allStepDataSentField.Index = 2 + numberOfStepResultsField.Index + numberOfStepResultsField.Size; - - var stepResultsField = GetField(1, DataFields.StepResults); - stepResultsField.Index = 2 + allStepDataSentField.Index + allStepDataSentField.Size; - - int numberOfStepResults = OpenProtocolConvert.ToInt32(GetValue(numberOfStepResultsField, package).ToString()); - stepResultsField.Size = 31 * numberOfStepResults; - - var numberOfSpecialValuesField = GetField(1, DataFields.NumberOfSpecialValues); - numberOfSpecialValuesField.Index = stepResultsField.Index + stepResultsField.Size; - - var specialValuesField = GetField(1, DataFields.SpecialValues); - specialValuesField.Index = 2 + numberOfSpecialValuesField.Index + numberOfSpecialValuesField.Size; - specialValuesField.Size = Header.Length - specialValuesField.Index; - - ProcessDataFields(package); - - BoltResults = ParseBoltResultList(boltResultField.Value); - StepResults = ParseStepResults(stepResultsField.Value); - SpecialValues = SpecialValue.ParseAll(specialValuesField.Value, NumberOfSpecialValues, true).ToList(); - return this; - } - - protected virtual string PackBoltResultList() - { - var builder = new StringBuilder(); - foreach (var bolt in BoltResults) - { - builder.Append(OpenProtocolConvert.TruncatePadded(' ', 20, PaddingOrientation.RightPadded, bolt.VariableName)); - builder.Append(bolt.Type.Type); - if (bolt.Type.Type == DataType.DataTypes[1].Type) // Integer - { - builder.Append(OpenProtocolConvert.ToString('0', 7, PaddingOrientation.LeftPadded, (int)bolt.Value)); - } - else if (bolt.Type.Type == DataType.DataTypes[2].Type) // Decimal - { - builder.Append(OpenProtocolConvert.ToString('0', 7, PaddingOrientation.LeftPadded, (decimal)bolt.Value)); - } - } - - return builder.ToString(); - } + GetField>(revision: 1, field: 12).Size = NumberOfBoltResults * 29; + GetField>(revision: 1, field: 14).Size = NumberOfStepResults * 31; + GetField>(revision: 1, field: 15).Size = 2 + SpecialValues.Sum(x => x.TotalFieldLength); - protected virtual string PackStepResults() - { - var builder = new StringBuilder(); - foreach (var step in StepResults) - { - builder.Append(OpenProtocolConvert.TruncatePadded(' ', 20, PaddingOrientation.RightPadded, step.VariableName)); - builder.Append(step.Type.Type); - if (step.Type.Type == DataType.DataTypes[1].Type) // Integer - { - builder.Append(OpenProtocolConvert.ToString('0', 7, PaddingOrientation.LeftPadded, (int)step.Value)); - } - else if (step.Type.Type == DataType.DataTypes[2].Type) // Decimal - { - builder.Append(OpenProtocolConvert.ToString('0', 7, PaddingOrientation.LeftPadded, (decimal)step.Value)); - } - builder.Append(OpenProtocolConvert.ToString('0', 2, PaddingOrientation.LeftPadded, step.StepNumber)); - } + var header = BuildHeader(); + var builder = new StringBuilder(Header.Length); + builder.Append(header); + var fields = DataFieldsByRevision(); + builder.Append(base.Pack(fields)); return builder.ToString(); } - protected virtual string PackSpecialValues() + protected override void ProcessDataFields(ReadOnlySpan package) { - var builder = new StringBuilder(); - foreach (var v in SpecialValues) - { - builder.Append(v.Pack(true)); - } - - return builder.ToString(); + foreach (var field in DataFieldsByRevision()) + ProcessDataField(field, package); } - protected virtual List ParseBoltResultList(string section) + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - var list = new List(); - for (int i = 0; i < section.Length; i += 29) + switch (dataField.Field) { - var result = new BoltResult() - { - VariableName = section.Substring(i, 20), - Type = DataType.DataTypes.First(x => x.Type.Trim() == section.Substring(20 + i, 2).Trim()) - }; - - var resultValue = section.Substring(22 + i, 7); - if (result.Type.Type == DataType.DataTypes[1].Type) // Integer - { - result.Value = OpenProtocolConvert.ToInt32(resultValue); - } - else if (result.Type.Type == DataType.DataTypes[2].Type) // Decimal - { - result.Value = OpenProtocolConvert.ToDecimal(resultValue); - } - - list.Add(result); + case 12 when dataField is DataField>: //BoltResults + dataField.Size = NumberOfBoltResults * 29; + break; + case 14 when dataField is DataField>: //StepResults + dataField.Size = NumberOfStepResults * 31; + break; + case 15 when dataField is DataField>: //SpecialValues + if (Header.Revision > 3) + dataField.Size = Header.Length - GetField(revision: 4, field: 16).TotalSize - 2; + else + dataField.Size = Header.Length - dataField.Index - 2; + + base.ProcessDataField(dataField, package); + NumberOfSpecialValues = SpecialValues.Count; + return; } - return list; + base.ProcessDataField(dataField, package); } - protected virtual List ParseStepResults(string section) + private IEnumerable DataFieldsByRevision() { - var list = new List(); - for (int i = 0; i < section.Length; i += 31) + var previousField = default(DataField); + for (int i = 1; i <= Header.StandardizedRevision; i++) { - var result = new StepResult() - { - VariableName = section.Substring(i, 20), - Type = DataType.DataTypes.First(x => x.Type.Trim() == section.Substring(20 + i, 2).Trim()), - StepNumber = OpenProtocolConvert.ToInt32(section.Substring(29 + i, 2)) - }; - - var resultValue = section.Substring(22 + i, 7); - if (result.Type.Type == DataType.DataTypes[1].Type) // Integer - { - result.Value = OpenProtocolConvert.ToInt32(resultValue); - } - else if (result.Type.Type == DataType.DataTypes[2].Type) // Decimal - { - int decimalPlaces = resultValue.Split('.')[1].Length; - result.Value = OpenProtocolConvert.ToDecimal(resultValue); - } - - list.Add(result); - } - - return list; - } - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() + foreach (var dataField in RevisionsByFields[i]) { + if (previousField != null && dataField.Index == 0) { - 1, new List() - { - DataField.Number(DataFields.TotalNumberOfMessages, 20, 2), - DataField.Number(DataFields.MessageNumber, 24, 2), - DataField.Number(DataFields.DataNumberSystem, 28, 10), - DataField.Number(DataFields.StationNumber, 40, 2), - DataField.Timestamp(DataFields.Time, 44), - DataField.Number(DataFields.BoltNumber, 65, 4), - DataField.String(DataFields.BoltName, 71, 20), - DataField.String(DataFields.ProgramName, 93, 20), - DataField.Number(DataFields.PMStatus, 115, 1), - DataField.String(DataFields.Errors, 118, 50), - DataField.String(DataFields.CustomerErrorCode, 170, 4), - DataField.Number(DataFields.NumberOfBoltResults, 176, 2), - DataField.Volatile(DataFields.BoltResults, 180, false), - DataField.Number(DataFields.NumberOfStepResults, 0, 3), - DataField.Boolean(DataFields.AllStepDataSent, 0), - DataField.Volatile(DataFields.StepResults, 0, false), - DataField.Number(DataFields.NumberOfSpecialValues, 0, 2), - DataField.Volatile(DataFields.SpecialValues, 0, false) - } - }, - { - 4, new List() - { - DataField.Number(DataFields.SystemSubType, 0, 3) - } + dataField.Index = previousField.Index + previousField.TotalSize; } - }; - } - protected enum DataFields - { - TotalNumberOfMessages, - MessageNumber, - DataNumberSystem, - StationNumber, - Time, - BoltNumber, - BoltName, - ProgramName, - PMStatus, - Errors, - CustomerErrorCode, - NumberOfBoltResults, - BoltResults, - NumberOfStepResults, - AllStepDataSent, - StepResults, - NumberOfSpecialValues, - SpecialValues, - SystemSubType + previousField = dataField; + yield return dataField; + } + } } } } diff --git a/src/OpenProtocolInterpreter/PowerMACS/Mid0108.cs b/src/OpenProtocolInterpreter/PowerMACS/Mid0108.cs index 45cab4ed..ce0238f4 100644 --- a/src/OpenProtocolInterpreter/PowerMACS/Mid0108.cs +++ b/src/OpenProtocolInterpreter/PowerMACS/Mid0108.cs @@ -9,9 +9,9 @@ namespace OpenProtocolInterpreter.PowerMACS /// tightening). Otherwise no more Bolt data is sent for this tightening. /// /// - /// If only the station data is wanted Bolt Data must be set to FALSE in the acknowledgement of + /// If only the station data is wanted Bolt Data must be set to FALSE in the acknowledgement of /// Last Power MACS tightening result Station data. - /// + /// /// Message sent by: Integrator /// Answer: None /// @@ -19,11 +19,8 @@ public class Mid0108 : Mid, IPowerMACS, IIntegrator, IAcknowledge { public const int MID = 108; - public bool BoltData - { - get => GetField(1, DataFields.BoltData).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(1, DataFields.BoltData).SetValue(OpenProtocolConvert.ToString, value); - } + [BooleanDataFieldDefinition(revision: 1, field: 1, Index = 20, HasPrefix = false)] + public bool BoltData { get; set; } public Mid0108() : this(DEFAULT_REVISION) { @@ -41,23 +38,5 @@ public Mid0108(int revision) : this(new Header() }) { } - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Boolean(DataFields.BoltData, 20, false), - } - }, - }; - } - - protected enum DataFields - { - BoltData - } } } diff --git a/src/OpenProtocolInterpreter/PowerMACS/SpecialValue.cs b/src/OpenProtocolInterpreter/PowerMACS/SpecialValue.cs index 8a28d646..cf2c8924 100644 --- a/src/OpenProtocolInterpreter/PowerMACS/SpecialValue.cs +++ b/src/OpenProtocolInterpreter/PowerMACS/SpecialValue.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.Linq; +using System.Reflection; using System.Text; namespace OpenProtocolInterpreter.PowerMACS @@ -8,6 +10,8 @@ namespace OpenProtocolInterpreter.PowerMACS /// public class SpecialValue { + public int TotalFieldLength => 20 + 2 + 2 + Length + (StepNumber > 0 ? 2 : 0); + public string VariableName { get; set; } public DataType Type { get; set; } public int Length { get; set; } @@ -45,6 +49,26 @@ public static SpecialValue Parse(string value, bool useStepNumber) return obj; } + public static IEnumerable ParseAll(string value, bool useStepNumber) + { + if (string.IsNullOrWhiteSpace(value)) + { + yield break; + } + + var totalSpecialValues = OpenProtocolConvert.ToInt32(value.Substring(0, 2)); + int index = 2; + const int sectionSize = 24; + for (int i = 0; i < totalSpecialValues; i++) + { + var length = OpenProtocolConvert.ToInt32(value.Substring(22 + index, 2)); + var totalSize = length + (useStepNumber ? sectionSize + 2 : sectionSize); + var section = value.Substring(index, totalSize); + index += totalSize; + yield return Parse(section, useStepNumber); + } + } + public static IEnumerable ParseAll(string value, int totalSpecialValues, bool useStepNumber) { if (string.IsNullOrWhiteSpace(value)) @@ -63,7 +87,39 @@ public static IEnumerable ParseAll(string value, int totalSpecialV yield return Parse(section, useStepNumber); } } + } + + public class SpecialValueCollectionDefinitionAttribute : DataFieldDefinitionAttribute + { + public bool UseStepNumber { get; set; } + + public SpecialValueCollectionDefinitionAttribute(int revision) : base(revision) + { + + } + public SpecialValueCollectionDefinitionAttribute(int field, int revision) : base(field, revision) + { + + } + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + { + return new DataField>(Field, index, Size, HasPrefix) + { + DefaultConverter = PackSpecialValues, + DefaultParser = ParseSpecialValues + }.Bind(mid, propertyInfo); + } + private string PackSpecialValues(char paddingChar, int size, PaddingOrientation orientation, List specialValues) + { + var packages = string.Join(string.Empty, specialValues.Select(x => x.Pack(UseStepNumber))); + return string.Concat(OpenProtocolConvert.ToString('0', 2, PaddingOrientation.LeftPadded, specialValues.Count), packages); + } + + private List ParseSpecialValues(string value) + { + return SpecialValue.ParseAll(value, UseStepNumber).ToList(); + } } } diff --git a/src/OpenProtocolInterpreter/PowerMACS/StepResult.cs b/src/OpenProtocolInterpreter/PowerMACS/StepResult.cs index 2e8c514a..81149dfa 100644 --- a/src/OpenProtocolInterpreter/PowerMACS/StepResult.cs +++ b/src/OpenProtocolInterpreter/PowerMACS/StepResult.cs @@ -1,4 +1,10 @@ -namespace OpenProtocolInterpreter.PowerMACS +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace OpenProtocolInterpreter.PowerMACS { /// /// Represents a Step Result entity @@ -10,4 +16,82 @@ public class StepResult public object Value { get; set; } public int StepNumber { get; set; } } + + public class StepResultCollectionDefinitionAttribute : DataFieldDefinitionAttribute + { + public StepResultCollectionDefinitionAttribute(int revision) : base(revision) + { + + } + public StepResultCollectionDefinitionAttribute(int field, int revision) : base(field, revision) + { + + } + + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + { + return new DataField>(Field, index, Size, HasPrefix) + { + DefaultConverter = PackStepResults, + DefaultParser = ParseStepResults + }.Bind(mid, propertyInfo); + } + + protected static string PackStepResults(char paddingChar, int size, PaddingOrientation orientation, List stepResults) + { + var builder = new StringBuilder(); + foreach (var step in stepResults) + { + builder.Append(OpenProtocolConvert.TruncatePadded(' ', 20, PaddingOrientation.RightPadded, step.VariableName)); + builder.Append(step.Type.Type); + if (DataType.DataTypes[1].Type.Equals(step.Type.Type)) // Integer + { + builder.Append(OpenProtocolConvert.ToString('0', 7, PaddingOrientation.LeftPadded, (int)step.Value)); + } + else if (DataType.DataTypes[2].Type.Equals(step.Type.Type)) // Decimal + { + builder.Append(OpenProtocolConvert.ToString('0', 7, PaddingOrientation.LeftPadded, (decimal)step.Value)); + } + else + { + throw new ArgumentException($"Unsupported data type: '{step.Type.Type}', it should be only either Integer ('I ') or Decimal ('F ')"); + } + builder.Append(OpenProtocolConvert.ToString('0', 2, PaddingOrientation.LeftPadded, step.StepNumber)); + } + + return builder.ToString(); + } + + protected static List ParseStepResults(string package) + { + var list = new List(); + var section = package.AsSpan(); + for (int i = 0; i < section.Length; i += 31) + { + var type = section.Slice(20 + i, 2).ToString(); + var result = new StepResult() + { + VariableName = section.Slice(i, 20).ToString(), + Type = DataType.DataTypes.First(x => x.Type.Trim() == type.Trim()), + StepNumber = OpenProtocolConvert.ToInt32(section.Slice(29 + i, 2)) + }; + + var resultValue = section.Slice(22 + i, 7); + if (result.Type.Type == DataType.DataTypes[1].Type) // Integer + { + result.Value = OpenProtocolConvert.ToInt32(resultValue); + } + else if (result.Type.Type == DataType.DataTypes[2].Type) // Decimal + { + var decimalPointIndex = resultValue.IndexOf('.'); + int decimalPlaces = decimalPointIndex >= 0 ? resultValue.Slice(decimalPointIndex + 1).Length : 0; + result.Value = OpenProtocolConvert.ToDecimal(resultValue); + } + + list.Add(result); + } + + return list; + } + } } diff --git a/src/OpenProtocolInterpreter/Result/Mid1201.cs b/src/OpenProtocolInterpreter/Result/Mid1201.cs index 8fa7919a..80f40636 100644 --- a/src/OpenProtocolInterpreter/Result/Mid1201.cs +++ b/src/OpenProtocolInterpreter/Result/Mid1201.cs @@ -8,13 +8,13 @@ namespace OpenProtocolInterpreter.Result /// /// Operation result Overall data /// - /// This MID contains the overall result data and some of the object data of the last tightening. - /// In the subscription of this message it can be chosen to also start subscription of Operation result object data. + /// This MID contains the overall result data and some of the object data of the last tightening. + /// In the subscription of this message it can be chosen to also start subscription of Operation result object data. /// The user defined values is preconfigured in the controller via the configuration tool. /// /// Message sent by: Controller /// - /// Answer: Operation result data acknowledge or + /// Answer: Operation result data acknowledge or /// with in the data field. /// /// If the sequence number acknowledge functionality is used there is no need for these acknowledges. @@ -104,8 +104,7 @@ public override string Pack() GetField(revision, DataFields.ObjectData).SetValue(PackObjectDataList()); GetField(revision, DataFields.DataFieldList).SetValue(OpenProtocolConvert.ToString(VariableDataFields)); - var index = 1; - return string.Concat(BuildHeader(), base.Pack(revision, ref index)); + return string.Concat(BuildHeader(), base.Pack(revision)); } public override Mid Parse(ReadOnlySpan package) diff --git a/src/OpenProtocolInterpreter/Result/Mid1202.cs b/src/OpenProtocolInterpreter/Result/Mid1202.cs index 7382fe24..9d8e7cb9 100644 --- a/src/OpenProtocolInterpreter/Result/Mid1202.cs +++ b/src/OpenProtocolInterpreter/Result/Mid1202.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; namespace OpenProtocolInterpreter.Result @@ -7,20 +7,20 @@ namespace OpenProtocolInterpreter.Result /// /// Operation result object data /// - /// This message contains the cycle data for one object, both data for the whole process and data related to - /// the different steps in the process.The user defined values are preconfigured in the controller via the + /// This message contains the cycle data for one object, both data for the whole process and data related to + /// the different steps in the process.The user defined values are preconfigured in the controller via the /// configuration tool. The message uses the Variable Parameter pattern for transmission of the values. /// /// - /// Note: Only values that exist in the result will be sent.So the actual data received may vary between + /// Note: Only values that exist in the result will be sent.So the actual data received may vary between /// the cycles if the settings differ between different programs. /// /// Message sent by: Controller /// - /// Answer: Operation result data acknowledge or + /// Answer: Operation result data acknowledge or /// with in the data field. /// - /// + /// /// If the sequence number acknowledge functionality is used there is no need for these acknowledges. /// public class Mid1202 : Mid, IResult, IController, IAcknowledgeable, IAcceptableCommand @@ -83,8 +83,7 @@ public override string Pack() NumberOfDataFields = VariableDataFields.Count; var revision = Header.StandardizedRevision; GetField(revision, DataFields.VariableDataFields).SetValue(OpenProtocolConvert.ToString(VariableDataFields)); - int prefixIndex = 0; - return string.Concat(BuildHeader(), base.Pack(revision, ref prefixIndex)); + return string.Concat(BuildHeader(), base.Pack(revision)); } public override Mid Parse(ReadOnlySpan package) diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0061.cs b/src/OpenProtocolInterpreter/Tightening/Mid0061.cs index 4cae027a..53bd10fc 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0061.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0061.cs @@ -346,9 +346,8 @@ public override string Pack() { var builder = new StringBuilder(); var fields = DataFieldsByRevision().OrderBy(f => f.Index).ToList(); - int prefixIndex = 1; builder.Append(BuildHeader()); - builder.Append(Pack(fields, ref prefixIndex)); + builder.Append(Pack(fields)); return builder.ToString(); } diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0065.cs b/src/OpenProtocolInterpreter/Tightening/Mid0065.cs index 28c54c76..70cbc001 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0065.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0065.cs @@ -263,9 +263,8 @@ public override string Pack() { var builder = new StringBuilder(); var fields = DataFieldsByRevision().OrderBy(f => f.Index).ToList(); - int prefixIndex = 1; builder.Append(BuildHeader()); - builder.Append(Pack(fields, ref prefixIndex)); + builder.Append(Pack(fields)); return builder.ToString(); } diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0066.cs b/src/OpenProtocolInterpreter/Tightening/Mid0066.cs index 011197ad..8af94560 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0066.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0066.cs @@ -55,8 +55,7 @@ public override string Pack() var fields = RevisionsByFields[Header.StandardizedRevision]; var builder = new StringBuilder(BuildHeader()); - int prefixIndex = 1; - builder.Append(Pack(fields, ref prefixIndex)); + builder.Append(Pack(fields)); return builder.ToString(); } diff --git a/src/OpenProtocolInterpreter/Tool/Mid0704.cs b/src/OpenProtocolInterpreter/Tool/Mid0704.cs index 15d3e69e..2f9dac4a 100644 --- a/src/OpenProtocolInterpreter/Tool/Mid0704.cs +++ b/src/OpenProtocolInterpreter/Tool/Mid0704.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; namespace OpenProtocolInterpreter.Tool @@ -8,7 +8,7 @@ namespace OpenProtocolInterpreter.Tool /// Tool Data status reply with generic data /// /// Upload requested parameters from given tool. - /// + /// /// Message sent by: Controller /// /// Answer: None @@ -46,8 +46,7 @@ public override string Pack() var revision = Header.StandardizedRevision; GetField(revision, DataFields.VariableDataFields).SetValue(OpenProtocolConvert.ToString(VariableDataFields)); - var index = 1; - return string.Concat(BuildHeader(), base.Pack(revision, ref index)); + return string.Concat(BuildHeader(), base.Pack(revision)); } public override Mid Parse(ReadOnlySpan package) From 92fc2f2f8128d85cb721d66c131d377a837cf577 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Mon, 27 Jul 2026 22:57:55 -0300 Subject: [PATCH 49/78] Statistic mids using datafield attributes --- .../Statistic/Mid0300.cs | 41 +---- .../Statistic/Mid0301.cs | 153 +++++------------- 2 files changed, 44 insertions(+), 150 deletions(-) diff --git a/src/OpenProtocolInterpreter/Statistic/Mid0300.cs b/src/OpenProtocolInterpreter/Statistic/Mid0300.cs index 8eed0132..eae66d33 100644 --- a/src/OpenProtocolInterpreter/Statistic/Mid0300.cs +++ b/src/OpenProtocolInterpreter/Statistic/Mid0300.cs @@ -6,13 +6,13 @@ namespace OpenProtocolInterpreter.Statistic /// Histogram upload request /// /// Request to upload a histogram from the controller for a certain parameter set. - /// The histogram is calculated with all the tightening results currently present in - /// the controller’s memory and within the statistic acceptance window(statistic min and max limits) + /// The histogram is calculated with all the tightening results currently present in + /// the controller’s memory and within the statistic acceptance window(statistic min and max limits) /// for the requested parameter set. /// /// Message sent by: Integrator /// - /// Answer: Histogram upload reply, or + /// Answer: Histogram upload reply, or /// Command error, No histogram available or Invalid data /// /// @@ -22,16 +22,11 @@ public class Mid0300 : Mid, IStatistic, IIntegrator, IAnswerableBy, IDe public IEnumerable DocumentedPossibleErrors => new Error[] { Error.NoHistogramAvailable, Error.InvalidData }; - public int ParameterSetId - { - get => GetField(1, DataFields.ParameterSetId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.ParameterSetId).SetValue(OpenProtocolConvert.ToString, value); - } - public HistogramType HistogramType - { - get => (HistogramType)GetField(1, DataFields.HistogramType).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.HistogramType).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 3)] + public int ParameterSetId { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 25, Size = 2)] + public HistogramType HistogramType { get; set; } public Mid0300() : this(new Header() { @@ -44,25 +39,5 @@ public Mid0300() : this(new Header() public Mid0300(Header header) : base(header) { } - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.ParameterSetId, 20, 3), - DataField.Number(DataFields.HistogramType, 25, 2) - } - } - }; - } - - protected enum DataFields - { - ParameterSetId, - HistogramType - } } } diff --git a/src/OpenProtocolInterpreter/Statistic/Mid0301.cs b/src/OpenProtocolInterpreter/Statistic/Mid0301.cs index f6d0c1ac..826687b0 100644 --- a/src/OpenProtocolInterpreter/Statistic/Mid0301.cs +++ b/src/OpenProtocolInterpreter/Statistic/Mid0301.cs @@ -15,76 +15,42 @@ public class Mid0301 : Mid, IStatistic, IController { public const int MID = 301; - public int ParameterSetId - { - get => GetField(1, DataFields.ParameterSetId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.ParameterSetId).SetValue(OpenProtocolConvert.ToString, value); - } - public HistogramType HistogramType - { - get => (HistogramType)GetField(1, DataFields.HistogramType).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.HistogramType).SetValue(OpenProtocolConvert.ToString, value); - } - public decimal SigmaHistogram - { - get => GetField(1, DataFields.SigmaHistogram).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(1, DataFields.SigmaHistogram).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal MeanValueHistogram - { - get => GetField(1, DataFields.MeanValueHistogram).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(1, DataFields.MeanValueHistogram).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal ClassRange - { - get => GetField(1, DataFields.ClassRange).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(1, DataFields.ClassRange).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public int FirstBar - { - get => GetField(1, DataFields.Bar1).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.Bar1).SetValue(OpenProtocolConvert.ToString, value); - } - public int SecondBar - { - get => GetField(1, DataFields.Bar2).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.Bar2).SetValue(OpenProtocolConvert.ToString, value); - } - public int ThirdBar - { - get => GetField(1, DataFields.Bar3).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.Bar3).SetValue(OpenProtocolConvert.ToString, value); - } - public int FourthBar - { - get => GetField(1, DataFields.Bar4).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.Bar4).SetValue(OpenProtocolConvert.ToString, value); - } - public int FifthBar - { - get => GetField(1, DataFields.Bar5).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.Bar5).SetValue(OpenProtocolConvert.ToString, value); - } - public int SixthBar - { - get => GetField(1, DataFields.Bar6).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.Bar6).SetValue(OpenProtocolConvert.ToString, value); - } - public int SeventhBar - { - get => GetField(1, DataFields.Bar7).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.Bar7).SetValue(OpenProtocolConvert.ToString, value); - } - public int EighthBar - { - get => GetField(1, DataFields.Bar8).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.Bar8).SetValue(OpenProtocolConvert.ToString, value); - } - public int NinethBar - { - get => GetField(1, DataFields.Bar9).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.Bar9).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 3)] + public int ParameterSetId { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 25, Size = 2)] + public HistogramType HistogramType { get; set; } + + [TruncatedDecimalDataFieldDefinition(revision: 1, field: 3, Index = 29, Size = 6)] + public decimal SigmaHistogram { get; set; } + + [TruncatedDecimalDataFieldDefinition(revision: 1, field: 4, Index = 37, Size = 6)] + public decimal MeanValueHistogram { get; set; } + + [TruncatedDecimalDataFieldDefinition(revision: 1, field: 5, Index = 45, Size = 6)] + public decimal ClassRange { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 6, Index = 53, Size = 4)] + public int FirstBar { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 7, Index = 59, Size = 4)] + public int SecondBar { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 8, Index = 65, Size = 4)] + public int ThirdBar { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 9, Index = 71, Size = 4)] + public int FourthBar { get; set; } + [Int32DataFieldDefinition(revision: 1, field: 10, Index = 77, Size = 4)] + public int FifthBar { get; set; } + [Int32DataFieldDefinition(revision: 1, field: 11, Index = 83, Size = 4)] + public int SixthBar { get; set; } + [Int32DataFieldDefinition(revision: 1, field: 12, Index = 89, Size = 4)] + public int SeventhBar { get; set; } + [Int32DataFieldDefinition(revision: 1, field: 13, Index = 95, Size = 4)] + public int EighthBar { get; set; } + [Int32DataFieldDefinition(revision: 1, field: 14, Index = 101, Size = 4)] + public int NinethBar { get; set; } public Mid0301() : this(new Header() { @@ -97,52 +63,5 @@ public Mid0301() : this(new Header() public Mid0301(Header header) : base(header) { } - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.ParameterSetId, 20, 3), - DataField.Number(DataFields.HistogramType, 25, 2), - DataField.Number(DataFields.SigmaHistogram, 29, 6), - DataField.Number(DataFields.MeanValueHistogram, 37, 6), - DataField.Number(DataFields.ClassRange, 45, 6), - DataField.Number(DataFields.Bar1, 53, 4), - DataField.Number(DataFields.Bar2, 59, 4), - DataField.Number(DataFields.Bar3, 65, 4), - DataField.Number(DataFields.Bar4, 71, 4), - DataField.Number(DataFields.Bar5, 77, 4), - DataField.Number(DataFields.Bar6, 83, 4), - DataField.Number(DataFields.Bar7, 89, 4), - DataField.Number(DataFields.Bar8, 95, 4), - DataField.Number(DataFields.Bar9, 101, 4) - } - } - }; - } - - protected enum DataFields - { - ParameterSetId, - HistogramType, - SigmaHistogram, - /// - /// X-BAR - /// - MeanValueHistogram, - ClassRange, - Bar1, - Bar2, - Bar3, - Bar4, - Bar5, - Bar6, - Bar7, - Bar8, - Bar9 - } } } From 4abe3c1d5a2c7cb3e153c304c1b78a72cc5c2960 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Mon, 27 Jul 2026 22:58:55 -0300 Subject: [PATCH 50/78] Time mids using datafield attributes --- src/OpenProtocolInterpreter/Time/Mid0081.cs | 28 +++------------------ src/OpenProtocolInterpreter/Time/Mid0082.cs | 28 +++------------------ 2 files changed, 6 insertions(+), 50 deletions(-) diff --git a/src/OpenProtocolInterpreter/Time/Mid0081.cs b/src/OpenProtocolInterpreter/Time/Mid0081.cs index fe563f2e..00853a79 100644 --- a/src/OpenProtocolInterpreter/Time/Mid0081.cs +++ b/src/OpenProtocolInterpreter/Time/Mid0081.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; namespace OpenProtocolInterpreter.Time { @@ -13,15 +12,12 @@ public class Mid0081 : Mid, ITime, IController { public const int MID = 81; - public DateTime Time - { - get => GetField(1, DataFields.Time).GetValue(OpenProtocolConvert.ToDateTime); - set => GetField(1, DataFields.Time).SetValue(OpenProtocolConvert.ToString, value); - } + [TimestampDataFieldDefinition(revision: 1, field: 1, Index = 20, HasPrefix = false)] + public DateTime Time { get; set; } public Mid0081() : this(new Header() { - Mid = MID, + Mid = MID, Revision = DEFAULT_REVISION }) { @@ -30,23 +26,5 @@ public Mid0081() : this(new Header() public Mid0081(Header header) : base(header) { } - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Timestamp(DataFields.Time, 20, false) - } - } - }; - } - - protected enum DataFields - { - Time - } } } diff --git a/src/OpenProtocolInterpreter/Time/Mid0082.cs b/src/OpenProtocolInterpreter/Time/Mid0082.cs index ca7ebdc5..200bcb0c 100644 --- a/src/OpenProtocolInterpreter/Time/Mid0082.cs +++ b/src/OpenProtocolInterpreter/Time/Mid0082.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; namespace OpenProtocolInterpreter.Time { @@ -13,15 +12,12 @@ public class Mid0082 : Mid, ITime, IIntegrator { public const int MID = 82; - public DateTime Time - { - get => GetField(1, DataFields.Time).GetValue(OpenProtocolConvert.ToDateTime); - set => GetField(1, DataFields.Time).SetValue(OpenProtocolConvert.ToString, value); - } + [TimestampDataFieldDefinition(revision: 1, field: 1, Index = 20, HasPrefix = false)] + public DateTime Time { get; set; } public Mid0082() : this(new Header() { - Mid = MID, + Mid = MID, Revision = DEFAULT_REVISION }) { @@ -30,23 +26,5 @@ public Mid0082() : this(new Header() public Mid0082(Header header) : base(header) { } - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Timestamp(DataFields.Time, 20, false) - } - } - }; - } - - protected enum DataFields - { - Time - } } } From bfdecae0f13da928c20b91b6dacac59fdac2fabf Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Mon, 27 Jul 2026 23:29:13 -0300 Subject: [PATCH 51/78] Tool mids using datafield attributes --- .../DataFieldDefinitionAttribute.cs | 1 + src/OpenProtocolInterpreter/Tool/Mid0040.cs | 29 +- src/OpenProtocolInterpreter/Tool/Mid0041.cs | 268 +++++------------- src/OpenProtocolInterpreter/Tool/Mid0042.cs | 40 +-- src/OpenProtocolInterpreter/Tool/Mid0043.cs | 41 +-- src/OpenProtocolInterpreter/Tool/Mid0045.cs | 76 +---- src/OpenProtocolInterpreter/Tool/Mid0046.cs | 29 +- src/OpenProtocolInterpreter/Tool/Mid0047.cs | 29 +- src/OpenProtocolInterpreter/Tool/Mid0048.cs | 35 +-- .../Tool/OpenEndData.cs | 37 ++- 10 files changed, 146 insertions(+), 439 deletions(-) diff --git a/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs b/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs index 227d1399..818f56d9 100644 --- a/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs +++ b/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs @@ -149,6 +149,7 @@ public TruncatedDecimalDataFieldDefinitionAttribute(int field, int revision) : b PaddingChar = '0'; PaddingOrientation = PaddingOrientation.LeftPadded; } + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) { return DataField.TruncatedDecimal(Field, index, Size, DecimalPoints, HasPrefix) diff --git a/src/OpenProtocolInterpreter/Tool/Mid0040.cs b/src/OpenProtocolInterpreter/Tool/Mid0040.cs index 805e7e08..a7f17900 100644 --- a/src/OpenProtocolInterpreter/Tool/Mid0040.cs +++ b/src/OpenProtocolInterpreter/Tool/Mid0040.cs @@ -5,7 +5,7 @@ namespace OpenProtocolInterpreter.Tool /// /// Tool data upload request /// - /// A request for some of the data stored in the tool. The result of this command + /// A request for some of the data stored in the tool. The result of this command /// is the transmission of the tool data. /// /// Message sent by: Integrator @@ -15,11 +15,8 @@ public class Mid0040 : Mid, ITool, IIntegrator, IAnswerableBy { public const int MID = 40; - public int ToolNumber - { - get => GetField(6, DataFields.ToolNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(6, DataFields.ToolNumber).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 6, field: 1, Index = 20, Size = 4)] + public int ToolNumber { get; set; } public Mid0040() : this(DEFAULT_REVISION) { @@ -32,28 +29,10 @@ public Mid0040(Header header) : base(header) public Mid0040(int revision) : this(new Header() { - Mid = MID, + Mid = MID, Revision = revision }) { } - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 6, new List() - { - DataField.Number(DataFields.ToolNumber, 20, 4) - } - }, - }; - } - - protected enum DataFields - { - ToolNumber - } } } diff --git a/src/OpenProtocolInterpreter/Tool/Mid0041.cs b/src/OpenProtocolInterpreter/Tool/Mid0041.cs index 31da0922..ea2c77ac 100644 --- a/src/OpenProtocolInterpreter/Tool/Mid0041.cs +++ b/src/OpenProtocolInterpreter/Tool/Mid0041.cs @@ -13,117 +13,78 @@ public class Mid0041 : Mid, ITool, IController { public const int MID = 41; - public string ToolSerialNumber - { - get => GetField(1, DataFields.ToolSerialNumber).Value; - set => GetField(1, DataFields.ToolSerialNumber).SetValue(value); - } - public long ToolNumberOfTightenings - { - get => GetField(1, DataFields.ToolNumberOfTightenings).GetValue(OpenProtocolConvert.ToInt64); - set => GetField(1, DataFields.ToolNumberOfTightenings).SetValue(OpenProtocolConvert.ToString, value); - } - public DateTime LastCalibrationDate - { - get => GetField(1, DataFields.LastCalibrationDate).GetValue(OpenProtocolConvert.ToDateTime); - set => GetField(1, DataFields.LastCalibrationDate).SetValue(OpenProtocolConvert.ToString, value); - } - public string ControllerSerialNumber - { - get => GetField(1, DataFields.ControllerSerialNumber).Value; - set => GetField(1, DataFields.ControllerSerialNumber).SetValue(value); - } + [StringDataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 14)] + public string ToolSerialNumber { get; set; } + + [Int64DataFieldDefinition(revision: 1, field: 2, Index = 36, Size = 10)] + public long ToolNumberOfTightenings { get; set; } + + [TimestampDataFieldDefinition(revision: 1, field: 3, Index = 48)] + public DateTime LastCalibrationDate { get; set; } + + [StringDataFieldDefinition(revision: 1, field: 4, Index = 69, Size = 10)] + public string ControllerSerialNumber { get; set; } + //Rev 2 - public decimal CalibrationValue - { - get => GetField(2, DataFields.CalibrationValue).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(2, DataFields.CalibrationValue).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public DateTime LastServiceDate - { - get => GetField(2, DataFields.LastServiceDate).GetValue(OpenProtocolConvert.ToDateTime); - set => GetField(2, DataFields.LastServiceDate).SetValue(OpenProtocolConvert.ToString, value); - } - public long TighteningsSinceService - { - get => GetField(2, DataFields.TighteningsSinceService).GetValue(OpenProtocolConvert.ToInt64); - set => GetField(2, DataFields.TighteningsSinceService).SetValue(OpenProtocolConvert.ToString, value); - } - public ToolType ToolType - { - get => (ToolType)GetField(2, DataFields.ToolType).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.ToolType).SetValue(OpenProtocolConvert.ToString, value); - } - public int MotorSize - { - get => GetField(2, DataFields.MotorSize).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.MotorSize).SetValue(OpenProtocolConvert.ToString, value); - } - public OpenEndData OpenEndData - { - get => GetField(2, DataFields.OpenEndData).GetValue(OpenEndData.Parse); - set => GetField(2, DataFields.OpenEndData).SetValue(PackOpenEndData, value); - } - public string ControllerSoftwareVersion - { - get => GetField(2, DataFields.ControllerSoftwareVersion).Value; - set => GetField(2, DataFields.ControllerSoftwareVersion).SetValue(value); - } + [TruncatedDecimalDataFieldDefinition(revision: 2, field: 5, Index = 81, Size = 6)] + public decimal CalibrationValue { get; set; } + + [TimestampDataFieldDefinition(revision: 2, field: 6, Index = 89)] + public DateTime LastServiceDate { get; set; } + + [Int64DataFieldDefinition(revision: 2, field: 7, Index = 110, Size = 10)] + public long TighteningsSinceService { get; set; } + + [Int32DataFieldDefinition(revision: 2, field: 8, Index = 122, Size = 2)] + public ToolType ToolType { get; set; } + + [Int32DataFieldDefinition(revision: 2, field: 9, Index = 126, Size = 2)] + public int MotorSize { get; set; } + + [OpenEndDataDefinition(revision: 2, field: 10, Index = 130, Size = 3)] + public OpenEndData OpenEndData { get; set; } + + [StringDataFieldDefinition(revision: 2, field: 11, Index = 135, Size = 19)] + public string ControllerSoftwareVersion { get; set; } + //Rev 3 - public decimal ToolMaxTorque - { - get => GetField(3, DataFields.ToolMaxTorque).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(3, DataFields.ToolMaxTorque).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal GearRatio - { - get => GetField(3, DataFields.GearRatio).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(3, DataFields.GearRatio).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal ToolFullSpeed - { - get => GetField(3, DataFields.ToolFullSpeed).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(3, DataFields.ToolFullSpeed).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } + [TruncatedDecimalDataFieldDefinition(revision: 3, field: 12, Index = 156, Size = 6)] + public decimal ToolMaxTorque { get; set; } + + [TruncatedDecimalDataFieldDefinition(revision: 3, field: 13, Index = 164, Size = 6)] + public decimal GearRatio { get; set; } + + [TruncatedDecimalDataFieldDefinition(revision: 3, field: 14, Index = 172, Size = 6)] + public decimal ToolFullSpeed { get; set; } + //Rev 4 - public PrimaryTool PrimaryTool - { - get => (PrimaryTool)GetField(4, DataFields.PrimaryTool).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(4, DataFields.PrimaryTool).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 4, field: 15, Index = 180, Size = 2)] + public PrimaryTool PrimaryTool { get; set; } + //Rev 5 - public string ToolModel - { - get => GetField(5, DataFields.ToolModel).Value; - set => GetField(5, DataFields.ToolModel).SetValue(value); - } + [StringDataFieldDefinition(revision: 5, field: 16, Index = 184, Size = 12)] + public string ToolModel { get; set; } + //Rev 6 - public int ToolNumber - { - get => GetField(6, DataFields.ToolNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(6, DataFields.ToolNumber).SetValue(OpenProtocolConvert.ToString, value); - } - public string ToolArticleNumber - { - get => GetField(6, DataFields.ToolArticleNumber).Value; - set => GetField(6, DataFields.ToolArticleNumber).SetValue(value); - } + /// + /// The number of the tool. It is the same number as the tool numbers sent in Tool List Upload. + /// In systems with only 1 tool the number sent will always be 0001 + /// + [Int32DataFieldDefinition(revision: 6, field: 17, Index = 198, Size = 4)] + public int ToolNumber { get; set; } + + [StringDataFieldDefinition(revision: 6, field: 18, Index = 204, Size = 30)] + public string ToolArticleNumber { get; set; } + //Rev 7 - public decimal RundownMinSpeed - { - get => GetField(7, DataFields.RundownMinSpeed).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(7, DataFields.RundownMinSpeed).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal DownshiftMaxSpeed - { - get => GetField(7, DataFields.DownshiftMaxSpeed).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(7, DataFields.DownshiftMaxSpeed).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public decimal DownshiftMinSpeed - { - get => GetField(7, DataFields.DownshiftMinSpeed).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(7, DataFields.DownshiftMinSpeed).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } + [TruncatedDecimalDataFieldDefinition(revision: 7, field: 19, Index = 236, Size = 6)] + public decimal RundownMinSpeed { get; set; } + + [TruncatedDecimalDataFieldDefinition(revision: 7, field: 20, Index = 244, Size = 6)] + public decimal DownshiftMaxSpeed { get; set; } + + [TruncatedDecimalDataFieldDefinition(revision: 7, field: 21, Index = 252, Size = 6)] + public decimal DownshiftMinSpeed { get; set; } public Mid0041() : this(DEFAULT_REVISION) { @@ -142,102 +103,5 @@ public Mid0041(int revision) : this(new Header() { } - - protected virtual string PackOpenEndData(char paddingChar, int size, PaddingOrientation orientation, OpenEndData value) - => value.Pack(); - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.String(DataFields.ToolSerialNumber, 20, 14), - DataField.Number(DataFields.ToolNumberOfTightenings, 36, 10), - DataField.Timestamp(DataFields.LastCalibrationDate, 48), - DataField.String(DataFields.ControllerSerialNumber, 69, 10) - } - }, - { - 2, new List() - { - DataField.Number(DataFields.CalibrationValue, 81, 6), - DataField.Timestamp(DataFields.LastServiceDate, 89), - DataField.Number(DataFields.TighteningsSinceService, 110, 10), - DataField.Number(DataFields.ToolType, 122, 2), - DataField.Number(DataFields.MotorSize, 126, 2), - new(DataFields.OpenEndData, 130, 3), - DataField.String(DataFields.ControllerSoftwareVersion, 135, 19) - } - }, - { - 3, new List() - { - DataField.Number(DataFields.ToolMaxTorque, 156, 6), - DataField.Number(DataFields.GearRatio, 164, 6), - DataField.Number(DataFields.ToolFullSpeed, 172, 6) - } - }, - { - 4, new List() - { - DataField.Number(DataFields.PrimaryTool, 180, 2) - } - }, - { - 5, new List() - { - DataField.String(DataFields.ToolModel, 184, 12) - } - }, - { - 6, new List() - { - DataField.Number(DataFields.ToolNumber, 198, 4), - DataField.String(DataFields.ToolArticleNumber, 204, 30) - } - }, - { - 7, new List() - { - DataField.Number(DataFields.RundownMinSpeed, 236, 6), - DataField.Number(DataFields.DownshiftMaxSpeed, 244, 6), - DataField.Number(DataFields.DownshiftMinSpeed, 252, 6), - } - } - }; - } - - protected enum DataFields - { - ToolSerialNumber, - ToolNumberOfTightenings, - LastCalibrationDate, - ControllerSerialNumber, - //Rev 2 - CalibrationValue, - LastServiceDate, - TighteningsSinceService, - ToolType, - MotorSize, - OpenEndData, - ControllerSoftwareVersion, - //Rev 3 - ToolMaxTorque, - GearRatio, - ToolFullSpeed, - //Rev 4 - PrimaryTool, - //Rev 5 - ToolModel, - //Rev 6 - ToolNumber, - ToolArticleNumber, - //Rev 7 - RundownMinSpeed, - DownshiftMaxSpeed, - DownshiftMinSpeed - } } } diff --git a/src/OpenProtocolInterpreter/Tool/Mid0042.cs b/src/OpenProtocolInterpreter/Tool/Mid0042.cs index e331063d..9cdd8547 100644 --- a/src/OpenProtocolInterpreter/Tool/Mid0042.cs +++ b/src/OpenProtocolInterpreter/Tool/Mid0042.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace OpenProtocolInterpreter.Tool +namespace OpenProtocolInterpreter.Tool { /// /// Disable tool @@ -11,16 +9,11 @@ public class Mid0042 : Mid, ITool, IIntegrator, IAcceptableCommand { public const int MID = 42; - public int ToolNumber - { - get => GetField(2, DataFields.ToolNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.ToolNumber).SetValue(OpenProtocolConvert.ToString, value); - } - public DisableType DisableType - { - get => (DisableType)GetField(2, DataFields.DisableType).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.DisableType).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 2, field: 1, Index = 20, Size = 4)] + public int ToolNumber { get; set; } + + [Int32DataFieldDefinition(revision: 2, field: 2, Index = 26, Size = 2)] + public DisableType DisableType { get; set; } public Mid0042() : this(DEFAULT_REVISION) { @@ -37,26 +30,5 @@ public Mid0042(int revision) : this(new Header() }) { } - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 2, new List() - { - DataField.Number(DataFields.ToolNumber, 20, 4), - DataField.Number(DataFields.DisableType, 26, 2) - } - }, - }; - } - - protected enum DataFields - { - ToolNumber, - DisableType - } } - } diff --git a/src/OpenProtocolInterpreter/Tool/Mid0043.cs b/src/OpenProtocolInterpreter/Tool/Mid0043.cs index 7fbd1a95..2b757341 100644 --- a/src/OpenProtocolInterpreter/Tool/Mid0043.cs +++ b/src/OpenProtocolInterpreter/Tool/Mid0043.cs @@ -1,9 +1,13 @@ -using System.Collections.Generic; - -namespace OpenProtocolInterpreter.Tool +namespace OpenProtocolInterpreter.Tool { /// /// Enable tool + /// + /// Enable the tool in revision 0-1. For revision 2, will release the inhibit / disable value set with Disable tool. + /// + /// + /// The number of the tool to release is specified in the telegram. If the tool number is set to 9999 all tools connected to the controller or station will be released. + /// /// Message sent by: Integrator /// Answer: Command accepted /// @@ -11,11 +15,11 @@ public class Mid0043 : Mid, ITool, IIntegrator, IAcceptableCommand { public const int MID = 43; - public int ToolNumber - { - get => GetField(2, DataFields.ToolNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.ToolNumber).SetValue(OpenProtocolConvert.ToString, value); - } + /// + /// The number of the tool to enable. It is the same number as the tool numbers sent in Tool List Upload + /// + [Int32DataFieldDefinition(revision: 2, field: 1, Index = 20, Size = 4)] + public int ToolNumber { get; set; } public Mid0043() : this(DEFAULT_REVISION) { @@ -28,29 +32,10 @@ public Mid0043(Header header) : base(header) public Mid0043(int revision) : this(new Header() { - Mid = MID, + Mid = MID, Revision = revision }) { } - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 2, new List() - { - DataField.Number(DataFields.ToolNumber, 20, 4), - } - }, - }; - } - - protected enum DataFields - { - ToolNumber, - DisableType - } } } diff --git a/src/OpenProtocolInterpreter/Tool/Mid0045.cs b/src/OpenProtocolInterpreter/Tool/Mid0045.cs index 2e494ec9..ebf20614 100644 --- a/src/OpenProtocolInterpreter/Tool/Mid0045.cs +++ b/src/OpenProtocolInterpreter/Tool/Mid0045.cs @@ -9,7 +9,7 @@ namespace OpenProtocolInterpreter.Tool /// /// Message sent by: Integrator /// - /// Answer: Command accepted or + /// Answer: Command accepted or /// Command error, Calibration failed /// /// @@ -19,32 +19,20 @@ public class Mid0045 : Mid, ITool, IIntegrator, IAcceptableCommand, IDeclinableC public IEnumerable DocumentedPossibleErrors => new Error[] { Error.CalibrationFailed }; - public CalibrationUnit CalibrationValueUnit - { - get => (CalibrationUnit)GetField(1, DataFields.CalibrationValueUnit).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.CalibrationValueUnit).SetValue(OpenProtocolConvert.ToString, value); - } - public decimal CalibrationValue - { - get => GetField(1, DataFields.CalibrationValue).GetValue(OpenProtocolConvert.ToTruncatedDecimal); - set => GetField(1, DataFields.CalibrationValue).SetValue(OpenProtocolConvert.TruncatedDecimalToString, value); - } - public int ChannelNumber - { - get => GetField(2, DataFields.ChannelNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(2, DataFields.ChannelNumber).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 1)] + public CalibrationUnit CalibrationValueUnit { get; set; } - public decimal ExtendedCalibrationValue - { - get => GetField(3, DataFields.ExtendedCalibrationValue).GetValue(x => OpenProtocolConvert.ToTruncatedDecimal(x, 4)); - set => GetField(3, DataFields.ExtendedCalibrationValue).SetValue((paddingChar, size, orientation, v) => OpenProtocolConvert.TruncatedDecimalToString(paddingChar, size, orientation, v, 4), value); - } - public int TransducerNumber - { - get => GetField(3, DataFields.TransducerNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(3, DataFields.TransducerNumber).SetValue(OpenProtocolConvert.ToString, value); - } + [TruncatedDecimalDataFieldDefinition(revision: 1, field: 2, Index = 23, Size = 6)] + public decimal CalibrationValue { get; set; } + + [Int32DataFieldDefinition(revision: 2, field: 3, Index = 31, Size = 2)] + public int ChannelNumber { get; set; } + + [TruncatedDecimalDataFieldDefinition(revision: 3, field: 4, Index = 35, Size = 10, DecimalPoints = 4)] + public decimal ExtendedCalibrationValue { get; set; } + + [Int32DataFieldDefinition(revision: 3, field: 5, Index = 47, Size = 1)] + public int TransducerNumber { get; set; } public Mid0045() : this(DEFAULT_REVISION) { @@ -61,41 +49,5 @@ public Mid0045(int revision) : this(new Header() }) { } - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.CalibrationValueUnit, 20, 1), - DataField.Number(DataFields.CalibrationValue, 23, 6) - } - }, - { - 2, new List() - { - DataField.Number(DataFields.ChannelNumber, 31, 2), - } - }, - { - 3, new List() - { - DataField.Number(DataFields.ExtendedCalibrationValue, 35, 10), - DataField.Number(DataFields.TransducerNumber, 47, 1) - } - } - }; - } - - protected enum DataFields - { - CalibrationValueUnit, - CalibrationValue, - ChannelNumber, - ExtendedCalibrationValue, - TransducerNumber - } } } diff --git a/src/OpenProtocolInterpreter/Tool/Mid0046.cs b/src/OpenProtocolInterpreter/Tool/Mid0046.cs index 2dbf916d..3a6dee58 100644 --- a/src/OpenProtocolInterpreter/Tool/Mid0046.cs +++ b/src/OpenProtocolInterpreter/Tool/Mid0046.cs @@ -9,8 +9,8 @@ namespace OpenProtocolInterpreter.Tool /// Warning 2: the new configuration will not be active until the next controller reboot! /// Message sent by: Integrator /// - /// Answer: Command accepted or - /// Command error, Programming control not granted or + /// Answer: Command accepted or + /// Command error, Programming control not granted or /// Invalid data (value not supported by controller) /// /// @@ -20,11 +20,8 @@ public class Mid0046 : Mid, ITool, IIntegrator, IAcceptableCommand, IDeclinableC public IEnumerable DocumentedPossibleErrors => new Error[] { Error.ProgrammingControlNotGranted, Error.InvalidData }; - public PrimaryTool PrimaryTool - { - get => (PrimaryTool)GetField(1, DataFields.PrimaryTool).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.PrimaryTool).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 2)] + public PrimaryTool PrimaryTool { get; set; } public Mid0046() : this(new Header() { @@ -37,23 +34,5 @@ public Mid0046() : this(new Header() public Mid0046(Header header) : base(header) { } - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.PrimaryTool, 20, 2) - } - } - }; - } - - protected enum DataFields - { - PrimaryTool - } } } diff --git a/src/OpenProtocolInterpreter/Tool/Mid0047.cs b/src/OpenProtocolInterpreter/Tool/Mid0047.cs index d4841ebf..90409b85 100644 --- a/src/OpenProtocolInterpreter/Tool/Mid0047.cs +++ b/src/OpenProtocolInterpreter/Tool/Mid0047.cs @@ -5,9 +5,9 @@ namespace OpenProtocolInterpreter.Tool /// /// Tool Pairing handling /// - /// This message is sent by the integrator in order to Pair tools, to abort ongoing pairing, + /// This message is sent by the integrator in order to Pair tools, to abort ongoing pairing, /// to Abort/Disconnect established connection and request for pairing status of the IRC-B or IRC-W tool types. - /// At pairing handling type, Start Pairing and Pairing Abort or Disconnect the controller must take program control + /// At pairing handling type, Start Pairing and Pairing Abort or Disconnect the controller must take program control /// and release when finished. MID 0048 will be uploaded during the pairing process at each change of the pairing stage. /// /// Message sent by: Integrator @@ -22,11 +22,8 @@ public class Mid0047 : Mid, ITool, IIntegrator, IAcceptableCommand, IDeclinableC public IEnumerable DocumentedPossibleErrors => new Error[] { }; - public PairingHandlingType PairingHandlingType - { - get => (PairingHandlingType)GetField(1, DataFields.PairingHandlingType).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.PairingHandlingType).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 2)] + public PairingHandlingType PairingHandlingType { get; set; } public Mid0047() : this(new Header() { @@ -39,23 +36,5 @@ public Mid0047() : this(new Header() public Mid0047(Header header) : base(header) { } - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.PairingHandlingType, 20, 2) - } - } - }; - } - - protected enum DataFields - { - PairingHandlingType - } } } diff --git a/src/OpenProtocolInterpreter/Tool/Mid0048.cs b/src/OpenProtocolInterpreter/Tool/Mid0048.cs index 6b062c32..18f68501 100644 --- a/src/OpenProtocolInterpreter/Tool/Mid0048.cs +++ b/src/OpenProtocolInterpreter/Tool/Mid0048.cs @@ -13,16 +13,11 @@ public class Mid0048 : Mid, ITool, IController { public const int MID = 48; - public PairingStatus PairingStatus - { - get => (PairingStatus)GetField(1, DataFields.PairingStatus).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.PairingStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public DateTime TimeStamp - { - get => GetField(1, DataFields.Timestamp).GetValue(OpenProtocolConvert.ToDateTime); - set => GetField(1, DataFields.Timestamp).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 2)] + public PairingStatus PairingStatus { get; set; } + + [TimestampDataFieldDefinition(revision: 1, field: 2, Index = 24)] + public DateTime TimeStamp { get; set; } public Mid0048() : this(new Header() { @@ -35,25 +30,5 @@ public Mid0048() : this(new Header() public Mid0048(Header header) : base(header) { } - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.PairingStatus, 20, 2), - DataField.Timestamp(DataFields.Timestamp, 24) - } - } - }; - } - - protected enum DataFields - { - PairingStatus, - Timestamp - } } } diff --git a/src/OpenProtocolInterpreter/Tool/OpenEndData.cs b/src/OpenProtocolInterpreter/Tool/OpenEndData.cs index fe75e201..ef3fd42b 100644 --- a/src/OpenProtocolInterpreter/Tool/OpenEndData.cs +++ b/src/OpenProtocolInterpreter/Tool/OpenEndData.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; namespace OpenProtocolInterpreter.Tool { @@ -28,14 +29,7 @@ public string Pack() } public static OpenEndData Parse(string value) - { - return new OpenEndData() - { - UseOpenEnd = OpenProtocolConvert.ToBoolean(value[0].ToString()), - TighteningDirection = (TighteningDirection)OpenProtocolConvert.ToInt32(value[1].ToString()), - MotorRotation = (MotorRotation)OpenProtocolConvert.ToInt32(value[2].ToString()), - }; - } + => Parse(value.AsSpan()); public static OpenEndData Parse(ReadOnlySpan value) { @@ -47,4 +41,31 @@ public static OpenEndData Parse(ReadOnlySpan value) }; } } + + public class OpenEndDataDefinitionAttribute : DataFieldDefinitionAttribute + { + public OpenEndDataDefinitionAttribute(int revision) : base(revision) + { + + } + public OpenEndDataDefinitionAttribute(int field, int revision) : base(field, revision) + { + + } + + internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + { + return new DataField(Field, index, Size, HasPrefix) + { + DefaultConverter = PackOpenEndData, + DefaultParser = ParseOpenEndData + }.Bind(mid, propertyInfo); + } + + private string PackOpenEndData(char paddingChar, int size, PaddingOrientation orientation, OpenEndData openEndData) + => openEndData.Pack(); + + private OpenEndData ParseOpenEndData(string value) + => OpenEndData.Parse(value); + } } From 5fdfee0d49c598dea2c6ac2e6cc3e71694bb62e9 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Mon, 27 Jul 2026 23:33:18 -0300 Subject: [PATCH 52/78] User interface mids using datafield attributes --- .../UserInterface/Mid0110.cs | 27 +----- .../UserInterface/Mid0111.cs | 83 +++++-------------- 2 files changed, 24 insertions(+), 86 deletions(-) diff --git a/src/OpenProtocolInterpreter/UserInterface/Mid0110.cs b/src/OpenProtocolInterpreter/UserInterface/Mid0110.cs index 6d546095..3d958363 100644 --- a/src/OpenProtocolInterpreter/UserInterface/Mid0110.cs +++ b/src/OpenProtocolInterpreter/UserInterface/Mid0110.cs @@ -14,7 +14,7 @@ namespace OpenProtocolInterpreter.UserInterface /// /// Message sent by: Integrator /// - /// Answer: Command accepted or + /// Answer: Command accepted or /// Command error, User text could not be displayed /// /// @@ -24,11 +24,8 @@ public class Mid0110 : Mid, IUserInterface, IIntegrator, IAcceptableCommand, IDe public IEnumerable DocumentedPossibleErrors => new Error[] { }; - public string UserText - { - get => GetField(1, DataFields.UserText).Value; - set => GetField(1, DataFields.UserText).SetValue(value); - } + [StringDataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 4, HasPrefix = false)] + public string UserText { get; set; } public Mid0110() : base(MID, DEFAULT_REVISION) { @@ -38,23 +35,5 @@ public Mid0110() : base(MID, DEFAULT_REVISION) public Mid0110(Header header) : base(header) { } - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.String(DataFields.UserText, 20, 4, false), - } - } - }; - } - - protected enum DataFields - { - UserText - } } } diff --git a/src/OpenProtocolInterpreter/UserInterface/Mid0111.cs b/src/OpenProtocolInterpreter/UserInterface/Mid0111.cs index 4ee17c5c..b1bf62ee 100644 --- a/src/OpenProtocolInterpreter/UserInterface/Mid0111.cs +++ b/src/OpenProtocolInterpreter/UserInterface/Mid0111.cs @@ -5,18 +5,18 @@ namespace OpenProtocolInterpreter.UserInterface /// /// Display user text on graph /// - /// By sending this message the integrator can display a text on the graphic display. - /// The user can furthermore set the time for the text to be displayed and if the text + /// By sending this message the integrator can display a text on the graphic display. + /// The user can furthermore set the time for the text to be displayed and if the text /// should be acknowledged by the operator or not. /// /// - /// The text is divided into four lines with 25 ASCII characters each.If a line is shorter + /// The text is divided into four lines with 25 ASCII characters each.If a line is shorter /// than 25 characters it must be right padded with blanks(SPC 0x20). /// /// The first line is the text header and is in upper character. /// Message sent by: Integrator /// - /// Answer: Command accepted or + /// Answer: Command accepted or /// Command error, User text could not be displayed /// /// @@ -26,36 +26,23 @@ public class Mid0111 : Mid, IUserInterface, IIntegrator, IAcceptableCommand, IDe public IEnumerable DocumentedPossibleErrors => new Error[] { }; - public int TextDuration - { - get => GetField(1, DataFields.TextDuration).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.TextDuration).SetValue(OpenProtocolConvert.ToString, value); - } - public RemovalCondition RemovalCondition - { - get => (RemovalCondition)GetField(1, DataFields.RemovalCondition).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.RemovalCondition).SetValue(OpenProtocolConvert.ToString, value); - } - public string Line1 - { - get => GetField(1, DataFields.Line1Header).Value; - set => GetField(1, DataFields.Line1Header).SetValue(value); - } - public string Line2 - { - get => GetField(1, DataFields.Line2).Value; - set => GetField(1, DataFields.Line2).SetValue(value); - } - public string Line3 - { - get => GetField(1, DataFields.Line3).Value; - set => GetField(1, DataFields.Line3).SetValue(value); - } - public string Line4 - { - get => GetField(1, DataFields.Line4).Value; - set => GetField(1, DataFields.Line4).SetValue(value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 4)] + public int TextDuration { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 26, Size = 1)] + public RemovalCondition RemovalCondition { get; set; } + + [StringDataFieldDefinition(revision: 1, field: 3, Index = 29, Size = 25)] + public string Line1 { get; set; } + + [StringDataFieldDefinition(revision: 1, field: 4, Index = 56, Size = 25)] + public string Line2 { get; set; } + + [StringDataFieldDefinition(revision: 1, field: 5, Index = 83, Size = 25)] + public string Line3 { get; set; } + + [StringDataFieldDefinition(revision: 1, field: 6, Index = 110, Size = 25)] + public string Line4 { get; set; } public Mid0111() : this(new Header() { @@ -68,33 +55,5 @@ public Mid0111() : this(new Header() public Mid0111(Header header) : base(header) { } - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.TextDuration, 20, 4), - DataField.Number(DataFields.RemovalCondition, 26, 1), - DataField.String(DataFields.Line1Header, 29, 25), - DataField.String(DataFields.Line2, 56, 25), - DataField.String(DataFields.Line3, 83, 25), - DataField.String(DataFields.Line4, 110, 25) - } - } - }; - } - - protected enum DataFields - { - TextDuration, - RemovalCondition, - Line1Header, - Line2, - Line3, - Line4 - } } } From 22e09c436a6f4a1c1059779c413d20b2e2e3275c Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Mon, 27 Jul 2026 23:50:07 -0300 Subject: [PATCH 53/78] Vin messages using datafield attribute --- src/MIDTesters.Core/Vin/TestMid0052.cs | 33 +------ src/OpenProtocolInterpreter/Job/Mid0033.cs | 2 +- src/OpenProtocolInterpreter/MID.cs | 4 +- src/OpenProtocolInterpreter/Vin/Mid0050.cs | 33 ++----- src/OpenProtocolInterpreter/Vin/Mid0052.cs | 110 ++++++++------------- 5 files changed, 51 insertions(+), 131 deletions(-) diff --git a/src/MIDTesters.Core/Vin/TestMid0052.cs b/src/MIDTesters.Core/Vin/TestMid0052.cs index 957b39a6..32d8b057 100644 --- a/src/MIDTesters.Core/Vin/TestMid0052.cs +++ b/src/MIDTesters.Core/Vin/TestMid0052.cs @@ -60,36 +60,7 @@ public void Mid0052ByteRevision1VehicleIdLengthLower() [TestMethod] [TestCategory("Revision 2"), TestCategory("ASCII")] - public void Mid0052Revision2VehicleIdLengthHigher() - { - string package = "01300052002 01VehicleIdNumberHigherThan2502IdentifierPart2 03IdentifierPart3 04IdentifierPart4 "; - var mid = _midInterpreter.Parse(package); - - Assert.AreEqual("VehicleIdNumberHigherThan25", mid.VinNumber); - Assert.AreEqual("IdentifierPart2", mid.IdentifierResultPart2.TrimEnd()); - Assert.AreEqual("IdentifierPart3", mid.IdentifierResultPart3.TrimEnd()); - Assert.AreEqual("IdentifierPart4", mid.IdentifierResultPart4.TrimEnd()); - AssertEqualPackages(package, mid); - } - - [TestMethod] - [TestCategory("Revision 2"), TestCategory("ByteArray")] - public void Mid0052ByteRevision2VehicleIdLengthHigher() - { - string package = "01300052002 01VehicleIdNumberHigherThan2502IdentifierPart2 03IdentifierPart3 04IdentifierPart4 "; - byte[] bytes = GetAsciiBytes(package); - var mid = _midInterpreter.Parse(bytes); - - Assert.AreEqual("VehicleIdNumberHigherThan25", mid.VinNumber); - Assert.AreEqual("IdentifierPart2", mid.IdentifierResultPart2.TrimEnd()); - Assert.AreEqual("IdentifierPart3", mid.IdentifierResultPart3.TrimEnd()); - Assert.AreEqual("IdentifierPart4", mid.IdentifierResultPart4.TrimEnd()); - AssertEqualPackages(bytes, mid); - } - - [TestMethod] - [TestCategory("Revision 2"), TestCategory("ASCII")] - public void Mid0052Revision2VehicleIdLengthLower() + public void Mid0052Revision2() { string package = "01280052002 01VehicleIdNumber 02IdentifierPart2 03IdentifierPart3 04IdentifierPart4 "; var mid = _midInterpreter.Parse(package); @@ -103,7 +74,7 @@ public void Mid0052Revision2VehicleIdLengthLower() [TestMethod] [TestCategory("Revision 2"), TestCategory("ByteArray")] - public void Mid0052ByteRevision2VehicleIdLengthLower() + public void Mid0052ByteRevision2() { string package = "01280052002 01VehicleIdNumber 02IdentifierPart2 03IdentifierPart3 04IdentifierPart4 "; byte[] bytes = GetAsciiBytes(package); diff --git a/src/OpenProtocolInterpreter/Job/Mid0033.cs b/src/OpenProtocolInterpreter/Job/Mid0033.cs index 3386f355..59b28d18 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0033.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0033.cs @@ -119,7 +119,7 @@ public override string Pack() return builder.ToString(); } - protected override void ProcessDataFields(List dataFields, ReadOnlySpan package) + protected override void ProcessDataFields(IEnumerable dataFields, ReadOnlySpan package) { foreach (var dataField in DataFieldsByRevision()) ProcessDataField(dataField, package); diff --git a/src/OpenProtocolInterpreter/MID.cs b/src/OpenProtocolInterpreter/MID.cs index a171bc6c..71468796 100644 --- a/src/OpenProtocolInterpreter/MID.cs +++ b/src/OpenProtocolInterpreter/MID.cs @@ -223,10 +223,10 @@ protected virtual void ProcessDataFields(int revision, ReadOnlySpan packag } } - protected virtual void ProcessDataFields(List dataFields, string package) + protected virtual void ProcessDataFields(IEnumerable dataFields, string package) => ProcessDataFields(dataFields, package.AsSpan()); - protected virtual void ProcessDataFields(List dataFields, ReadOnlySpan package) + protected virtual void ProcessDataFields(IEnumerable dataFields, ReadOnlySpan package) { foreach (var dataField in dataFields) ProcessDataField(dataField, package); diff --git a/src/OpenProtocolInterpreter/Vin/Mid0050.cs b/src/OpenProtocolInterpreter/Vin/Mid0050.cs index ad20f83a..02d2ea30 100644 --- a/src/OpenProtocolInterpreter/Vin/Mid0050.cs +++ b/src/OpenProtocolInterpreter/Vin/Mid0050.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace OpenProtocolInterpreter.Vin { @@ -11,7 +11,7 @@ namespace OpenProtocolInterpreter.Vin /// Used by the integrator to send a VIN number to the controller. /// Message sent by: Integrator /// - /// Answer: Command accepted or + /// Answer: Command accepted or /// Command error, VIN input source not granted /// /// @@ -21,11 +21,8 @@ public class Mid0050 : Mid, IVin, IIntegrator, IAcceptableCommand, IDeclinableCo public IEnumerable DocumentedPossibleErrors => new Error[] { Error.VINInputSourceNotGranted }; - public string VinNumber - { - get => GetField(1, DataFields.VinNumber).Value; - set => GetField(1, DataFields.VinNumber).SetValue(value); - } + [StringDataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 25, HasPrefix = false)] + public string VinNumber { get; set; } public Mid0050() : base(MID, DEFAULT_REVISION) { } @@ -35,34 +32,16 @@ public Mid0050(Header header) : base(header) public override string Pack() { - GetField(1, DataFields.VinNumber).Size = VinNumber.Length; + GetField(nameof(VinNumber)).Size = VinNumber.Length; return base.Pack(); } public override Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); - GetField(1, DataFields.VinNumber).Size = Header.Length - 20; + GetField(nameof(VinNumber)).Size = Header.Length - 20; ProcessDataFields(package); return this; } - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Volatile(DataFields.VinNumber, 20, false), //dynamic - } - } - }; - } - - protected enum DataFields - { - VinNumber - } } } diff --git a/src/OpenProtocolInterpreter/Vin/Mid0052.cs b/src/OpenProtocolInterpreter/Vin/Mid0052.cs index c07dc2e3..2c0f7b70 100644 --- a/src/OpenProtocolInterpreter/Vin/Mid0052.cs +++ b/src/OpenProtocolInterpreter/Vin/Mid0052.cs @@ -1,5 +1,6 @@ using System; -using System.Collections.Generic; +using System.Collections.Generic; +using System.Text; namespace OpenProtocolInterpreter.Vin { @@ -24,26 +25,21 @@ public class Mid0052 : Mid, IVin, IController, IAcknowledgeable { public const int MID = 52; - public string VinNumber - { - get => GetField(1, DataFields.VinNumber).Value; - set => GetField(1, DataFields.VinNumber).SetValue(value); - } - public string IdentifierResultPart2 - { - get => GetField(2, DataFields.IdentifierResultPart2).Value; - set => GetField(2, DataFields.IdentifierResultPart2).SetValue(value); - } - public string IdentifierResultPart3 - { - get => GetField(2, DataFields.IdentifierResultPart3).Value; - set => GetField(2, DataFields.IdentifierResultPart3).SetValue(value); - } - public string IdentifierResultPart4 - { - get => GetField(2, DataFields.IdentifierResultPart4).Value; - set => GetField(2, DataFields.IdentifierResultPart4).SetValue(value); - } + /// + /// Note: Only for PowerMACS and rev 000-001, the VIN number can be up to 40 bytes long. Minimum number of bytes is always 25. + /// + [StringDataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 25, HasPrefix = false)] + [StringDataFieldDefinition(revision: 2, field: 1, Index = 20, Size = 25)] + public string VinNumber { get; set; } + + [StringDataFieldDefinition(revision: 2, field: 2, Index = 47, Size = 25)] + public string IdentifierResultPart2 { get; set; } + + [StringDataFieldDefinition(revision: 2, field: 3, Index = 74, Size = 25)] + public string IdentifierResultPart3 { get; set; } + + [StringDataFieldDefinition(revision: 2, field: 4, Index = 101, Size = 25)] + public string IdentifierResultPart4 { get; set; } public Mid0052() : this(DEFAULT_REVISION) { @@ -56,66 +52,40 @@ public Mid0052(Header header) : base(header) public Mid0052(int revision) : base(MID, revision) { } - public override string Pack() + protected override string BuildHeader() { - var vinNumberField = GetField(1, DataFields.VinNumber); - if (Header.Revision > 1) - vinNumberField.HasPrefix = true; + Header.Length = Header.DefaultSize; + foreach (var dataField in DataFieldsByRevision()) + Header.Length += dataField.TotalSize; - //Can be up to 40 bytes long - vinNumberField.Size = (VinNumber.Length > 25) ? VinNumber.Length : 25; - return base.Pack(); + return Header.ToString(); } - public override Mid Parse(ReadOnlySpan package) + public override string Pack() { - Header = ProcessHeader(package); - if (Header.Revision > 1) - { - var vinNumberField = GetField(1, DataFields.VinNumber); - vinNumberField.HasPrefix = true; - vinNumberField.Size = Header.Length - 103; - if (vinNumberField.Size > 25) - { - int addedSize = vinNumberField.Size - 25; - GetField(2, DataFields.IdentifierResultPart2).Index += addedSize; - GetField(2, DataFields.IdentifierResultPart3).Index += addedSize; - GetField(2, DataFields.IdentifierResultPart4).Index += addedSize; - } - } - else - GetField(1, DataFields.VinNumber).Size = Header.Length - 20; - ProcessDataFields(package); - return this; + if (Header.StandardizedRevision == 1) + GetField(nameof(VinNumber)).Size = (VinNumber.Length > 25) ? VinNumber.Length : 25; + + var header = BuildHeader(); + var builder = new StringBuilder(Header.Length).Append(header).Append(base.Pack(DataFieldsByRevision())); + return builder.ToString(); } - protected override Dictionary> RegisterDatafields() + protected override void ProcessDataFields(ReadOnlySpan package) + => base.ProcessDataFields(DataFieldsByRevision(), package); + + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - return new Dictionary>() - { - { - 1, new List() - { - DataField.String(DataFields.VinNumber, 20, 25, false) - } - }, - { - 2, new List() - { - DataField.String(DataFields.IdentifierResultPart2, 47, 25), - DataField.String(DataFields.IdentifierResultPart3, 74, 25), - DataField.String(DataFields.IdentifierResultPart4, 101, 25) - } - } - }; + if (Header.StandardizedRevision == 1 && dataField.Field == 1) + dataField.Size = Header.Length - Header.DefaultSize; + + base.ProcessDataField(dataField, package); } - protected enum DataFields + private IEnumerable DataFieldsByRevision() { - VinNumber, - IdentifierResultPart2, - IdentifierResultPart3, - IdentifierResultPart4, + foreach (var dataField in RevisionsByFields[Header.StandardizedRevision]) + yield return dataField; } } } From 6f34a319f0e7b75dc97160348e59678b68f6d22b Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Tue, 28 Jul 2026 00:00:12 -0300 Subject: [PATCH 54/78] Moved all marker interfaces into a single file --- .../IAcceptableCommand.cs | 9 --- src/OpenProtocolInterpreter/IAcknowledge.cs | 9 --- .../IAcknowledgeable.cs | 10 --- src/OpenProtocolInterpreter/IAnswerableBy.cs | 10 --- src/OpenProtocolInterpreter/IController.cs | 9 --- .../IDeclinableCommand.cs | 12 ---- src/OpenProtocolInterpreter/IIntegrator.cs | 9 --- .../IMarkerInterfaces.cs | 70 +++++++++++++++++++ src/OpenProtocolInterpreter/ISubscription.cs | 9 --- .../IUnsubscription.cs | 9 --- 10 files changed, 70 insertions(+), 86 deletions(-) delete mode 100644 src/OpenProtocolInterpreter/IAcceptableCommand.cs delete mode 100644 src/OpenProtocolInterpreter/IAcknowledge.cs delete mode 100644 src/OpenProtocolInterpreter/IAcknowledgeable.cs delete mode 100644 src/OpenProtocolInterpreter/IAnswerableBy.cs delete mode 100644 src/OpenProtocolInterpreter/IController.cs delete mode 100644 src/OpenProtocolInterpreter/IDeclinableCommand.cs delete mode 100644 src/OpenProtocolInterpreter/IIntegrator.cs create mode 100644 src/OpenProtocolInterpreter/IMarkerInterfaces.cs delete mode 100644 src/OpenProtocolInterpreter/ISubscription.cs delete mode 100644 src/OpenProtocolInterpreter/IUnsubscription.cs diff --git a/src/OpenProtocolInterpreter/IAcceptableCommand.cs b/src/OpenProtocolInterpreter/IAcceptableCommand.cs deleted file mode 100644 index 8a58937d..00000000 --- a/src/OpenProtocolInterpreter/IAcceptableCommand.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace OpenProtocolInterpreter -{ - /// - /// Contract of every message that can be accepted with implements. - /// - public interface IAcceptableCommand - { - } -} diff --git a/src/OpenProtocolInterpreter/IAcknowledge.cs b/src/OpenProtocolInterpreter/IAcknowledge.cs deleted file mode 100644 index 235f8103..00000000 --- a/src/OpenProtocolInterpreter/IAcknowledge.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace OpenProtocolInterpreter -{ - /// - /// Contract which every acknowledge message implements. - /// - public interface IAcknowledge - { - } -} diff --git a/src/OpenProtocolInterpreter/IAcknowledgeable.cs b/src/OpenProtocolInterpreter/IAcknowledgeable.cs deleted file mode 100644 index 9409874a..00000000 --- a/src/OpenProtocolInterpreter/IAcknowledgeable.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace OpenProtocolInterpreter -{ - /// - /// Contract which every that might needs an acknowledge response implements. - /// - public interface IAcknowledgeable where TAckMid : Mid, IAcknowledge, new() - { - - } -} diff --git a/src/OpenProtocolInterpreter/IAnswerableBy.cs b/src/OpenProtocolInterpreter/IAnswerableBy.cs deleted file mode 100644 index 9c8dc152..00000000 --- a/src/OpenProtocolInterpreter/IAnswerableBy.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace OpenProtocolInterpreter -{ - /// - /// Contract of every message that can be answered by another mid which is not classified as an acknowledge. - /// - public interface IAnswerableBy where TAnswerMid : Mid - { - - } -} diff --git a/src/OpenProtocolInterpreter/IController.cs b/src/OpenProtocolInterpreter/IController.cs deleted file mode 100644 index e03a8f37..00000000 --- a/src/OpenProtocolInterpreter/IController.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace OpenProtocolInterpreter -{ - /// - /// Contract which every controller message implements. - /// - public interface IController - { - } -} diff --git a/src/OpenProtocolInterpreter/IDeclinableCommand.cs b/src/OpenProtocolInterpreter/IDeclinableCommand.cs deleted file mode 100644 index 8d970fee..00000000 --- a/src/OpenProtocolInterpreter/IDeclinableCommand.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; - -namespace OpenProtocolInterpreter -{ - /// - /// Contract which every message that can be declined with implements. - /// - public interface IDeclinableCommand - { - IEnumerable DocumentedPossibleErrors { get; } - } -} diff --git a/src/OpenProtocolInterpreter/IIntegrator.cs b/src/OpenProtocolInterpreter/IIntegrator.cs deleted file mode 100644 index 21cdc3ff..00000000 --- a/src/OpenProtocolInterpreter/IIntegrator.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace OpenProtocolInterpreter -{ - /// - /// Contract which every integrator message implements. - /// - public interface IIntegrator - { - } -} diff --git a/src/OpenProtocolInterpreter/IMarkerInterfaces.cs b/src/OpenProtocolInterpreter/IMarkerInterfaces.cs new file mode 100644 index 00000000..56a3c015 --- /dev/null +++ b/src/OpenProtocolInterpreter/IMarkerInterfaces.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; + +namespace OpenProtocolInterpreter +{ + /// + /// Contract which every controller message implements. + /// + public interface IController + { + } + + /// + /// Contract which every integrator message implements. + /// + public interface IIntegrator + { + } + + /// + /// Contract which every acknowledge message implements. + /// + public interface IAcknowledge + { + } + + /// + /// Contract which every that might needs an acknowledge response implements. + /// + public interface IAcknowledgeable where TAckMid : Mid, IAcknowledge, new() + { + + } + + /// + /// Contract of every message that can be answered by another mid which is not classified as an acknowledge. + /// + public interface IAnswerableBy where TAnswerMid : Mid + { + + } + + /// + /// Contract of every message that can be accepted with implements. + /// + public interface IAcceptableCommand + { + } + + /// + /// Contract which every message that can be declined with implements. + /// + public interface IDeclinableCommand + { + IEnumerable DocumentedPossibleErrors { get; } + } + + /// + /// Contract which every subscription message implements. + /// + public interface ISubscription + { + } + + /// + /// Contract which every unsubscription message implements. + /// + public interface IUnsubscription + { + } +} diff --git a/src/OpenProtocolInterpreter/ISubscription.cs b/src/OpenProtocolInterpreter/ISubscription.cs deleted file mode 100644 index 06fd69bc..00000000 --- a/src/OpenProtocolInterpreter/ISubscription.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace OpenProtocolInterpreter -{ - /// - /// Contract which every subscription message implements. - /// - public interface ISubscription - { - } -} diff --git a/src/OpenProtocolInterpreter/IUnsubscription.cs b/src/OpenProtocolInterpreter/IUnsubscription.cs deleted file mode 100644 index f66260c8..00000000 --- a/src/OpenProtocolInterpreter/IUnsubscription.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace OpenProtocolInterpreter -{ - /// - /// Contract which every unsubscription message implements. - /// - public interface IUnsubscription - { - } -} From f46bfae37345c836a346adcf4dd678087a78cf05 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Tue, 28 Jul 2026 19:17:49 -0300 Subject: [PATCH 55/78] Reorganized and fixed some mids that were without index set at attribute --- src/MIDTesters.Core/Alarm/TestMid0071.cs | 35 +- src/OpenProtocolInterpreter/Alarm/Mid0071.cs | 68 ++-- src/OpenProtocolInterpreter/Alarm/Mid0074.cs | 38 +- src/OpenProtocolInterpreter/Alarm/Mid0076.cs | 73 ++-- src/OpenProtocolInterpreter/Alarm/Mid1000.cs | 24 +- .../ApplicationSelector/Mid0251.cs | 16 +- .../ApplicationSelector/Mid0254.cs | 11 +- .../ApplicationSelector/Mid0255.cs | 11 +- .../ApplicationToolLocationSystem/Mid0262.cs | 8 +- .../ApplicationToolLocationSystem/Mid0265.cs | 11 +- .../AutomaticManualMode/Mid0401.cs | 8 +- .../AutomaticManualMode/Mid0411.cs | 11 +- .../Communication/Mid0001.cs | 15 +- .../Communication/Mid0002.cs | 70 +--- .../Communication/Mid0004.cs | 15 +- .../Communication/Mid0005.cs | 8 +- .../Communication/Mid0006.cs | 18 +- .../Communication/Mid0008.cs | 20 +- .../Communication/Mid0009.cs | 20 +- .../IMarkerInterfaces.cs | 8 + .../IOInterface/Mid0200.cs | 35 +- .../IOInterface/Mid0211.cs | 29 +- .../IOInterface/Mid0214.cs | 8 +- .../IOInterface/Mid0215.cs | 27 +- .../IOInterface/Mid0216.cs | 8 +- .../IOInterface/Mid0217.cs | 11 +- .../IOInterface/Mid0219.cs | 8 +- .../IOInterface/Mid0220.cs | 8 +- .../IOInterface/Mid0221.cs | 11 +- .../IOInterface/Mid0223.cs | 8 +- .../IOInterface/Mid0224.cs | 8 +- .../IOInterface/Mid0225.cs | 8 +- .../Job/Advanced/Mid0129.cs | 7 - .../Job/Advanced/Mid0130.cs | 6 - .../Job/Advanced/Mid0140.cs | 27 -- src/OpenProtocolInterpreter/Job/Mid0031.cs | 15 +- src/OpenProtocolInterpreter/Job/Mid0032.cs | 10 +- src/OpenProtocolInterpreter/Job/Mid0033.cs | 76 ++-- src/OpenProtocolInterpreter/Job/Mid0035.cs | 26 -- src/OpenProtocolInterpreter/Job/Mid0038.cs | 6 - src/OpenProtocolInterpreter/Job/Mid0039.cs | 6 - .../LinkCommunication/Mid9997.cs | 6 - .../LinkCommunication/Mid9998.cs | 7 - .../MotorTuning/Mid0501.cs | 6 - .../MultiSpindle/Mid0091.cs | 10 - .../MultiSpindle/Mid0100.cs | 7 - .../MultiSpindle/Mid0101.cs | 28 -- .../MultiSpindle/Mid0104.cs | 6 - .../MultipleIdentifiers/Mid0150.cs | 6 - .../MultipleIdentifiers/Mid0152.cs | 9 - .../OpenProtocolCommandsDisabled/Mid0421.cs | 6 - .../ParameterSet/Mid0011.cs | 7 - .../ParameterSet/Mid0012.cs | 9 - .../ParameterSet/Mid0013.cs | 26 +- .../ParameterSet/Mid0015.cs | 21 -- .../ParameterSet/Mid0018.cs | 6 - .../ParameterSet/Mid0019.cs | 7 - .../ParameterSet/Mid0020.cs | 6 - .../ParameterSet/Mid0022.cs | 6 - .../ParameterSet/Mid2504.cs | 6 - .../ParameterSet/Mid2505.cs | 8 - .../ParameterSet/Mid2506.cs | 7 - src/OpenProtocolInterpreter/Result/Mid1201.cs | 66 ++-- .../Tightening/Mid0061.cs | 335 ++++++------------ .../Tightening/Mid0064.cs | 11 +- .../Tightening/Mid0065.cs | 225 ++++-------- .../Tightening/Mid0066.cs | 13 +- .../Tightening/Mid0902.cs | 26 +- 68 files changed, 520 insertions(+), 1192 deletions(-) diff --git a/src/MIDTesters.Core/Alarm/TestMid0071.cs b/src/MIDTesters.Core/Alarm/TestMid0071.cs index 7112edcf..958c67f3 100644 --- a/src/MIDTesters.Core/Alarm/TestMid0071.cs +++ b/src/MIDTesters.Core/Alarm/TestMid0071.cs @@ -41,14 +41,13 @@ public void Mid0071ByteRevision1() [TestCategory("Revision 2"), TestCategory("ASCII")] public void Mid0071Revision2() { - string pack = @"01060071002 01E1021021031042017-12-01:20:12:4505Alarm Text "; + string pack = @"00540071002 01E1021021031042017-12-01:20:12:45"; var mid = _midInterpreter.Parse(pack); Assert.AreEqual("E1021", mid.ErrorCode); Assert.IsTrue(mid.ControllerReadyStatus); Assert.IsTrue(mid.ToolReadyStatus); Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.Time); - Assert.AreEqual("Alarm Text ", mid.AlarmText); AssertEqualPackages(pack, mid); } @@ -56,7 +55,37 @@ public void Mid0071Revision2() [TestCategory("Revision 2"), TestCategory("ByteArray")] public void Mid0071ByteRevision2() { - string pack = @"01060071002 01E1021021031042017-12-01:20:12:4505Alarm Text "; + string pack = @"00540071002 01E1021021031042017-12-01:20:12:45"; + byte[] bytes = GetAsciiBytes(pack); + var mid = _midInterpreter.Parse(bytes); + + Assert.AreEqual("E1021", mid.ErrorCode); + Assert.IsTrue(mid.ControllerReadyStatus); + Assert.IsTrue(mid.ToolReadyStatus); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.Time); + AssertEqualPackages(bytes, mid); + } + + [TestMethod] + [TestCategory("Revision 3"), TestCategory("ASCII")] + public void Mid0071Revision3() + { + string pack = @"01090071003 01E1021021031042017-12-01:20:12:4505106Alarm Text "; + var mid = _midInterpreter.Parse(pack); + + Assert.AreEqual("E1021", mid.ErrorCode); + Assert.IsTrue(mid.ControllerReadyStatus); + Assert.IsTrue(mid.ToolReadyStatus); + Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.Time); + Assert.AreEqual("Alarm Text ", mid.AlarmText); + AssertEqualPackages(pack, mid); + } + + [TestMethod] + [TestCategory("Revision 3"), TestCategory("ByteArray")] + public void Mid0071ByteRevision3() + { + string pack = @"01090071003 01E1021021031042017-12-01:20:12:4505106Alarm Text "; byte[] bytes = GetAsciiBytes(pack); var mid = _midInterpreter.Parse(bytes); diff --git a/src/OpenProtocolInterpreter/Alarm/Mid0071.cs b/src/OpenProtocolInterpreter/Alarm/Mid0071.cs index ce7380df..f09b35d2 100644 --- a/src/OpenProtocolInterpreter/Alarm/Mid0071.cs +++ b/src/OpenProtocolInterpreter/Alarm/Mid0071.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text; namespace OpenProtocolInterpreter.Alarm { @@ -13,17 +14,30 @@ public class Mid0071 : Mid, IAlarm, IController, IAcknowledgeable { public const int MID = 71; - [StringDataFieldDefinition(field: 1, revision: 1, Size = 4, PaddingOrientation = PaddingOrientation.LeftPadded)] + [StringDataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 4, PaddingOrientation = PaddingOrientation.LeftPadded)] + [StringDataFieldDefinition(revision: 2, field: 1, Index = 20, Size = 5, PaddingOrientation = PaddingOrientation.LeftPadded)] + [StringDataFieldDefinition(revision: 3, field: 1, Index = 20, Size = 5, PaddingOrientation = PaddingOrientation.LeftPadded)] public string ErrorCode { get; set; } - [BooleanDataFieldDefinition(field: 2, revision: 1)] + [BooleanDataFieldDefinition(revision: 1, field: 2, Index = 26)] + [BooleanDataFieldDefinition(revision: 2, field: 2, Index = 27)] + [BooleanDataFieldDefinition(revision: 3, field: 2, Index = 27)] public bool ControllerReadyStatus { get; set; } - [BooleanDataFieldDefinition(field: 3, revision: 1)] + + [BooleanDataFieldDefinition(revision: 1, field: 3, Index = 29)] + [BooleanDataFieldDefinition(revision: 2, field: 3, Index = 30)] + [BooleanDataFieldDefinition(revision: 3, field: 3, Index = 30)] public bool ToolReadyStatus { get; set; } - [TimestampDataFieldDefinition(field: 4, revision: 1)] + + [TimestampDataFieldDefinition(revision: 1, field: 4, Index = 32)] + [TimestampDataFieldDefinition(revision: 2, field: 4, Index = 33)] + [TimestampDataFieldDefinition(revision: 3, field: 4, Index = 33)] public DateTime Time { get; set; } - [StringDataFieldDefinition(field: 5, revision: 2, Index = 54, Size = 50)] //Always has index 54 due to error code field size change + [Int32DataFieldDefinition(revision: 3, field: 5, Index = 54, Size = 1)] + public ToolHealth ToolHealth { get; set; } + + [StringDataFieldDefinition(revision: 3, field: 6, Index = 57, Size = 50)] public string AlarmText { get; set; } public Mid0071() : this(DEFAULT_REVISION) @@ -33,7 +47,6 @@ public Mid0071() : this(DEFAULT_REVISION) public Mid0071(Header header) : base(header) { - HandleRevision(); } public Mid0071(int revision) : this(new Header() @@ -45,41 +58,38 @@ public Mid0071(int revision) : this(new Header() } - public override string Pack() + protected override string BuildHeader() { - HandleRevision(); - return base.Pack(); + Header.Length = Header.DefaultSize; + foreach (var field in RevisionsByFields[Header.StandardizedRevision]) + { + Header.Length += field.TotalSize; + } + + return Header.ToString(); } - protected override void ProcessDataFields(ReadOnlySpan package) + public override string Pack() { - HandleRevision(); - base.ProcessDataFields(package); + var header = BuildHeader(); + var builder = new StringBuilder(Header.Length); + builder.Append(header); + builder.Append(base.Pack(DataFieldsByRevision())); + return builder.ToString(); } - private void HandleRevision() + protected override void ProcessDataFields(ReadOnlySpan package) { - var errorCodeField = GetField(revision: 1, field: 0); - errorCodeField.Size = Header.Revision > 1 ? 5 : 4; - - int index = errorCodeField.Index + errorCodeField.Size; - for (int fieldIndex = errorCodeField.Field + 1; fieldIndex < RevisionsByFields[1].Count; fieldIndex++) + foreach (var field in DataFieldsByRevision()) { - var field = GetField(revision: 1, field: fieldIndex); - field.Index = 2 + index; - index = field.Index + field.Size; + ProcessDataField(field, package); } } - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields + private IEnumerable DataFieldsByRevision() { - ErrorCode, - ControllerReadyStatus, - ToolReadyStatus, - Time, - //Rev 2 - AlarmText + foreach (var field in RevisionsByFields[Header.StandardizedRevision]) + yield return field; } } } diff --git a/src/OpenProtocolInterpreter/Alarm/Mid0074.cs b/src/OpenProtocolInterpreter/Alarm/Mid0074.cs index bf342f3c..634dc34c 100644 --- a/src/OpenProtocolInterpreter/Alarm/Mid0074.cs +++ b/src/OpenProtocolInterpreter/Alarm/Mid0074.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text; namespace OpenProtocolInterpreter.Alarm { @@ -13,7 +14,8 @@ public class Mid0074 : Mid, IAlarm, IController, IAcknowledgeable { public const int MID = 74; - [StringDataFieldDefinition(field: 0, revision: 1, Size = 4, HasPrefix = false, PaddingOrientation = PaddingOrientation.LeftPadded)] + [StringDataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 4, HasPrefix = false, PaddingOrientation = PaddingOrientation.LeftPadded)] + [StringDataFieldDefinition(revision: 2, field: 1, Index = 20, Size = 5, HasPrefix = false, PaddingOrientation = PaddingOrientation.LeftPadded)] public string ErrorCode { get; set; } public Mid0074() : this(DEFAULT_REVISION) @@ -31,28 +33,38 @@ public Mid0074(int revision) : base(MID, revision) } - public override string Pack() + protected override string BuildHeader() { - HandleRevision(); - return base.Pack(); + Header.Length = Header.DefaultSize; + foreach (var field in RevisionsByFields[Header.StandardizedRevision]) + { + Header.Length += field.TotalSize; + } + + return Header.ToString(); } - protected override void ProcessDataFields(ReadOnlySpan package) + public override string Pack() { - HandleRevision(); - base.ProcessDataFields(package); + var header = BuildHeader(); + var builder = new StringBuilder(Header.Length); + builder.Append(header); + builder.Append(base.Pack(DataFieldsByRevision())); + return builder.ToString(); } - private void HandleRevision() + protected override void ProcessDataFields(ReadOnlySpan package) { - var errorCodeField = GetField(revision: 1, field: 0); - errorCodeField.Size = Header.Revision > 1 ? 5 : 4; + foreach (var field in DataFieldsByRevision()) + { + ProcessDataField(field, package); + } } - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields + private IEnumerable DataFieldsByRevision() { - ErrorCode + foreach (var field in RevisionsByFields[Header.StandardizedRevision]) + yield return field; } } } diff --git a/src/OpenProtocolInterpreter/Alarm/Mid0076.cs b/src/OpenProtocolInterpreter/Alarm/Mid0076.cs index f07dd8f1..35a35847 100644 --- a/src/OpenProtocolInterpreter/Alarm/Mid0076.cs +++ b/src/OpenProtocolInterpreter/Alarm/Mid0076.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text; namespace OpenProtocolInterpreter.Alarm { @@ -14,17 +15,32 @@ public class Mid0076 : Mid, IAlarm, IController, IAcknowledgeable { public const int MID = 76; - [BooleanDataFieldDefinition(field: 0, revision: 1)] + [BooleanDataFieldDefinition(revision: 1, field: 1, Index = 20)] + [BooleanDataFieldDefinition(revision: 2, field: 1, Index = 20)] + [BooleanDataFieldDefinition(revision: 3, field: 1, Index = 20)] public bool AlarmStatus { get; set; } - [StringDataFieldDefinition(field: 1, revision: 1, Size = 4, PaddingOrientation = PaddingOrientation.LeftPadded)] + + [StringDataFieldDefinition(revision: 1, field: 2, Index = 23, Size = 4, PaddingOrientation = PaddingOrientation.LeftPadded)] + [StringDataFieldDefinition(revision: 2, field: 2, Index = 23, Size = 5, PaddingOrientation = PaddingOrientation.LeftPadded)] + [StringDataFieldDefinition(revision: 3, field: 2, Index = 23, Size = 5, PaddingOrientation = PaddingOrientation.LeftPadded)] public string ErrorCode { get; set; } - [BooleanDataFieldDefinition(field: 2, revision: 1)] + + [BooleanDataFieldDefinition(revision: 1, field: 3, Index = 29)] + [BooleanDataFieldDefinition(revision: 2, field: 3, Index = 30)] + [BooleanDataFieldDefinition(revision: 3, field: 3, Index = 30)] public bool ControllerReadyStatus { get; set; } - [BooleanDataFieldDefinition(field: 3, revision: 1)] + + [BooleanDataFieldDefinition(revision: 1, field: 4, Index = 32)] + [BooleanDataFieldDefinition(revision: 2, field: 4, Index = 33)] + [BooleanDataFieldDefinition(revision: 3, field: 4, Index = 33)] public bool ToolReadyStatus { get; set; } - [TimestampDataFieldDefinition(field: 4, revision: 1)] + + [TimestampDataFieldDefinition(revision: 1, field: 5, Index = 35)] + [TimestampDataFieldDefinition(revision: 2, field: 5, Index = 36)] + [TimestampDataFieldDefinition(revision: 3, field: 5, Index = 36)] public DateTime Time { get; set; } - [Int32DataFieldDefinition(field: 5, revision: 3, Index = 57, Size = 1)] + + [Int32DataFieldDefinition(revision: 3, field: 6, Index = 57, Size = 1)] public ToolHealth ToolHealth { get; set; } public Mid0076() : this(DEFAULT_REVISION) @@ -34,7 +50,7 @@ public Mid0076() : this(DEFAULT_REVISION) public Mid0076(Header header) : base(header) { - HandleRevision(); + } public Mid0076(int revision) : this(new Header() @@ -46,41 +62,38 @@ public Mid0076(int revision) : this(new Header() } - public override string Pack() + protected override string BuildHeader() { - HandleRevision(); - return base.Pack(); + Header.Length = Header.DefaultSize; + foreach (var field in RevisionsByFields[Header.StandardizedRevision]) + { + Header.Length += field.TotalSize; + } + + return Header.ToString(); } - protected override void ProcessDataFields(ReadOnlySpan package) + public override string Pack() { - HandleRevision(); - base.ProcessDataFields(package); + var header = BuildHeader(); + var builder = new StringBuilder(Header.Length); + builder.Append(header); + builder.Append(base.Pack(DataFieldsByRevision())); + return builder.ToString(); } - private void HandleRevision() + protected override void ProcessDataFields(ReadOnlySpan package) { - var errorCodeField = GetField(revision: 1, field: 1); - errorCodeField.Size = Header.Revision > 1 ? 5 : 4; - - int index = errorCodeField.Index + errorCodeField.Size; - for (int fieldIndex = errorCodeField.Field + 1; fieldIndex < RevisionsByFields[1].Count; fieldIndex++) + foreach (var field in DataFieldsByRevision()) { - var field = GetField(revision: 1, field: fieldIndex); - field.Index = 2 + index; - index = field.Index + field.Size; + ProcessDataField(field, package); } } - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields + private IEnumerable DataFieldsByRevision() { - AlarmStatus, - ErrorCode, - ControllerReadyStatus, - ToolReadyStatus, - Time, - ToolHealth + foreach (var field in RevisionsByFields[Header.StandardizedRevision]) + yield return field; } } } diff --git a/src/OpenProtocolInterpreter/Alarm/Mid1000.cs b/src/OpenProtocolInterpreter/Alarm/Mid1000.cs index 842f062a..90906d16 100644 --- a/src/OpenProtocolInterpreter/Alarm/Mid1000.cs +++ b/src/OpenProtocolInterpreter/Alarm/Mid1000.cs @@ -15,13 +15,16 @@ public class Mid1000 : Mid, IAlarm, IController, IAcknowledgeable { public const int MID = 1000; - [StringDataFieldDefinition(field: 0, revision: 1, Size = 5, HasPrefix = false, PaddingOrientation = PaddingOrientation.LeftPadded)] + [StringDataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 5, HasPrefix = false, PaddingOrientation = PaddingOrientation.LeftPadded)] public string AlarmCode { get; set; } - [TimestampDataFieldDefinition(field: 1, revision: 1, HasPrefix = false)] + + [TimestampDataFieldDefinition(revision: 1, field: 2, Index = 25, HasPrefix = false)] public DateTime Time { get; set; } - [Int32DataFieldDefinition(field: 2, revision: 1, Size = 3, HasPrefix = false)] + + [Int32DataFieldDefinition(revision: 1, field: 3, Index = 44, Size = 3, HasPrefix = false)] public int NumberOfDataFields { get; set; } - [VariableDataFieldCollectionDefinition(field: 3, revision: 1, HasPrefix = false)] + + [VariableDataFieldCollectionDefinition(revision: 1, field: 4, Index = 47, HasPrefix = false)] public List AlarmDataFields { get; set; } public Mid1000() : this(DEFAULT_REVISION) @@ -46,26 +49,17 @@ public Mid1000(int revision) : this(new Header() public override string Pack() { NumberOfDataFields = AlarmDataFields?.Count ?? 0; //Enforce list size even if modified - GetField(revision: 1, field: 3).Size = AlarmDataFields?.Sum(x => x.TotalSize) ?? 0; //Enforce size of variable data fields + GetField(revision: 1, field: 4).Size = AlarmDataFields?.Sum(x => x.TotalSize) ?? 0; //Enforce size of variable data fields return base.Pack(); } protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - if (dataField.Field == 3) //AlarmDataFields + if (dataField.Field == 4) //AlarmDataFields { dataField.Size = Header.Length - dataField.Index; } base.ProcessDataField(dataField, package); } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - AlarmCode, - Time, - NumberOfDataFields, - EachAlarmDataField - } } } diff --git a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0251.cs b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0251.cs index 6b93f924..c0635c83 100644 --- a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0251.cs +++ b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0251.cs @@ -20,11 +20,13 @@ public class Mid0251 : Mid, IApplicationSelector, IController, IAcknowledgeable< { public const int MID = 251; - [Int32DataFieldDefinition(field: 1, revision: 1, Size = 2)] + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 2)] public int DeviceId { get; set; } - [Int32DataFieldDefinition(field: 2, revision: 1, Size = 2)] + + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 24, Size = 2)] public int NumberOfSockets { get; set; } - [SocketStatusListDefinition(field: 3, revision: 1)] + + [SocketStatusListDefinition(revision: 1, field: 3, Index = 28)] public List SocketStatus { get; set; } public Mid0251() : this(new Header() @@ -95,13 +97,5 @@ protected static List ParseSocketStatus(string section) return list; } } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - DeviceId, - NumberOfSockets, - SocketStatus - } } } diff --git a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0254.cs b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0254.cs index cfe50e96..ff8db233 100644 --- a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0254.cs +++ b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0254.cs @@ -26,10 +26,10 @@ public class Mid0254 : Mid, IApplicationSelector, IIntegrator, IAcceptableComman public IEnumerable DocumentedPossibleErrors => new Error[] { Error.FaultyIODeviceId }; - [Int32DataFieldDefinition(field: 1, revision: 1, Size = 2)] + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 2)] public int DeviceId { get; set; } - [EnumCollectionDefinition(field: 2, revision: 1, Size = 8)] + [EnumCollectionDefinition(revision: 1, field: 2, Index = 24, Size = 8)] public List GreenLights { get; set; } public Mid0254() : this(new Header() @@ -45,12 +45,5 @@ public Mid0254(Header header) : base(header) { GreenLights ??= []; } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - DeviceId, - GreenLightCommand - } } } diff --git a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0255.cs b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0255.cs index 6b7f28bf..fa0ee764 100644 --- a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0255.cs +++ b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0255.cs @@ -23,9 +23,9 @@ public class Mid0255 : Mid, IApplicationSelector, IIntegrator, IAcceptableComman public IEnumerable DocumentedPossibleErrors => new Error[] { Error.FaultyIODeviceId }; - [Int32DataFieldDefinition(field: 1, revision: 1, Size = 2)] + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 2)] public int DeviceId { get; set; } - [EnumCollectionDefinition(field: 2, revision: 1, Size = 8)] + [EnumCollectionDefinition(revision: 1, field: 2, Index = 24, Size = 8)] public List RedLights { get; set; } public Mid0255() : this(new Header() @@ -41,12 +41,5 @@ public Mid0255(Header header) : base(header) { RedLights ??= []; } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - DeviceId, - RedLightCommand - } } } diff --git a/src/OpenProtocolInterpreter/ApplicationToolLocationSystem/Mid0262.cs b/src/OpenProtocolInterpreter/ApplicationToolLocationSystem/Mid0262.cs index 858bad37..8bf70d06 100644 --- a/src/OpenProtocolInterpreter/ApplicationToolLocationSystem/Mid0262.cs +++ b/src/OpenProtocolInterpreter/ApplicationToolLocationSystem/Mid0262.cs @@ -13,7 +13,7 @@ public class Mid0262 : Mid, IApplicationToolLocationSystem, IController, IAcknow { public const int MID = 262; - [StringDataFieldDefinition(field: 0, revision: 1, Size = 8)] + [StringDataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 8)] public string ToolTagId { get; set; } public Mid0262() : base(MID, DEFAULT_REVISION) @@ -25,11 +25,5 @@ public Mid0262(Header header) : base(header) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - ToolTagId - } } } diff --git a/src/OpenProtocolInterpreter/ApplicationToolLocationSystem/Mid0265.cs b/src/OpenProtocolInterpreter/ApplicationToolLocationSystem/Mid0265.cs index 77f6350d..b4f00da7 100644 --- a/src/OpenProtocolInterpreter/ApplicationToolLocationSystem/Mid0265.cs +++ b/src/OpenProtocolInterpreter/ApplicationToolLocationSystem/Mid0265.cs @@ -15,10 +15,10 @@ public class Mid0265 : Mid, IApplicationToolLocationSystem, IIntegrator, IAccept public IEnumerable DocumentedPossibleErrors => new Error[] { Error.MidRevisionUnsupported }; - [StringDataFieldDefinition(field: 1, revision: 1, Size = 8)] + [StringDataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 8)] public string ToolTagId { get; set; } - [Int32DataFieldDefinition(field: 2, revision: 1, Size = 2)] + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 30, Size = 2)] public ToolStatus ToolStatus { get; set; } public Mid0265() : this(new Header() @@ -34,12 +34,5 @@ public Mid0265(Header header) : base(header) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - ToolTagId, - ToolStatus - } } } diff --git a/src/OpenProtocolInterpreter/AutomaticManualMode/Mid0401.cs b/src/OpenProtocolInterpreter/AutomaticManualMode/Mid0401.cs index a734539a..e0cc888d 100644 --- a/src/OpenProtocolInterpreter/AutomaticManualMode/Mid0401.cs +++ b/src/OpenProtocolInterpreter/AutomaticManualMode/Mid0401.cs @@ -20,7 +20,7 @@ public class Mid0401 : Mid, IAutomaticManualMode, IController, IAcknowledgeable< /// Automatic Mode = false (0) /// Manual Mode = true (1) /// - [BooleanDataFieldDefinition(field: 1, revision: 1, HasPrefix = false)] + [BooleanDataFieldDefinition(revision: 1, field: 1, Index = 20, HasPrefix = false)] public bool ManualAutomaticMode { get; set; } public Mid0401() : this(new Header() @@ -35,11 +35,5 @@ public Mid0401() : this(new Header() public Mid0401(Header header) : base(header) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - ManualAutomaticMode - } } } diff --git a/src/OpenProtocolInterpreter/AutomaticManualMode/Mid0411.cs b/src/OpenProtocolInterpreter/AutomaticManualMode/Mid0411.cs index 9d24af3a..2fb52e25 100644 --- a/src/OpenProtocolInterpreter/AutomaticManualMode/Mid0411.cs +++ b/src/OpenProtocolInterpreter/AutomaticManualMode/Mid0411.cs @@ -33,9 +33,9 @@ public class Mid0411 : Mid, IAutomaticManualMode, IController { public const int MID = 411; - [Int32DataFieldDefinition(field: 1, revision: 1, Size = 2, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 2, HasPrefix = false)] public int AutoDisableSetting { get; set; } - [Int32DataFieldDefinition(field: 2, revision: 1, Size = 2, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 22, Size = 2, HasPrefix = false)] public int CurrentBatch { get; set; } public Mid0411() : this(new Header() @@ -50,12 +50,5 @@ public Mid0411() : this(new Header() public Mid0411(Header header) : base(header) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - AutoDisableSetting, - CurrentBatch - } } } diff --git a/src/OpenProtocolInterpreter/Communication/Mid0001.cs b/src/OpenProtocolInterpreter/Communication/Mid0001.cs index a1bd0179..523e2ea9 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0001.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0001.cs @@ -15,13 +15,13 @@ public class Mid0001 : Mid, ICommunication, IIntegrator, IAnswerableBy, public IEnumerable DocumentedPossibleErrors => new Error[] { Error.ClientAlreadyConnected, Error.MidRevisionUnsupported }; - [BooleanDataFieldDefinition(field: 1, revision: 7)] + [BooleanDataFieldDefinition(revision: 7, field: 1)] public bool OptionalKeepAlive { get; set; } - [BooleanDataFieldDefinition(field: 2, revision: 8)] + [BooleanDataFieldDefinition(revision: 8, field: 2)] public bool OptionalToolLockAtDisconnection { get; set; } - [DecimalDataFieldDefinition(field: 3, revision: 8, Size = 4)] + [DecimalDataFieldDefinition(revision: 8, field: 3, Size = 4)] public decimal OptionalEarlyLock { get; set; } public Mid0001() : this(DEFAULT_REVISION) @@ -40,14 +40,5 @@ public Mid0001(int revision) : this(new Header() }) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - //Rev 7 - UseKeepAlive, - OptionalToolLockAtDisconnection, - OptionalEarlyLock - } } } diff --git a/src/OpenProtocolInterpreter/Communication/Mid0002.cs b/src/OpenProtocolInterpreter/Communication/Mid0002.cs index a4dd79f0..414fb060 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0002.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0002.cs @@ -17,38 +17,38 @@ public class Mid0002 : Mid, ICommunication, IController { public const int MID = 2; - [Int32DataFieldDefinition(field: 1, revision: 1, Size = 4)] + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 4)] public int CellId { get; set; } - [Int32DataFieldDefinition(field: 2, revision: 1, Size = 2)] + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 26, Size = 2)] public int ChannelId { get; set; } - [StringDataFieldDefinition(field: 3, revision: 1, Size = 25)] + [StringDataFieldDefinition(revision: 1, field: 3, Index = 30, Size = 25)] public string ControllerName { get; set; } //Rev 2 - [StringDataFieldDefinition(field: 4, revision: 2, Size = 3)] + [StringDataFieldDefinition(revision: 2, field: 4, Index = 57, Size = 3)] public string SupplierCode { get; set; } //Rev 3 - [StringDataFieldDefinition(field: 5, revision: 3, Size = 19)] + [StringDataFieldDefinition(revision: 3, field: 5, Index = 62, Size = 19)] public string OpenProtocolVersion { get; set; } - [StringDataFieldDefinition(field: 6, revision: 3, Size = 19)] + [StringDataFieldDefinition(revision: 3, field: 6, Index = 83, Size = 19)] public string ControllerSoftwareVersion { get; set; } - [StringDataFieldDefinition(field: 7, revision: 3, Size = 19)] + [StringDataFieldDefinition(revision: 3, field: 7, Index = 104, Size = 19)] public string ToolSoftwareVersion { get; set; } //Rev 4 - [StringDataFieldDefinition(field: 8, revision: 4, Size = 24)] + [StringDataFieldDefinition(revision: 4, field: 8, Index = 125, Size = 24)] public string RBUType { get; set; } - [StringDataFieldDefinition(field: 9, revision: 4, Size = 10)] + [StringDataFieldDefinition(revision: 4, field: 9, Index = 151, Size = 10)] public string ControllerSerialNumber { get; set; } //Rev 5 - [Int32DataFieldDefinition(field: 10, revision: 5, Size = 3)] + [Int32DataFieldDefinition(revision: 5, field: 10, Index = 163, Size = 3)] public SystemType SystemType { get; set; } /// @@ -60,31 +60,31 @@ public class Mid0002 : Mid, ICommunication, IController /// 002 = a system running presses instead of spindles. /// - [Int32DataFieldDefinition(field: 11, revision: 5, Size = 3)] + [Int32DataFieldDefinition(revision: 5, field: 11, Index = 168, Size = 3)] public SystemSubType SystemSubType { get; set; } //Rev 6 - [BooleanDataFieldDefinition(field: 12, revision: 6)] + [BooleanDataFieldDefinition(revision: 6, field: 12, Index = 173)] public bool SequenceNumberSupport { get; set; } - [BooleanDataFieldDefinition(field: 13, revision: 6)] + [BooleanDataFieldDefinition(revision: 6, field: 13, Index = 176)] public bool LinkingHandlingSupport { get; set; } /// /// Station ID for PF6000 /// Cell ID for PF4000 /// - [Int64DataFieldDefinition(field: 14, revision: 6, Size = 10)] + [Int64DataFieldDefinition(revision: 6, field: 14, Index = 179, Size = 10)] public long StationCellId { get; set; } /// /// Station ID for PF6000 /// Cell ID for PF4000 /// - [StringDataFieldDefinition(field: 15, revision: 6, Size = 25)] + [StringDataFieldDefinition(revision: 6, field: 15, Index = 191, Size = 25)] public string StationCellName { get; set; } - [StringDataFieldDefinition(field: 16, revision: 6, Size = 1)] + [StringDataFieldDefinition(revision: 6, field: 16, Index = 218, Size = 1)] public string ClientId { get; set; } //Rev 7 @@ -92,13 +92,13 @@ public class Mid0002 : Mid, ICommunication, IController /// False = Use Keep alive (Keep alive is mandatory) /// True = Ignore Keep alive (Keep alive is optional) /// - [BooleanDataFieldDefinition(field: 17, revision: 7)] + [BooleanDataFieldDefinition(revision: 7, field: 17, Index = 221)] public bool OptionalKeepAlive { get; set; } - [BooleanDataFieldDefinition(field: 18, revision: 8)] + [BooleanDataFieldDefinition(revision: 8, field: 18, Index = 224)] public bool OptionalToolLockAtDisconnection { get; set; } - [DecimalDataFieldDefinition(field: 19, revision: 8, Size = 1)] + [DecimalDataFieldDefinition(revision: 8, field: 19, Index = 227, Size = 1)] public decimal OptionalEarlyLock { get; set; } public Mid0002() : this(DEFAULT_REVISION) @@ -119,37 +119,5 @@ public Mid0002(int revision) : this(new Header() { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - //Rev 1 - CellId, - ChannelId, - ControllerName, - //Rev 2 - SupplierCode, - //Rev 3 - OpenProtocolVersion, - ControllerSoftwareVersion, - ToolSoftwareVersion, - //Rev 4 - RBUType, - ControllerSerialNumber, - //Rev 5 - SystemType, - SystemSubtype, - //Rev 6 - SequenceNumberSupport, - LinkingHandlingSupport, - StationCellId, - StationCellName, - ClientId, - //Rev 7 - OptionalKeepAlive, - //Rev 8 - OptionalToolLockAtDisconnection, - OptionalEarlyLock - } } } diff --git a/src/OpenProtocolInterpreter/Communication/Mid0004.cs b/src/OpenProtocolInterpreter/Communication/Mid0004.cs index 36178714..7ef93027 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0004.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0004.cs @@ -25,12 +25,12 @@ public class Mid0004 : Mid, ICommunication, IController { public const int MID = 4; - [Int32DataFieldDefinition(field: 1, revision: 1, Index = 20, Size = 4, HasPrefix = false)] - [Int32DataFieldDefinition(field: 1, revision: 2, Index = 20, Size = 4, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 4, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 2, field: 1, Index = 20, Size = 4, HasPrefix = false)] public int FailedMid { get; set; } - [Int32DataFieldDefinition(field: 2, revision: 1, Index = 24, Size = 2, HasPrefix = false)] - [Int32DataFieldDefinition(field: 2, revision: 2, Index = 24, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 24, Size = 2, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 2, field: 2, Index = 24, Size = 3, HasPrefix = false)] public Error ErrorCode { get; set; } public Mid0004() : this(DEFAULT_REVISION) @@ -83,12 +83,5 @@ protected override void ProcessDataFields(ReadOnlySpan package) base.ProcessDataFields(dataFields, package); } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - Mid, - ErrorCode - } } } diff --git a/src/OpenProtocolInterpreter/Communication/Mid0005.cs b/src/OpenProtocolInterpreter/Communication/Mid0005.cs index 2620623c..e63f914e 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0005.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0005.cs @@ -27,7 +27,7 @@ public class Mid0005 : Mid, ICommunication, IController { public const int MID = 5; - [Int32DataFieldDefinition(field: 1, revision: 1, Size = 4, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 4, HasPrefix = false)] public int MidAccepted { get; set; } public Mid0005() : this(DEFAULT_REVISION) @@ -47,11 +47,5 @@ public Mid0005(int revision) : this(new Header() { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - MidAccepted - } } } diff --git a/src/OpenProtocolInterpreter/Communication/Mid0006.cs b/src/OpenProtocolInterpreter/Communication/Mid0006.cs index 04b76b09..3275b9af 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0006.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0006.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; namespace OpenProtocolInterpreter.Communication { @@ -20,16 +19,16 @@ public class Mid0006 : Mid, ICommunication, IIntegrator { public const int MID = 6; - [Int32DataFieldDefinition(field: 1, revision: 1, Size = 4, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 4, HasPrefix = false)] public int RequestedMid { get; set; } - [Int32DataFieldDefinition(field: 2, revision: 1, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 24, Size = 3, HasPrefix = false)] public int WantedRevision { get; set; } - [Int32DataFieldDefinition(field: 3, revision: 1, Size = 2, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 3, Index = 27, Size = 2, HasPrefix = false)] public int ExtraDataLength { get; set; } - [StringDataFieldDefinition(field: 4, revision: 1, Size = 0, HasPrefix = false)] + [StringDataFieldDefinition(revision: 1, field: 4, Index = 29, Size = 0, HasPrefix = false)] public string ExtraData { get; set; } public Mid0006() : this(new Header() @@ -65,14 +64,5 @@ private void HandleExtraDataFieldSize() { GetField(nameof(ExtraData)).Size = ExtraDataLength; } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - RequestedMid, - WantedRevision, - ExtraDataLength, - ExtraData - } } } diff --git a/src/OpenProtocolInterpreter/Communication/Mid0008.cs b/src/OpenProtocolInterpreter/Communication/Mid0008.cs index 42e2a1d8..3cfe305a 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0008.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0008.cs @@ -24,13 +24,16 @@ public class Mid0008 : Mid, ICommunication, IIntegrator { public const int MID = 8; - [Int32DataFieldDefinition(field: 1, revision: 1, Size = 4, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 4, HasPrefix = false)] public int SubscriptionMid { get; set; } - [Int32DataFieldDefinition(field: 2, revision: 1, Size = 3, HasPrefix = false)] + + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 24, Size = 3, HasPrefix = false)] public int WantedRevision { get; set; } - [Int32DataFieldDefinition(field: 3, revision: 1, Size = 2, HasPrefix = false)] + + [Int32DataFieldDefinition(revision: 1, field: 3, Index = 27, Size = 2, HasPrefix = false)] public int ExtraDataLength { get; set; } - [StringDataFieldDefinition(field: 4, revision: 1, Size = 0, HasPrefix = false)] + + [StringDataFieldDefinition(revision: 1, field: 4, Index = 29, Size = 0, HasPrefix = false)] public string ExtraData { get; set; } public Mid0008() : this(new Header() @@ -66,14 +69,5 @@ private void HandleExtraDataFieldSize() { GetField(nameof(ExtraData)).Size = ExtraDataLength; } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - SubscriptionMid, - WantedRevision, - ExtraDataLength, - ExtraData - } } } diff --git a/src/OpenProtocolInterpreter/Communication/Mid0009.cs b/src/OpenProtocolInterpreter/Communication/Mid0009.cs index 1e5631e2..2c420cb4 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0009.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0009.cs @@ -23,13 +23,16 @@ public class Mid0009 : Mid, ICommunication, IIntegrator { public const int MID = 9; - [Int32DataFieldDefinition(field: 1, revision: 1, Size = 4, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 4, HasPrefix = false)] public int UnsubscriptionMid { get; set; } - [Int32DataFieldDefinition(field: 2, revision: 1, Size = 3, HasPrefix = false)] + + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 24, Size = 3, HasPrefix = false)] public int ExtraDataRevision { get; set; } - [Int32DataFieldDefinition(field: 3, revision: 1, Size = 2, HasPrefix = false)] + + [Int32DataFieldDefinition(revision: 1, field: 3, Index = 27, Size = 2, HasPrefix = false)] public int ExtraDataLength { get; set; } - [StringDataFieldDefinition(field: 4, revision: 1, Size = 0, HasPrefix = false)] + + [StringDataFieldDefinition(revision: 1, field: 4, Index = 29, Size = 0, HasPrefix = false)] public string ExtraData { get; set; } public Mid0009() : this(new Header() @@ -65,14 +68,5 @@ private void HandleExtraDataFieldSize() { GetField(nameof(ExtraData)).Size = ExtraDataLength; } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - UnsubscriptionMid, - ExtraDataRevision, - ExtraDataLength, - ExtraData - } } } diff --git a/src/OpenProtocolInterpreter/IMarkerInterfaces.cs b/src/OpenProtocolInterpreter/IMarkerInterfaces.cs index 56a3c015..08754fd8 100644 --- a/src/OpenProtocolInterpreter/IMarkerInterfaces.cs +++ b/src/OpenProtocolInterpreter/IMarkerInterfaces.cs @@ -67,4 +67,12 @@ public interface ISubscription public interface IUnsubscription { } + + /// + /// Contract which a mid can be packed as extra data in a . + /// + public interface IExtraData + { + string PackAsExtraData(); + } } diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0200.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0200.cs index 6567fe23..0c554543 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0200.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0200.cs @@ -16,34 +16,34 @@ public class Mid0200 : Mid, IIOInterface, IIntegrator, IAcceptableCommand { public const int MID = 200; - [Int32DataFieldDefinition(field: 1, revision: 1, Size = 1, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 1, HasPrefix = false)] public RelayStatus StatusRelayOne { get; set; } - [Int32DataFieldDefinition(field: 2, revision: 1, Size = 1, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 21, Size = 1, HasPrefix = false)] public RelayStatus StatusRelayTwo { get; set; } - [Int32DataFieldDefinition(field: 3, revision: 1, Size = 1, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 3, Index = 22, Size = 1, HasPrefix = false)] public RelayStatus StatusRelayThree { get; set; } - [Int32DataFieldDefinition(field: 4, revision: 1, Size = 1, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 4, Index = 23, Size = 1, HasPrefix = false)] public RelayStatus StatusRelayFour { get; set; } - [Int32DataFieldDefinition(field: 5, revision: 1, Size = 1, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 5, Index = 24, Size = 1, HasPrefix = false)] public RelayStatus StatusRelayFive { get; set; } - [Int32DataFieldDefinition(field: 6, revision: 1, Size = 1, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 6, Index = 25, Size = 1, HasPrefix = false)] public RelayStatus StatusRelaySix { get; set; } - [Int32DataFieldDefinition(field: 7, revision: 1, Size = 1, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 7, Index = 26, Size = 1, HasPrefix = false)] public RelayStatus StatusRelaySeven { get; set; } - [Int32DataFieldDefinition(field: 8, revision: 1, Size = 1, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 8, Index = 27, Size = 1, HasPrefix = false)] public RelayStatus StatusRelayEight { get; set; } - [Int32DataFieldDefinition(field: 9, revision: 1, Size = 1, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 9, Index = 28, Size = 1, HasPrefix = false)] public RelayStatus StatusRelayNine { get; set; } - [Int32DataFieldDefinition(field: 10, revision: 1, Size = 1, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 10, Index = 29, Size = 1, HasPrefix = false)] public RelayStatus StatusRelayTen { get; set; } public Mid0200() : this(new Header() @@ -58,20 +58,5 @@ public Mid0200() : this(new Header() public Mid0200(Header header) : base(header) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - StatusRelay1, - StatusRelay2, - StatusRelay3, - StatusRelay4, - StatusRelay5, - StatusRelay6, - StatusRelay7, - StatusRelay8, - StatusRelay9, - StatusRelay10 - } } } diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0211.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0211.cs index 3b33ea9a..60f86bf3 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0211.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0211.cs @@ -16,21 +16,21 @@ public class Mid0211 : Mid, IIOInterface, IController, IAcknowledgeable { public const int MID = 211; - [BooleanDataFieldDefinition(field: 1, revision: 1, HasPrefix = false)] + [BooleanDataFieldDefinition(revision: 1, field: 1, Index = 20, HasPrefix = false)] public bool StatusDigInOne { get; set; } - [BooleanDataFieldDefinition(field: 2, revision: 1, HasPrefix = false)] + [BooleanDataFieldDefinition(revision: 1, field: 2, Index = 21, HasPrefix = false)] public bool StatusDigInTwo { get; set; } - [BooleanDataFieldDefinition(field: 3, revision: 1, HasPrefix = false)] + [BooleanDataFieldDefinition(revision: 1, field: 3, Index = 22, HasPrefix = false)] public bool StatusDigInThree { get; set; } - [BooleanDataFieldDefinition(field: 4, revision: 1, HasPrefix = false)] + [BooleanDataFieldDefinition(revision: 1, field: 4, Index = 23, HasPrefix = false)] public bool StatusDigInFour { get; set; } - [BooleanDataFieldDefinition(field: 5, revision: 1, HasPrefix = false)] + [BooleanDataFieldDefinition(revision: 1, field: 5, Index = 24, HasPrefix = false)] public bool StatusDigInFive { get; set; } - [BooleanDataFieldDefinition(field: 6, revision: 1, HasPrefix = false)] + [BooleanDataFieldDefinition(revision: 1, field: 6, Index = 25, HasPrefix = false)] public bool StatusDigInSix { get; set; } - [BooleanDataFieldDefinition(field: 7, revision: 1, HasPrefix = false)] + [BooleanDataFieldDefinition(revision: 1, field: 7, Index = 26, HasPrefix = false)] public bool StatusDigInSeven { get; set; } - [BooleanDataFieldDefinition(field: 8, revision: 1, HasPrefix = false)] + [BooleanDataFieldDefinition(revision: 1, field: 8, Index = 27, HasPrefix = false)] public bool StatusDigInEight { get; set; } public Mid0211() : this(new Header() @@ -45,18 +45,5 @@ public Mid0211() : this(new Header() public Mid0211(Header header) : base(header) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - StatusDigIn1, - StatusDigIn2, - StatusDigIn3, - StatusDigIn4, - StatusDigIn5, - StatusDigIn6, - StatusDigIn7, - StatusDigIn8 - } } } diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0214.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0214.cs index cd13babe..5cb03e42 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0214.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0214.cs @@ -21,7 +21,7 @@ public class Mid0214 : Mid, IIOInterface, IIntegrator, IAnswerableBy, I public IEnumerable DocumentedPossibleErrors => new Error[] { Error.FaultyIODeviceId, Error.IODeviceNotConnected }; - [Int32DataFieldDefinition(field: 1, revision: 1, Size = 2, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 2, HasPrefix = false)] public int DeviceNumber { get; set; } public Mid0214() : this(DEFAULT_REVISION) @@ -39,11 +39,5 @@ public Mid0214(int revision) : this(new Header() }) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - DeviceNumber - } } } diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs index 6555c667..63937f6f 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs @@ -23,23 +23,23 @@ public class Mid0215 : Mid, IIOInterface, IController { public const int MID = 215; - [Int32DataFieldDefinition(field: 1, revision: 1, Index = 20, Size = 2)] - [Int32DataFieldDefinition(field: 1, revision: 2, Index = 20, Size = 2)] + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 2)] + [Int32DataFieldDefinition(revision: 2, field: 1, Index = 20, Size = 2)] public int IODeviceId { get; set; } - [RelayCollectionDefinition(field: 2, revision: 1, Index = 24, Size = 4 * 8)] - [RelayCollectionDefinition(field: 2, revision: 2, Index = 28, Size = 4 * 8)] + [RelayCollectionDefinition(revision: 1, field: 2, Index = 24, Size = 4 * 8)] + [RelayCollectionDefinition(revision: 2, field: 2, Index = 28, Size = 4 * 8)] public List Relays { get; set; } - [DigitalInputCollectionDefinition(field: 3, revision: 1, Index = 58, Size = 4 * 8)] - [DigitalInputCollectionDefinition(field: 3, revision: 2, Index = 0, Size = 4 * 8)] + [DigitalInputCollectionDefinition(revision: 1, field: 3, Index = 58, Size = 4 * 8)] + [DigitalInputCollectionDefinition(revision: 2, field: 3, Index = 0, Size = 4 * 8)] public List DigitalInputs { get; set; } //At revision 2 number of relays/digital inputs comes before their lists - [Int32DataFieldDefinition(field: 4, revision: 2, Index = 24, Size = 2)] + [Int32DataFieldDefinition(revision: 2, field: 4, Index = 24, Size = 2)] public int NumberOfRelays { get; set; } - [Int32DataFieldDefinition(field: 5, revision: 2, Index = 0, Size = 2)] + [Int32DataFieldDefinition(revision: 2, field: 5, Index = 0, Size = 2)] public int NumberOfDigitalInputs { get; set; } public Mid0215() : this(DEFAULT_REVISION) @@ -171,16 +171,5 @@ private void EnsureEightRelaysAndDigitalInputs() for (int i = DigitalInputs.Count; i < 8; i++) DigitalInputs.Add(new DigitalInput(DigitalInputNumber.Off, false)); } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - IODeviceId, - RelayList, - DigitalInputList, - //rev2 - NumberOfRelays, - NumberOfDigitalInputs - } } } diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0216.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0216.cs index 1251a2c5..713e499b 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0216.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0216.cs @@ -22,7 +22,7 @@ public class Mid0216 : Mid, IIOInterface, IIntegrator, ISubscription, IAcceptabl public IEnumerable DocumentedPossibleErrors => new Error[] { Error.RelayFunctionSubscriptionAlreadyExists }; - [Int32DataFieldDefinition(field: 1, revision: 1, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 3, HasPrefix = false)] public RelayNumber RelayNumber { get; set; } public Mid0216() : this(false) @@ -43,11 +43,5 @@ public Mid0216(bool noAckFlag = false) : this(new Header() { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - RelayNumber - } } } diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0217.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0217.cs index a626bc1f..ba79f94c 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0217.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0217.cs @@ -18,9 +18,9 @@ public class Mid0217 : Mid, IIOInterface, IController, IAcknowledgeable { public const int MID = 217; - [Int32DataFieldDefinition(field: 1, revision: 1, Size = 3)] + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 3)] public RelayNumber RelayNumber { get; set; } - [BooleanDataFieldDefinition(field: 2, revision: 1)] + [BooleanDataFieldDefinition(revision: 1, field: 2, Index = 25)] public bool RelayStatus { get; set; } public Mid0217() : this(new Header() @@ -35,13 +35,6 @@ public Mid0217() : this(new Header() public Mid0217(Header header) : base(header) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - RelayNumber, - RelayStatus - } } } diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0219.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0219.cs index c2d9eafd..0526f40e 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0219.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0219.cs @@ -20,7 +20,7 @@ public class Mid0219 : Mid, IIOInterface, IIntegrator, IUnsubscription, IAccepta public IEnumerable DocumentedPossibleErrors => new Error[] { Error.RelayFunctionSubscriptionDoesntExists }; - [Int32DataFieldDefinition(field: 1, revision: 1, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 3, HasPrefix = false)] public RelayNumber RelayNumber { get; set; } public Mid0219() : this(new Header() @@ -35,11 +35,5 @@ public Mid0219() : this(new Header() public Mid0219(Header header) : base(header) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - RelayNumber - } } } diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0220.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0220.cs index a90296b9..7823f55c 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0220.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0220.cs @@ -24,7 +24,7 @@ public class Mid0220 : Mid, IIOInterface, IIntegrator, ISubscription, IAcceptabl public IEnumerable DocumentedPossibleErrors => new Error[] { }; - [Int32DataFieldDefinition(field: 1, revision: 1, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 3, HasPrefix = false)] public DigitalInputNumber DigitalInputNumber { get; set; } public Mid0220() : this(false) @@ -45,11 +45,5 @@ public Mid0220(bool noAckFlag = false) : this(new Header() { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - DigitalInputNumber - } } } diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0221.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0221.cs index cd9df683..31c3f3d5 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0221.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0221.cs @@ -19,9 +19,9 @@ public class Mid0221 : Mid, IIOInterface, IController, IAcknowledgeable { public const int MID = 221; - [Int32DataFieldDefinition(field: 1, revision: 1, Size = 3)] + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 3)] public DigitalInputNumber DigitalInputNumber { get; set; } - [BooleanDataFieldDefinition(field: 2, revision: 1)] + [BooleanDataFieldDefinition(revision: 1, field: 2, Index = 25)] public bool DigitalInputStatus { get; set; } public Mid0221() : this(new Header() @@ -35,12 +35,5 @@ public Mid0221() : this(new Header() public Mid0221(Header header) : base(header) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - DigitalInputNumber, - DigitalInputStatus - } } } diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0223.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0223.cs index 454db7fb..87a1c553 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0223.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0223.cs @@ -19,7 +19,7 @@ public class Mid0223 : Mid, IIOInterface, IIntegrator, IUnsubscription, IAccepta public IEnumerable DocumentedPossibleErrors => new Error[] { Error.RelayFunctionSubscriptionDoesntExists }; - [Int32DataFieldDefinition(field: 1, revision: 1, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 3, HasPrefix = false)] public DigitalInputNumber DigitalInputNumber { get; set; } public Mid0223() : this(new Header() @@ -34,11 +34,5 @@ public Mid0223() : this(new Header() public Mid0223(Header header) : base(header) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - DigitalInputNumber - } } } diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0224.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0224.cs index a332613e..460cf32e 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0224.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0224.cs @@ -18,7 +18,7 @@ public class Mid0224 : Mid, IIOInterface, IIntegrator, IAcceptableCommand, IDecl public IEnumerable DocumentedPossibleErrors => new Error[] { Error.InvalidData }; - [Int32DataFieldDefinition(field: 1, revision: 1, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 3, HasPrefix = false)] public DigitalInputNumber DigitalInputNumber { get; set; } public Mid0224() : this(new Header() @@ -33,11 +33,5 @@ public Mid0224() : this(new Header() public Mid0224(Header header) : base(header) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - DigitalInputNumber - } } } diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0225.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0225.cs index ba0ba82f..1104b228 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0225.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0225.cs @@ -24,7 +24,7 @@ public class Mid0225 : Mid, IIOInterface, IIntegrator, IAcceptableCommand, IDecl public IEnumerable DocumentedPossibleErrors => new Error[] { Error.InvalidData }; - [Int32DataFieldDefinition(field: 1, revision: 1, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 3, HasPrefix = false)] public DigitalInputNumber DigitalInputNumber { get; set; } public Mid0225() : this(new Header() @@ -39,11 +39,5 @@ public Mid0225() : this(new Header() public Mid0225(Header header) : base(header) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - DigitalInputNumber - } } } diff --git a/src/OpenProtocolInterpreter/Job/Advanced/Mid0129.cs b/src/OpenProtocolInterpreter/Job/Advanced/Mid0129.cs index c22a5c0f..eb0973e1 100644 --- a/src/OpenProtocolInterpreter/Job/Advanced/Mid0129.cs +++ b/src/OpenProtocolInterpreter/Job/Advanced/Mid0129.cs @@ -45,12 +45,5 @@ public Mid0129(int revision) : this(new Header() { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - ChannelId, - ParameterSetId - } } } diff --git a/src/OpenProtocolInterpreter/Job/Advanced/Mid0130.cs b/src/OpenProtocolInterpreter/Job/Advanced/Mid0130.cs index 8961cd2f..e4c5223d 100644 --- a/src/OpenProtocolInterpreter/Job/Advanced/Mid0130.cs +++ b/src/OpenProtocolInterpreter/Job/Advanced/Mid0130.cs @@ -32,11 +32,5 @@ public Mid0130() : this(new Header() public Mid0130(Header header) : base(header) { } - - [Obsolete("Use the constructor that takes a Header as parameter instead")] - protected enum DataFields - { - JobOffStatus - } } } diff --git a/src/OpenProtocolInterpreter/Job/Advanced/Mid0140.cs b/src/OpenProtocolInterpreter/Job/Advanced/Mid0140.cs index d2fd6387..2f0b0d79 100644 --- a/src/OpenProtocolInterpreter/Job/Advanced/Mid0140.cs +++ b/src/OpenProtocolInterpreter/Job/Advanced/Mid0140.cs @@ -224,32 +224,5 @@ private IEnumerable DataFieldsByRevision() yield return dataField; } } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - JobId, - JobName, - NumberOfParameterSets, - JobList, - ForcedOrder, - LockAtJobDone, - ToolLoosening, - RepeatJob, - JobBatchDone, - BatchStatusAtIncrement, - DecrementBatchAtOkLoosening, - MaxTimeForFirstTightening, - MaxTimeToCompleteJob, - DisplayResultAtAutoSelect, - UseLineControl, - IdentifierResultPart, - ResultOfNonTightenings, - ResetAllIdentifiersAtJobDone, - Reserved, - - //Rev 2 - JobSequenceNumber - } } } diff --git a/src/OpenProtocolInterpreter/Job/Mid0031.cs b/src/OpenProtocolInterpreter/Job/Mid0031.cs index f8ff5389..7e7a6501 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0031.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0031.cs @@ -18,12 +18,12 @@ public class Mid0031 : Mid, IJob, IController { public const int MID = 31; - [Int32DataFieldDefinition(field: 1, revision: 1, Index = 20, Size = 2, HasPrefix = false)] - [Int32DataFieldDefinition(field: 1, revision: 2, Index = 20, Size = 4, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 2, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 2, field: 1, Index = 20, Size = 4, HasPrefix = false)] public int TotalJobs { get; set; } - [Int32CollectionDefinition(field: 2, revision: 1, Index = 22, Size = 2, EachFieldSize = 2, HasPrefix = false)] - [Int32CollectionDefinition(field: 2, revision: 2, Index = 24, Size = 4, EachFieldSize = 4, HasPrefix = false)] + [Int32CollectionDefinition(revision: 1, field: 2, Index = 22, Size = 2, EachFieldSize = 2, HasPrefix = false)] + [Int32CollectionDefinition(revision: 2, field: 2, Index = 24, Size = 4, EachFieldSize = 4, HasPrefix = false)] public List JobIds { get; set; } public Mid0031() : this(DEFAULT_REVISION) @@ -80,12 +80,5 @@ public override string Pack() builder.Append(Pack(fields)); return builder.ToString(); } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - NumberOfJobs, - EachJobId - } } } diff --git a/src/OpenProtocolInterpreter/Job/Mid0032.cs b/src/OpenProtocolInterpreter/Job/Mid0032.cs index 48d52bce..1ad2c524 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0032.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0032.cs @@ -16,8 +16,8 @@ public class Mid0032 : Mid, IJob, IIntegrator, IAnswerableBy, IDeclinab public IEnumerable DocumentedPossibleErrors => new Error[] { Error.JobIdNotPresent }; - [Int32DataFieldDefinition(field: 1, revision: 1, Index = 20, Size = 2, HasPrefix = false)] - [Int32DataFieldDefinition(field: 1, revision: 2, Index = 20, Size = 4, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 2, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 2, field: 1, Index = 20, Size = 4, HasPrefix = false)] public int JobId { get; set; } public Mid0032() : this(DEFAULT_REVISION) @@ -77,11 +77,5 @@ public override Mid Parse(ReadOnlySpan package) } return this; } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - JobId - } } } diff --git a/src/OpenProtocolInterpreter/Job/Mid0033.cs b/src/OpenProtocolInterpreter/Job/Mid0033.cs index 59b28d18..74a0dd62 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0033.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0033.cs @@ -15,59 +15,59 @@ public class Mid0033 : Mid, IJob, IController { public const int MID = 33; - [Int32DataFieldDefinition(field: 1, revision: 1, Index = 20, Size = 2)] - [Int32DataFieldDefinition(field: 1, revision: 2, Index = 20, Size = 4)] + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 2)] + [Int32DataFieldDefinition(revision: 2, field: 1, Index = 20, Size = 4)] public int JobId { get; set; } - [StringDataFieldDefinition(field: 2, revision: 1, Index = 24, Size = 25)] - [StringDataFieldDefinition(field: 2, revision: 2, Index = 26, Size = 25)] + [StringDataFieldDefinition(revision: 1, field: 2, Index = 24, Size = 25)] + [StringDataFieldDefinition(revision: 2, field: 2, Index = 26, Size = 25)] public string JobName { get; set; } - [Int32DataFieldDefinition(field: 3, revision: 1, Index = 51, Size = 1)] - [Int32DataFieldDefinition(field: 3, revision: 2, Index = 53, Size = 1)] + [Int32DataFieldDefinition(revision: 1, field: 3, Index = 51, Size = 1)] + [Int32DataFieldDefinition(revision: 2, field: 3, Index = 53, Size = 1)] public ForcedOrder ForcedOrder { get; set; } - [Int32DataFieldDefinition(field: 4, revision: 1, Index = 54, Size = 4)] - [Int32DataFieldDefinition(field: 4, revision: 2, Index = 56, Size = 4)] + [Int32DataFieldDefinition(revision: 1, field: 4, Index = 54, Size = 4)] + [Int32DataFieldDefinition(revision: 2, field: 4, Index = 56, Size = 4)] public int MaxTimeForFirstTightening { get; set; } - [Int32DataFieldDefinition(field: 5, revision: 1, Index = 60, Size = 5)] - [Int32DataFieldDefinition(field: 5, revision: 2, Index = 62, Size = 5)] + [Int32DataFieldDefinition(revision: 1, field: 5, Index = 60, Size = 5)] + [Int32DataFieldDefinition(revision: 2, field: 5, Index = 62, Size = 5)] public int MaxTimeToCompleteJob { get; set; } - [Int32DataFieldDefinition(field: 6, revision: 1, Index = 67, Size = 1)] - [Int32DataFieldDefinition(field: 6, revision: 2, Index = 69, Size = 1)] + [Int32DataFieldDefinition(revision: 1, field: 6, Index = 67, Size = 1)] + [Int32DataFieldDefinition(revision: 2, field: 6, Index = 69, Size = 1)] public JobBatchMode JobBatchMode { get; set; } - [BooleanDataFieldDefinition(field: 7, revision: 1, Index = 70, Size = 1)] - [BooleanDataFieldDefinition(field: 7, revision: 2, Index = 72, Size = 1)] + [BooleanDataFieldDefinition(revision: 1, field: 7, Index = 70, Size = 1)] + [BooleanDataFieldDefinition(revision: 2, field: 7, Index = 72, Size = 1)] public bool LockAtJobDone { get; set; } - [BooleanDataFieldDefinition(field: 8, revision: 1, Index = 73)] - [BooleanDataFieldDefinition(field: 8, revision: 2, Index = 75)] + [BooleanDataFieldDefinition(revision: 1, field: 8, Index = 73)] + [BooleanDataFieldDefinition(revision: 2, field: 8, Index = 75)] public bool UseLineControl { get; set; } - [BooleanDataFieldDefinition(field: 9, revision: 1, Index = 76)] - [BooleanDataFieldDefinition(field: 9, revision: 2, Index = 78)] + [BooleanDataFieldDefinition(revision: 1, field: 9, Index = 76)] + [BooleanDataFieldDefinition(revision: 2, field: 9, Index = 78)] public bool RepeatJob { get; set; } - [Int32DataFieldDefinition(field: 10, revision: 1, Index = 79, Size = 1)] - [Int32DataFieldDefinition(field: 10, revision: 2, Index = 81, Size = 1)] + [Int32DataFieldDefinition(revision: 1, field: 10, Index = 79, Size = 1)] + [Int32DataFieldDefinition(revision: 2, field: 10, Index = 81, Size = 1)] public ToolLoosening ToolLoosening { get; set; } - [Int32DataFieldDefinition(field: 11, revision: 1, Index = 82, Size = 1)] - [Int32DataFieldDefinition(field: 11, revision: 2, Index = 84, Size = 1)] + [Int32DataFieldDefinition(revision: 1, field: 11, Index = 82, Size = 1)] + [Int32DataFieldDefinition(revision: 2, field: 11, Index = 84, Size = 1)] public Reserved Reserved { get; set; } - [Int32DataFieldDefinition(field: 12, revision: 1, Index = 85, Size = 2)] - [Int32DataFieldDefinition(field: 12, revision: 2, Index = 87, Size = 2)] + [Int32DataFieldDefinition(revision: 1, field: 12, Index = 85, Size = 2)] + [Int32DataFieldDefinition(revision: 2, field: 12, Index = 87, Size = 2)] public int NumberOfParameterSets { get; set; } - [ParameterSetCollectionDefinition(field: 13, revision: 1, Index = 89, Size = 0)] - [ParameterSetCollectionDefinition(field: 13, revision: 2, Index = 91, Size = 0)] - [ParameterSetCollectionDefinition(field: 13, revision: 3, Index = 91, Size = 0)] - [ParameterSetCollectionDefinition(field: 13, revision: 4, Index = 91, Size = 0)] - [ParameterSetCollectionDefinition(field: 13, revision: 5, Index = 91, Size = 0)] + [ParameterSetCollectionDefinition(revision: 1, field: 13, Index = 89, Size = 0)] + [ParameterSetCollectionDefinition(revision: 2, field: 13, Index = 91, Size = 0)] + [ParameterSetCollectionDefinition(revision: 3, field: 13, Index = 91, Size = 0)] + [ParameterSetCollectionDefinition(revision: 4, field: 13, Index = 91, Size = 0)] + [ParameterSetCollectionDefinition(revision: 5, field: 13, Index = 91, Size = 0)] public List ParameterSetList { get; set; } public Mid0033() : this(DEFAULT_REVISION) @@ -153,23 +153,5 @@ protected override void ProcessDataField(DataField dataField, ReadOnlySpan } base.ProcessDataField(dataField, package); } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - JobId, - JobName, - ForcedOrder, - MaxTimeForFirstTightening, - MaxTimeToCompleteJob, - JobBatchDone, - LockAtJobDone, - UseLineControl, - RepeatJob, - ToolLoosening, - Reserved, - NumberOfParameterSets, - ParameterSetList - } } } diff --git a/src/OpenProtocolInterpreter/Job/Mid0035.cs b/src/OpenProtocolInterpreter/Job/Mid0035.cs index a8776114..a6627696 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0035.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0035.cs @@ -125,32 +125,6 @@ private IEnumerable DataFieldsByRevision() yield return dataField; } } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - //rev 1 and 2 - JobId, - JobStatus, - JobBatchMode, - JobBatchSize, - JobBatchCounter, - Timestamp, - //rev 3 - JobCurrentStep, - JobTotalNumberOfSteps, - JobStepType, - //rev 4 - JobTighteningStatus, - //rev5 - JobSequenceNumber, - VinNumber, - IdentifierResultPart2, - IdentifierResultPart3, - IdentifierResultPart4, - //Rev 6 - JointId - } } } diff --git a/src/OpenProtocolInterpreter/Job/Mid0038.cs b/src/OpenProtocolInterpreter/Job/Mid0038.cs index 8f70a5a4..952c0456 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0038.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0038.cs @@ -64,11 +64,5 @@ public override Mid Parse(ReadOnlySpan package) ProcessDataFields(RevisionsByFields[Header.StandardizedRevision], package); return this; } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - JobId - } } } diff --git a/src/OpenProtocolInterpreter/Job/Mid0039.cs b/src/OpenProtocolInterpreter/Job/Mid0039.cs index 63faef37..9088f55e 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0039.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0039.cs @@ -64,11 +64,5 @@ public override Mid Parse(ReadOnlySpan package) ProcessDataFields(RevisionsByFields[Header.StandardizedRevision], package); return this; } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - JobId - } } } diff --git a/src/OpenProtocolInterpreter/LinkCommunication/Mid9997.cs b/src/OpenProtocolInterpreter/LinkCommunication/Mid9997.cs index 8d776116..dba06de7 100644 --- a/src/OpenProtocolInterpreter/LinkCommunication/Mid9997.cs +++ b/src/OpenProtocolInterpreter/LinkCommunication/Mid9997.cs @@ -33,11 +33,5 @@ public Mid9997() : this(new Header() }) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - MidNumber - } } } diff --git a/src/OpenProtocolInterpreter/LinkCommunication/Mid9998.cs b/src/OpenProtocolInterpreter/LinkCommunication/Mid9998.cs index abda77a7..f322a7be 100644 --- a/src/OpenProtocolInterpreter/LinkCommunication/Mid9998.cs +++ b/src/OpenProtocolInterpreter/LinkCommunication/Mid9998.cs @@ -36,12 +36,5 @@ public Mid9998() : this(new Header() public Mid9998(Header header) : base(header) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - MidNumber, - ErrorCode - } } } diff --git a/src/OpenProtocolInterpreter/MotorTuning/Mid0501.cs b/src/OpenProtocolInterpreter/MotorTuning/Mid0501.cs index 702ddb3d..b4a97bde 100644 --- a/src/OpenProtocolInterpreter/MotorTuning/Mid0501.cs +++ b/src/OpenProtocolInterpreter/MotorTuning/Mid0501.cs @@ -31,11 +31,5 @@ public Mid0501() : this(new Header() public Mid0501(Header header) : base(header) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - MotorTuneResult - } } } diff --git a/src/OpenProtocolInterpreter/MultiSpindle/Mid0091.cs b/src/OpenProtocolInterpreter/MultiSpindle/Mid0091.cs index 1503798f..3c9be1b6 100644 --- a/src/OpenProtocolInterpreter/MultiSpindle/Mid0091.cs +++ b/src/OpenProtocolInterpreter/MultiSpindle/Mid0091.cs @@ -58,15 +58,5 @@ protected override void ProcessDataField(DataField dataField, ReadOnlySpan } base.ProcessDataField(dataField, package); } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - NumberOfSpindles, - SyncTighteningId, - Time, - SyncOverallStatus, - SpindleStatus - } } } diff --git a/src/OpenProtocolInterpreter/MultiSpindle/Mid0100.cs b/src/OpenProtocolInterpreter/MultiSpindle/Mid0100.cs index 687b0ce2..35ec639e 100644 --- a/src/OpenProtocolInterpreter/MultiSpindle/Mid0100.cs +++ b/src/OpenProtocolInterpreter/MultiSpindle/Mid0100.cs @@ -61,12 +61,5 @@ public Mid0100(int revision, bool noAckFlag = false) : this(new Header() }) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - DataNumberSystem, - SendOnlyNewData - } } } diff --git a/src/OpenProtocolInterpreter/MultiSpindle/Mid0101.cs b/src/OpenProtocolInterpreter/MultiSpindle/Mid0101.cs index 0dbac0af..cdd1a077 100644 --- a/src/OpenProtocolInterpreter/MultiSpindle/Mid0101.cs +++ b/src/OpenProtocolInterpreter/MultiSpindle/Mid0101.cs @@ -135,33 +135,5 @@ private IEnumerable DataFieldsByRevision() } } } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - NumberOfSpindlesOrPresses, - VinNumber, - JobId, - ParameterSetId, - BatchSize, - BatchCounter, - BatchStatus, - TorqueOrForceMinLimit, - TorqueOrForceMaxLimit, - TorqueOrForceFinalTarget, - AngleOrStrokeMinLimit, - AngleOrStrokeMaxLimit, - FinalAngleOrStrokeTarget, - Target, - LastChangeInParameterSet, - Timestamp, - SyncTighteningId, - SyncOverallStatus, - SpindesOrPressesStatus, - //Rev 4 - SystemSubType, - //Rev 5 - JobSequenceNumber - } } } diff --git a/src/OpenProtocolInterpreter/MultiSpindle/Mid0104.cs b/src/OpenProtocolInterpreter/MultiSpindle/Mid0104.cs index 5ec9c059..ace2329c 100644 --- a/src/OpenProtocolInterpreter/MultiSpindle/Mid0104.cs +++ b/src/OpenProtocolInterpreter/MultiSpindle/Mid0104.cs @@ -28,11 +28,5 @@ public Mid0104() : base(MID, DEFAULT_REVISION) { } public Mid0104(Header header) : base(header) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - RequestedResultIndex - } } } diff --git a/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0150.cs b/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0150.cs index 60d11c22..3b474c24 100644 --- a/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0150.cs +++ b/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0150.cs @@ -53,11 +53,5 @@ public override Mid Parse(ReadOnlySpan package) ProcessDataFields(package); return this; } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - IdentifierData - } } } diff --git a/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0152.cs b/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0152.cs index 63c07b25..c6605e59 100644 --- a/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0152.cs +++ b/src/OpenProtocolInterpreter/MultipleIdentifiers/Mid0152.cs @@ -41,14 +41,5 @@ public Mid0152(Header header) : base(header) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - FirstIdentifierStatus, - SecondIdentifierStatus, - ThirdIdentifierStatus, - FourthIdentifierStatus - } } } diff --git a/src/OpenProtocolInterpreter/OpenProtocolCommandsDisabled/Mid0421.cs b/src/OpenProtocolInterpreter/OpenProtocolCommandsDisabled/Mid0421.cs index a4b82253..99aefcf3 100644 --- a/src/OpenProtocolInterpreter/OpenProtocolCommandsDisabled/Mid0421.cs +++ b/src/OpenProtocolInterpreter/OpenProtocolCommandsDisabled/Mid0421.cs @@ -31,11 +31,5 @@ public Mid0421() : this(new Header() public Mid0421(Header header) : base(header) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - DigitalInputStatus - } } } diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid0011.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid0011.cs index b3b9d7ad..70312f97 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid0011.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid0011.cs @@ -52,12 +52,5 @@ public override string Pack() GetField(nameof(ParameterSets)).Size = TotalParameterSets * 3; return base.Pack(); } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - TotalParameterSets, - EachParameterSet - } } } diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid0012.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid0012.cs index a3986e52..fa653764 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid0012.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid0012.cs @@ -39,14 +39,5 @@ public Mid0012(int revision) : this(new Header() public Mid0012(Header header) : base(header) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - //Revision 1-2 - ParameterSetId, - //Revision 3-4 - PSetFileVersion - } } } diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid0013.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid0013.cs index 9cdfb588..f2048abe 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid0013.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid0013.cs @@ -34,12 +34,12 @@ public class Mid0013 : Mid, IParameterSet, IController [Int32DataFieldDefinition(revision: 1, field: 10, Index = 97, Size = 5)] public int AngleFinalTarget { get; set; } //Rev 2 - [TruncatedDecimalDataFieldDefinition(revision: 2, field: 1, Index = 104, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 2, field: 11, Index = 104, Size = 6)] public decimal FirstTarget { get; set; } - [TruncatedDecimalDataFieldDefinition(revision: 2, field: 2, Index = 112, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 2, field: 12, Index = 112, Size = 6)] public decimal StartFinalAngle { get; set; } //Rev 5 - [TimestampDataFieldDefinition(revision: 5, field: 1, Index = 120)] + [TimestampDataFieldDefinition(revision: 5, field: 13, Index = 120)] public DateTime LastChangeInParameterSet { get; set; } public Mid0013() : this(DEFAULT_REVISION) @@ -59,25 +59,5 @@ public Mid0013(int revision) : this(new Header() }) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - ParameterSetId, - ParameterSetName, - RotationDirection, - BatchSize, - MinTorque, - MaxTorque, - TorqueFinalTarget, - MinAngle, - MaxAngle, - AngleFinalTarget, - //Rev 2 - FirstTarget, - StartFinalTarget, - //Rev 5 - LastChangeInParameterSet - } } } diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid0015.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid0015.cs index 7337671c..3d435ae5 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid0015.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid0015.cs @@ -138,26 +138,5 @@ private IEnumerable DataFieldsByRevision() foreach (var dataField in RevisionsByFields[Header.StandardizedRevision].OrderBy(x => x.Field)) yield return dataField; } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - ParameterSetId, - LastChangeInParameterSet, - //Rev 2 - ParameterSetName, - RotationDirection, - BatchSize, - TorqueMin, - TorqueMax, - TorqueFinalTarget, - AngleMin, - AngleMax, - FinalAngleTarget, - FirstTarget, - StartFinalAngle, - SelectedIdentifierNumber, - JointId - } } } diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid0018.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid0018.cs index 796126ef..28ca9a83 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid0018.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid0018.cs @@ -31,11 +31,5 @@ public Mid0018() : this(new Header() public Mid0018(Header header) : base(header) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - ParameterSetId - } } } diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid0019.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid0019.cs index 50047a4a..1778ea41 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid0019.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid0019.cs @@ -72,12 +72,5 @@ protected override void ProcessDataFields(ReadOnlySpan package) private IEnumerable DataFieldsByRevision() => RevisionsByFields[Header.StandardizedRevision]; - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - ParameterSetId, - BatchSize - } } } diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid0020.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid0020.cs index 5efb8afb..2b28e54a 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid0020.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid0020.cs @@ -32,11 +32,5 @@ public Mid0020() : this(new Header() public Mid0020(Header header) : base(header) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - ParameterSetId - } } } diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid0022.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid0022.cs index 7ad33ae3..1979fc87 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid0022.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid0022.cs @@ -26,11 +26,5 @@ public Mid0022() : this(new Header() public Mid0022(Header header) : base(header) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - RelayStatus - } } } diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid2504.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid2504.cs index e5621417..7e3eba83 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid2504.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid2504.cs @@ -32,11 +32,5 @@ public Mid2504() : this(new Header() public Mid2504(Header header) : base(header) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - ParameterSetId - } } } diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid2505.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid2505.cs index 52749130..a4af76f3 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid2505.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid2505.cs @@ -65,13 +65,5 @@ protected override void ProcessDataField(DataField dataField, ReadOnlySpan } base.ProcessDataField(dataField, package); } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - ParameterSetId, - NumberOfParameterDataFields, - DataFields - } } } diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid2506.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid2506.cs index a34d743f..64b47277 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid2506.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid2506.cs @@ -39,12 +39,5 @@ public Mid2506(Header header) : base(header) { } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - ProgramId, - NodeType - } } } diff --git a/src/OpenProtocolInterpreter/Result/Mid1201.cs b/src/OpenProtocolInterpreter/Result/Mid1201.cs index 80f40636..1dca07a6 100644 --- a/src/OpenProtocolInterpreter/Result/Mid1201.cs +++ b/src/OpenProtocolInterpreter/Result/Mid1201.cs @@ -23,52 +23,38 @@ public class Mid1201 : Mid, IResult, IController, IAcknowledgeable, IAc { public const int MID = 1201; - public int TotalNumberOfMessages - { - get => GetField(Header.StandardizedRevision, DataFields.TotalMessages).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(Header.StandardizedRevision, DataFields.TotalMessages).SetValue(OpenProtocolConvert.ToString, value); - } - public int MessageNumber - { - get => GetField(Header.StandardizedRevision, DataFields.MessageNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(Header.StandardizedRevision, DataFields.MessageNumber).SetValue(OpenProtocolConvert.ToString, value); - } - public int ResultDataIdentifier - { - get => GetField(Header.StandardizedRevision, DataFields.ResultDataIdentifier).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(Header.StandardizedRevision, DataFields.ResultDataIdentifier).SetValue(OpenProtocolConvert.ToString, value); - } - public DateTime Time - { - get => GetField(Header.StandardizedRevision, DataFields.Time).GetValue(OpenProtocolConvert.ToDateTime); - set => GetField(Header.StandardizedRevision, DataFields.Time).SetValue(OpenProtocolConvert.ToString, value); - } - public bool ResultStatus - { - get => GetField(Header.StandardizedRevision, DataFields.ResultStatus).GetValue(OpenProtocolConvert.ToBoolean); - set => GetField(Header.StandardizedRevision, DataFields.ResultStatus).SetValue(OpenProtocolConvert.ToString, value); - } - public OperationType OperationType - { - get => (OperationType)GetField(Header.StandardizedRevision, DataFields.OperationType).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(Header.StandardizedRevision, DataFields.OperationType).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 3, HasPrefix = false)] + public int TotalNumberOfMessages { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 23, Size = 3, HasPrefix = false)] + public int MessageNumber { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 3, Index = 26, Size = 10, HasPrefix = false)] + public int ResultDataIdentifier { get; set; } + + [TimestampDataFieldDefinition(revision: 1, field: 4, Index = 36, PaddingChar = '0', HasPrefix = false)] + public DateTime Time { get; set; } + + [BooleanDataFieldDefinition(revision: 1, field: 5, Index = 55, HasPrefix = false)] + public bool ResultStatus { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 6, Index = 56, Size = 2, HasPrefix = false)] + public OperationType OperationType { get; set; } + + [Int32DataFieldDefinition(revision: 2, field: 7, Index = 58, Size = 4, HasPrefix = false)] public int RequestMid { get => GetField(Header.StandardizedRevision, DataFields.RequestMid).GetValue(OpenProtocolConvert.ToInt32); set => GetField(Header.StandardizedRevision, DataFields.RequestMid).SetValue(OpenProtocolConvert.ToString, value); } - public int NumberOfObjects - { - get => GetField(Header.StandardizedRevision, DataFields.NumberOfObjects).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(Header.StandardizedRevision, DataFields.NumberOfObjects).SetValue(OpenProtocolConvert.ToString, value); - } - public int NumberOfDataFields - { - get => GetField(Header.StandardizedRevision, DataFields.NumberOfDataFields).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(Header.StandardizedRevision, DataFields.NumberOfDataFields).SetValue(OpenProtocolConvert.ToString, value); - } + + [Int32DataFieldDefinition(revision: 1, field: 8, Index = 58, Size = 3, HasPrefix = false)] + public int NumberOfObjects { get; set; } + public List ObjectDataList { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 10, Index = 61, Size = 3, HasPrefix = false)] + public int NumberOfDataFields { get; set; } public List VariableDataFields { get; set; } public Mid1201() : this(DEFAULT_REVISION) diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0061.cs b/src/OpenProtocolInterpreter/Tightening/Mid0061.cs index 53bd10fc..aaa04941 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0061.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0061.cs @@ -15,297 +15,297 @@ public class Mid0061 : Mid, ITightening, IController, IAcknowledgeable { public const int MID = 61; - [Int32DataFieldDefinition(field: 1, revision: 1, Index = 20, Size = 4)] - [Int32DataFieldDefinition(field: 1, revision: 2, Index = 20, Size = 4)] + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 4)] + [Int32DataFieldDefinition(revision: 2, field: 1, Index = 20, Size = 4)] public int CellId { get; set; } - [Int32DataFieldDefinition(field: 2, revision: 1, Index = 26, Size = 2)] - [Int32DataFieldDefinition(field: 2, revision: 2, Index = 26, Size = 2)] + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 26, Size = 2)] + [Int32DataFieldDefinition(revision: 2, field: 2, Index = 26, Size = 2)] public int ChannelId { get; set; } - [StringDataFieldDefinition(field: 3, revision: 1, Index = 30, Size = 25)] - [StringDataFieldDefinition(field: 3, revision: 2, Index = 30, Size = 25)] + [StringDataFieldDefinition(revision: 1, field: 3, Index = 30, Size = 25)] + [StringDataFieldDefinition(revision: 2, field: 3, Index = 30, Size = 25)] public string TorqueControllerName { get; set; } - [StringDataFieldDefinition(field: 4, revision: 1, Index = 57, Size = 25)] - [StringDataFieldDefinition(field: 4, revision: 2, Index = 57, Size = 25)] - [StringDataFieldDefinition(field: 1, revision: 999, Index = 20, Size = 25, HasPrefix = false)] + [StringDataFieldDefinition(revision: 1, field: 4, Index = 57, Size = 25)] + [StringDataFieldDefinition(revision: 2, field: 4, Index = 57, Size = 25)] + [StringDataFieldDefinition(revision: 999, field: 1, Index = 20, Size = 25, HasPrefix = false)] public string VinNumber { get; set; } - [Int32DataFieldDefinition(field: 5, revision: 1, Index = 84, Size = 2)] - [Int32DataFieldDefinition(field: 5, revision: 2, Index = 84, Size = 4)] - [Int32DataFieldDefinition(field: 2, revision: 999, Index = 45, Size = 2, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 5, Index = 84, Size = 2)] + [Int32DataFieldDefinition(revision: 2, field: 5, Index = 84, Size = 4)] + [Int32DataFieldDefinition(revision: 999, field: 2, Index = 45, Size = 2, HasPrefix = false)] public int JobId { get; set; } - [Int32DataFieldDefinition(field: 6, revision: 1, Index = 88, Size = 3)] - [Int32DataFieldDefinition(field: 6, revision: 2, Index = 90, Size = 3)] - [Int32DataFieldDefinition(field: 3, revision: 999, Index = 47, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 6, Index = 88, Size = 3)] + [Int32DataFieldDefinition(revision: 2, field: 6, Index = 90, Size = 3)] + [Int32DataFieldDefinition(revision: 999, field: 3, Index = 47, Size = 3, HasPrefix = false)] public int ParameterSetId { get; set; } - [Int32DataFieldDefinition(field: 7, revision: 1, Index = 93, Size = 4)] - [Int32DataFieldDefinition(field: 9, revision: 2, Index = 106, Size = 4)] - [Int32DataFieldDefinition(field: 4, revision: 999, Index = 50, Size = 4, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 7, Index = 93, Size = 4)] + [Int32DataFieldDefinition(revision: 2, field: 9, Index = 106, Size = 4)] + [Int32DataFieldDefinition(revision: 999, field: 4, Index = 50, Size = 4, HasPrefix = false)] public int BatchSize { get; set; } - [Int32DataFieldDefinition(field: 8, revision: 1, Index = 99, Size = 4)] - [Int32DataFieldDefinition(field: 10, revision: 2, Index = 112, Size = 4)] - [Int32DataFieldDefinition(field: 5, revision: 999, Index = 54, Size = 4, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 8, Index = 99, Size = 4)] + [Int32DataFieldDefinition(revision: 2, field: 10, Index = 112, Size = 4)] + [Int32DataFieldDefinition(revision: 999, field: 5, Index = 54, Size = 4, HasPrefix = false)] public int BatchCounter { get; set; } - [BooleanDataFieldDefinition(field: 9, revision: 1, Index = 105)] - [BooleanDataFieldDefinition(field: 11, revision: 2, Index = 118)] - [BooleanDataFieldDefinition(field: 7, revision: 999, Index = 59, HasPrefix = false)] + [BooleanDataFieldDefinition(revision: 1, field: 9, Index = 105)] + [BooleanDataFieldDefinition(revision: 2, field: 11, Index = 118)] + [BooleanDataFieldDefinition(revision: 999, field: 7, Index = 59, HasPrefix = false)] public bool TighteningStatus { get; set; } - [Int32DataFieldDefinition(field: 10, revision: 1, Index = 108, Size = 1)] - [Int32DataFieldDefinition(field: 13, revision: 2, Index = 124, Size = 1)] - [Int32DataFieldDefinition(field: 8, revision: 999, Index = 60, Size = 1, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 10, Index = 108, Size = 1)] + [Int32DataFieldDefinition(revision: 2, field: 13, Index = 124, Size = 1)] + [Int32DataFieldDefinition(revision: 999, field: 8, Index = 60, Size = 1, HasPrefix = false)] public TighteningValueStatus TorqueStatus { get; set; } - [Int32DataFieldDefinition(field: 11, revision: 1, Index = 111, Size = 1)] - [Int32DataFieldDefinition(field: 14, revision: 2, Index = 127, Size = 1)] - [Int32DataFieldDefinition(field: 9, revision: 999, Index = 61, Size = 1, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 11, Index = 111, Size = 1)] + [Int32DataFieldDefinition(revision: 2, field: 14, Index = 127, Size = 1)] + [Int32DataFieldDefinition(revision: 999, field: 9, Index = 61, Size = 1, HasPrefix = false)] public TighteningValueStatus AngleStatus { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 12, revision: 1, Index = 114, Size = 6)] - [TruncatedDecimalDataFieldDefinition(field: 21, revision: 2, Index = 157, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 1, field: 12, Index = 114, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 2, field: 21, Index = 157, Size = 6)] public decimal TorqueMinLimit { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 13, revision: 1, Index = 122, Size = 6)] - [TruncatedDecimalDataFieldDefinition(field: 22, revision: 2, Index = 165, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 1, field: 13, Index = 122, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 2, field: 22, Index = 165, Size = 6)] public decimal TorqueMaxLimit { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 14, revision: 1, Index = 130, Size = 6)] - [TruncatedDecimalDataFieldDefinition(field: 23, revision: 2, Index = 173, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 1, field: 14, Index = 130, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 2, field: 23, Index = 173, Size = 6)] public decimal TorqueFinalTarget { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 15, revision: 1, Index = 138, Size = 6)] - [TruncatedDecimalDataFieldDefinition(field: 24, revision: 2, Index = 181, Size = 6)] - [TruncatedDecimalDataFieldDefinition(field: 10, revision: 999, Index = 62, Size = 6, HasPrefix = false)] + [TruncatedDecimalDataFieldDefinition(revision: 1, field: 15, Index = 138, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 2, field: 24, Index = 181, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 999, field: 10, Index = 62, Size = 6, HasPrefix = false)] public decimal Torque { get; set; } - [Int32DataFieldDefinition(field: 16, revision: 1, Index = 146, Size = 5)] - [Int32DataFieldDefinition(field: 25, revision: 2, Index = 189, Size = 5)] + [Int32DataFieldDefinition(revision: 1, field: 16, Index = 146, Size = 5)] + [Int32DataFieldDefinition(revision: 2, field: 25, Index = 189, Size = 5)] public int AngleMinLimit { get; set; } - [Int32DataFieldDefinition(field: 17, revision: 1, Index = 153, Size = 5)] - [Int32DataFieldDefinition(field: 26, revision: 2, Index = 196, Size = 5)] + [Int32DataFieldDefinition(revision: 1, field: 17, Index = 153, Size = 5)] + [Int32DataFieldDefinition(revision: 2, field: 26, Index = 196, Size = 5)] public int AngleMaxLimit { get; set; } - [Int32DataFieldDefinition(field: 18, revision: 1, Index = 160, Size = 5)] - [Int32DataFieldDefinition(field: 27, revision: 2, Index = 203, Size = 5)] + [Int32DataFieldDefinition(revision: 1, field: 18, Index = 160, Size = 5)] + [Int32DataFieldDefinition(revision: 2, field: 27, Index = 203, Size = 5)] public int AngleFinalTarget { get; set; } - [Int32DataFieldDefinition(field: 19, revision: 1, Index = 167, Size = 5)] - [Int32DataFieldDefinition(field: 28, revision: 2, Index = 210, Size = 5)] - [Int32DataFieldDefinition(field: 11, revision: 999, Index = 68, Size = 5, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 19, Index = 167, Size = 5)] + [Int32DataFieldDefinition(revision: 2, field: 28, Index = 210, Size = 5)] + [Int32DataFieldDefinition(revision: 999, field: 11, Index = 68, Size = 5, HasPrefix = false)] public int Angle { get; set; } - [TimestampDataFieldDefinition(field: 20, revision: 1, Index = 174)] - [TimestampDataFieldDefinition(field: 45, revision: 2, Index = 343)] - [TimestampDataFieldDefinition(field: 12, revision: 999, Index = 73, HasPrefix = false)] + [TimestampDataFieldDefinition(revision: 1, field: 20, Index = 174)] + [TimestampDataFieldDefinition(revision: 2, field: 45, Index = 343)] + [TimestampDataFieldDefinition(revision: 999, field: 12, Index = 73, HasPrefix = false)] public DateTime Timestamp { get; set; } - [TimestampDataFieldDefinition(field: 21, revision: 1, Index = 195)] - [TimestampDataFieldDefinition(field: 46, revision: 2, Index = 364)] - [TimestampDataFieldDefinition(field: 13, revision: 999, Index = 92, HasPrefix = false)] + [TimestampDataFieldDefinition(revision: 1, field: 21, Index = 195)] + [TimestampDataFieldDefinition(revision: 2, field: 46, Index = 364)] + [TimestampDataFieldDefinition(revision: 999, field: 13, Index = 92, HasPrefix = false)] public DateTime LastChangeInParameterSet { get; set; } - [Int32DataFieldDefinition(field: 22, revision: 1, Index = 216, Size = 1)] - [Int32DataFieldDefinition(field: 12, revision: 2, Index = 121, Size = 1)] - [Int32DataFieldDefinition(field: 6, revision: 999, Index = 58, Size = 1, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 22, Index = 216, Size = 1)] + [Int32DataFieldDefinition(revision: 2, field: 12, Index = 121, Size = 1)] + [Int32DataFieldDefinition(revision: 999, field: 6, Index = 58, Size = 1, HasPrefix = false)] public BatchStatus BatchStatus { get; set; } - [Int64DataFieldDefinition(field: 23, revision: 1, Index = 219, Size = 10)] - [Int64DataFieldDefinition(field: 41, revision: 2, Index = 301, Size = 10)] - [Int64DataFieldDefinition(field: 14, revision: 999, Index = 111, Size = 10, HasPrefix = false)] + [Int64DataFieldDefinition(revision: 1, field: 23, Index = 219, Size = 10)] + [Int64DataFieldDefinition(revision: 2, field: 41, Index = 301, Size = 10)] + [Int64DataFieldDefinition(revision: 999, field: 14, Index = 111, Size = 10, HasPrefix = false)] public long TighteningId { get; set; } //Rev 2 - [Int32DataFieldDefinition(field: 7, revision: 2, Index = 95, Size = 2)] + [Int32DataFieldDefinition(revision: 2, field: 7, Index = 95, Size = 2)] public Strategy Strategy { get; set; } - [StrategyOptionsDefinition(field: 8, revision: 2, Index = 99, Size = 5, PaddingChar = '0', PaddingOrientation = PaddingOrientation.LeftPadded)] + [StrategyOptionsDefinition(revision: 2, field: 8, Index = 99, Size = 5, PaddingChar = '0', PaddingOrientation = PaddingOrientation.LeftPadded)] public StrategyOptions StrategyOptions { get; set; } - [Int32DataFieldDefinition(field: 15, revision: 2, Index = 130, Size = 1)] + [Int32DataFieldDefinition(revision: 2, field: 15, Index = 130, Size = 1)] public TighteningValueStatus RundownAngleStatus { get; set; } - [Int32DataFieldDefinition(field: 16, revision: 2, Index = 133, Size = 1)] + [Int32DataFieldDefinition(revision: 2, field: 16, Index = 133, Size = 1)] public TighteningValueStatus CurrentMonitoringStatus { get; set; } - [Int32DataFieldDefinition(field: 17, revision: 2, Index = 136, Size = 1)] + [Int32DataFieldDefinition(revision: 2, field: 17, Index = 136, Size = 1)] public TighteningValueStatus SelftapStatus { get; set; } - [Int32DataFieldDefinition(field: 18, revision: 2, Index = 139, Size = 1)] + [Int32DataFieldDefinition(revision: 2, field: 18, Index = 139, Size = 1)] public TighteningValueStatus PrevailTorqueMonitoringStatus { get; set; } - [Int32DataFieldDefinition(field: 19, revision: 2, Index = 142, Size = 1)] + [Int32DataFieldDefinition(revision: 2, field: 19, Index = 142, Size = 1)] public TighteningValueStatus PrevailTorqueCompensateStatus { get; set; } - [TighteningErrorStatusDefinition(field: 20, revision: 2, Index = 145, Size = 10, PaddingChar = '0', PaddingOrientation = PaddingOrientation.LeftPadded)] + [TighteningErrorStatusDefinition(revision: 2, field: 20, Index = 145, Size = 10, PaddingChar = '0', PaddingOrientation = PaddingOrientation.LeftPadded)] public TighteningErrorStatus TighteningErrorStatus { get; set; } - [Int32DataFieldDefinition(field: 29, revision: 2, Index = 217, Size = 5)] + [Int32DataFieldDefinition(revision: 2, field: 29, Index = 217, Size = 5)] public int RundownAngleMin { get; set; } - [Int32DataFieldDefinition(field: 30, revision: 2, Index = 224, Size = 5)] + [Int32DataFieldDefinition(revision: 2, field: 30, Index = 224, Size = 5)] public int RundownAngleMax { get; set; } - [Int32DataFieldDefinition(field: 31, revision: 2, Index = 231, Size = 5)] + [Int32DataFieldDefinition(revision: 2, field: 31, Index = 231, Size = 5)] public int RundownAngle { get; set; } - [Int32DataFieldDefinition(field: 32, revision: 2, Index = 238, Size = 3)] + [Int32DataFieldDefinition(revision: 2, field: 32, Index = 238, Size = 3)] public int CurrentMonitoringMin { get; set; } - [Int32DataFieldDefinition(field: 33, revision: 2, Index = 243, Size = 3)] + [Int32DataFieldDefinition(revision: 2, field: 33, Index = 243, Size = 3)] public int CurrentMonitoringMax { get; set; } - [Int32DataFieldDefinition(field: 34, revision: 2, Index = 248, Size = 3)] + [Int32DataFieldDefinition(revision: 2, field: 34, Index = 248, Size = 3)] public int CurrentMonitoringValue { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 35, revision: 2, Index = 253, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 2, field: 35, Index = 253, Size = 6)] public decimal SelftapMin { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 36, revision: 2, Index = 261, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 2, field: 36, Index = 261, Size = 6)] public decimal SelftapMax { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 37, revision: 2, Index = 269, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 2, field: 37, Index = 269, Size = 6)] public decimal SelftapTorque { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 38, revision: 2, Index = 277, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 2, field: 38, Index = 277, Size = 6)] public decimal PrevailTorqueMonitoringMin { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 39, revision: 2, Index = 285, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 2, field: 39, Index = 285, Size = 6)] public decimal PrevailTorqueMonitoringMax { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 40, revision: 2, Index = 293, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 2, field: 40, Index = 293, Size = 6)] public decimal PrevailTorque { get; set; } - [Int32DataFieldDefinition(field: 42, revision: 2, Index = 313, Size = 5)] + [Int32DataFieldDefinition(revision: 2, field: 42, Index = 313, Size = 5)] public int JobSequenceNumber { get; set; } - [Int32DataFieldDefinition(field: 43, revision: 2, Index = 320, Size = 5)] + [Int32DataFieldDefinition(revision: 2, field: 43, Index = 320, Size = 5)] public int SyncTighteningId { get; set; } - [StringDataFieldDefinition(field: 44, revision: 2, Index = 327, Size = 14)] + [StringDataFieldDefinition(revision: 2, field: 44, Index = 327, Size = 14)] public string ToolSerialNumber { get; set; } //Rev 3 Addition - [StringDataFieldDefinition(field: 47, revision: 3, Index = 385, Size = 25)] + [StringDataFieldDefinition(revision: 3, field: 47, Index = 385, Size = 25)] public string ParameterSetName { get; set; } - [Int32DataFieldDefinition(field: 48, revision: 3, Index = 412, Size = 1)] + [Int32DataFieldDefinition(revision: 3, field: 48, Index = 412, Size = 1)] public TorqueValuesUnit TorqueValuesUnit { get; set; } - [Int32DataFieldDefinition(field: 49, revision: 3, Index = 415, Size = 2)] + [Int32DataFieldDefinition(revision: 3, field: 49, Index = 415, Size = 2)] public ResultType ResultType { get; set; } //Rev 4 addition - [StringDataFieldDefinition(field: 50, revision: 4, Index = 419, Size = 25)] + [StringDataFieldDefinition(revision: 4, field: 50, Index = 419, Size = 25)] public string IdentifierResultPart2 { get; set; } - [StringDataFieldDefinition(field: 51, revision: 4, Index = 446, Size = 25)] + [StringDataFieldDefinition(revision: 4, field: 51, Index = 446, Size = 25)] public string IdentifierResultPart3 { get; set; } - [StringDataFieldDefinition(field: 52, revision: 4, Index = 473, Size = 25)] + [StringDataFieldDefinition(revision: 4, field: 52, Index = 473, Size = 25)] public string IdentifierResultPart4 { get; set; } //Rev 5 addition - [StringDataFieldDefinition(field: 53, revision: 5, Index = 500, Size = 4)] + [StringDataFieldDefinition(revision: 5, field: 53, Index = 500, Size = 4)] public string CustomerTighteningErrorCode { get; set; } //Rev 6 Addition - [TruncatedDecimalDataFieldDefinition(field: 54, revision: 6, Index = 506, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 6, field: 54, Index = 506, Size = 6)] public decimal PrevailTorqueCompensateValue { get; set; } - [TighteningErrorStatus2Definition(field: 55, revision: 6, Index = 514, Size = 10, PaddingChar = '0', PaddingOrientation = PaddingOrientation.LeftPadded)] + [TighteningErrorStatus2Definition(revision: 6, field: 55, Index = 514, Size = 10, PaddingChar = '0', PaddingOrientation = PaddingOrientation.LeftPadded)] public TighteningErrorStatus2 TighteningErrorStatus2 { get; set; } //Rev 7 addition - [TruncatedDecimalDataFieldDefinition(field: 56, revision: 7, Index = 526, Size = 7)] + [TruncatedDecimalDataFieldDefinition(revision: 7, field: 56, Index = 526, Size = 7)] public decimal CompensatedAngle { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 57, revision: 7, Index = 535, Size = 7)] + [TruncatedDecimalDataFieldDefinition(revision: 7, field: 57, Index = 535, Size = 7)] public decimal FinalAngleDecimal { get; set; } //Rev 8 addition - [TruncatedDecimalDataFieldDefinition(field: 58, revision: 8, Index = 544, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 8, field: 58, Index = 544, Size = 6)] public decimal StartFinalAngle { get; set; } - [Int32DataFieldDefinition(field: 59, revision: 8, Index = 552, Size = 1)] + [Int32DataFieldDefinition(revision: 8, field: 59, Index = 552, Size = 1)] public PostViewTorque PostViewTorqueActivated { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 60, revision: 8, Index = 555, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 8, field: 60, Index = 555, Size = 6)] public decimal PostViewTorqueHigh { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 61, revision: 8, Index = 563, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 8, field: 61, Index = 563, Size = 6)] public decimal PostViewTorqueLow { get; set; } //Rev 9 addition - [TruncatedDecimalDataFieldDefinition(field: 62, revision: 9, Index = 571, Size = 5)] + [TruncatedDecimalDataFieldDefinition(revision: 9, field: 62, Index = 571, Size = 5)] public decimal CurrentMonitoringAmpere { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 63, revision: 9, Index = 578, Size = 5)] + [TruncatedDecimalDataFieldDefinition(revision: 9, field: 63, Index = 578, Size = 5)] public decimal CurrentMonitoringAmpereMin { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 64, revision: 9, Index = 585, Size = 5)] + [TruncatedDecimalDataFieldDefinition(revision: 9, field: 64, Index = 585, Size = 5)] public decimal CurrentMonitoringAmpereMax { get; set; } //Rev 10 addition - [Int32DataFieldDefinition(field: 65, revision: 10, Index = 592, Size = 5)] + [Int32DataFieldDefinition(revision: 10, field: 65, Index = 592, Size = 5)] public int AngleNumeratorScaleFactor { get; set; } - [Int32DataFieldDefinition(field: 66, revision: 10, Index = 599, Size = 5)] + [Int32DataFieldDefinition(revision: 10, field: 66, Index = 599, Size = 5)] public int AngleDenominatorScaleFactor { get; set; } - [Int32DataFieldDefinition(field: 67, revision: 10, Index = 606, Size = 1)] + [Int32DataFieldDefinition(revision: 10, field: 67, Index = 606, Size = 1)] public TighteningValueStatus OverallAngleStatus { get; set; } - [Int32DataFieldDefinition(field: 68, revision: 10, Index = 609, Size = 5)] + [Int32DataFieldDefinition(revision: 10, field: 68, Index = 609, Size = 5)] public int OverallAngleMin { get; set; } - [Int32DataFieldDefinition(field: 69, revision: 10, Index = 616, Size = 5)] + [Int32DataFieldDefinition(revision: 10, field: 69, Index = 616, Size = 5)] public int OverallAngleMax { get; set; } - [Int32DataFieldDefinition(field: 70, revision: 10, Index = 623, Size = 5)] + [Int32DataFieldDefinition(revision: 10, field: 70, Index = 623, Size = 5)] public int OverallAngle { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 71, revision: 10, Index = 630, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 10, field: 71, Index = 630, Size = 6)] public decimal PeakTorque { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 72, revision: 10, Index = 638, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 10, field: 72, Index = 638, Size = 6)] public decimal ResidualBreakawayTorque { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 73, revision: 10, Index = 646, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 10, field: 73, Index = 646, Size = 6)] public decimal StartRundownAngle { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 74, revision: 10, Index = 654, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 10, field: 74, Index = 654, Size = 6)] public decimal RundownAngleComplete { get; set; } //Rev 11 - [TruncatedDecimalDataFieldDefinition(field: 75, revision: 11, Index = 662, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 11, field: 75, Index = 662, Size = 6)] public decimal ClickTorque { get; set; } - [Int32DataFieldDefinition(field: 76, revision: 11, Index = 670, Size = 5)] + [Int32DataFieldDefinition(revision: 11, field: 76, Index = 670, Size = 5)] public int ClickAngle { get; set; } //Rev 12 - [Int32DataFieldDefinition(field: 77, revision: 12, Index = 677, Size = 4)] + [Int32DataFieldDefinition(revision: 12, field: 77, Index = 677, Size = 4)] public int SelectedIdentifierNumber { get; set; } - [StringDataFieldDefinition(field: 78, revision: 12, Index = 683, Size = 25)] + [StringDataFieldDefinition(revision: 12, field: 78, Index = 683, Size = 25)] public string JointId { get; set; } //Rev 998 addition - [Int32DataFieldDefinition(field: 56, revision: 998, Index = 526, Size = 2)] + [Int32DataFieldDefinition(revision: 998, field: 56, Index = 526, Size = 2)] public int NumberOfStagesInMultistage { get; set; } - [Int32DataFieldDefinition(field: 57, revision: 998, Index = 530, Size = 2)] + [Int32DataFieldDefinition(revision: 998, field: 57, Index = 530, Size = 2)] public int NumberOfStageResults { get; set; } - [StageResultCollectionDefinition(field: 58, revision: 998, Index = 534, Size = 0)] + [StageResultCollectionDefinition(revision: 998, field: 58, Index = 534, Size = 0)] public List StageResults { get; set; } public Mid0061() : this(DEFAULT_REVISION) @@ -394,104 +394,5 @@ private IEnumerable DataFieldsByRevision() yield return dataField; } } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - CellId, - ChannelId, - TorqueControllerName, - VinNumber, - JobId, - ParameterSetId, - BatchSize, - BatchCounter, - TighteningStatus, - TorqueStatus, - AngleStatus, - TorqueMinLimit, - TorqueMaxLimit, - TorqueFinalTarget, - Torque, - AngleMinLimit, - AngleMaxLimit, - AngleFinalTarget, - Angle, - Timestamp, - LastChangeInParameterSet, - BatchStatus, - TighteningId, - //Rev 2 - Strategy, - StrategyOptions, - RundownAngleStatus, - CurrentMonitoringStatus, - SelftapStatus, - PrevailTorqueMonitoringStatus, - PrevailTorqueCompensateStatus, - TighteningErrorStatus, - RundownAngleMin, - RundownAngleMax, - RundownAngle, - CurrentMonitoringMin, - CurrentMonitoringMax, - CurrentMonitoringValue, - SelftapMin, - SelftapMax, - SelftapTorque, - PrevailTorqueMonitoringMin, - PrevailTorqueMonitoringMax, - PrevailTorque, - JobSequenceNumber, - SyncTighteningId, - ToolSerialNumber, - //Rev 3 - ParameterSetName, - TorqueValuesUnit, - ResultType, - //Rev 4 - IdentifierResultPart2, - IdentifierResultPart3, - IdentifierResultPart4, - //Rev 5 - CustomerTighteningErrorCode, - //Rev 6 - PrevailTorqueCompensateValue, - TighteningErrorStatus2, - //Rev 7 - CompensatedAngle, - FinalAngleDecimal, - //Rev 8 - StartFinalAngle, - PostViewTorqueActivated, - PostViewTorqueHigh, - PostViewTorqueLow, - //Rev 9 - CurrentMonitoringAmp, - CurrentMonitoringAmpMin, - CurrentMonitoringAmpMax, - //Rev 10 - AngleNumeratorScaleFactor, - AngleDenominatorScaleFactor, - OverallAngleStatus, - OverallAngleMin, - OverallAngleMax, - OverallAngle, - PeakTorque, - ResidualBreakawayTorque, - StartRundownAngle, - RundownAngleComplete, - //Rev 11 - ClickTorque, - ClickAngle, - //Rev 12 - SelectedIdentifierNumber, - JointId, - //Rev 998 (Go over rev 7) - NumberOfStagesInMultistage, - NumberOfStageResults, - StageResult - //Rev 999 => all registered - } } } diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0064.cs b/src/OpenProtocolInterpreter/Tightening/Mid0064.cs index f95be229..36eebb8b 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0064.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0064.cs @@ -28,10 +28,10 @@ public class Mid0064 : Mid, ITightening, IIntegrator, IAnswerableBy, ID public IEnumerable DocumentedPossibleErrors => new Error[] { Error.TighteningIdRequestNotFound, Error.MidRevisionUnsupported }; - [Int64DataFieldDefinition(field: 1, revision: 1, Size = 10, HasPrefix = false)] + [Int64DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 10, HasPrefix = false)] public long TighteningId { get; set; } - [BooleanDataFieldDefinition(field: 2, revision: 10, Size = 1, HasPrefix = false)] + [BooleanDataFieldDefinition(revision: 10, field: 2, Index = 30, Size = 1, HasPrefix = false)] public bool OfflineResult { get; set; } public Mid0064() : this(DEFAULT_REVISION) @@ -56,12 +56,5 @@ override public string Pack() { return base.Pack(); } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - TighteningId, - OfflineResult - } } } diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0065.cs b/src/OpenProtocolInterpreter/Tightening/Mid0065.cs index 70cbc001..d21c6f56 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0065.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0065.cs @@ -14,214 +14,214 @@ public class Mid0065 : Mid, ITightening, IController { public const int MID = 65; - [Int64DataFieldDefinition(field: 1, revision: 1, Index = 20, Size = 10)] - [Int64DataFieldDefinition(field: 1, revision: 2, Index = 20, Size = 10)] + [Int64DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 10)] + [Int64DataFieldDefinition(revision: 2, field: 1, Index = 20, Size = 10)] public long TighteningId { get; set; } - [StringDataFieldDefinition(field: 2, revision: 1, Index = 32, Size = 25)] - [StringDataFieldDefinition(field: 2, revision: 2, Index = 32, Size = 25)] + [StringDataFieldDefinition(revision: 1, field: 2, Index = 32, Size = 25)] + [StringDataFieldDefinition(revision: 2, field: 2, Index = 32, Size = 25)] public string VinNumber { get; set; } - [Int32DataFieldDefinition(field: 3, revision: 1, Index = 59, Size = 3)] - [Int32DataFieldDefinition(field: 4, revision: 2, Index = 65, Size = 3)] + [Int32DataFieldDefinition(revision: 1, field: 3, Index = 59, Size = 3)] + [Int32DataFieldDefinition(revision: 2, field: 4, Index = 65, Size = 3)] public int ParameterSetId { get; set; } - [Int32DataFieldDefinition(field: 4, revision: 1, Index = 64, Size = 4)] - [Int32DataFieldDefinition(field: 8, revision: 2, Index = 87, Size = 4)] + [Int32DataFieldDefinition(revision: 1, field: 4, Index = 64, Size = 4)] + [Int32DataFieldDefinition(revision: 2, field: 8, Index = 87, Size = 4)] public int BatchCounter { get; set; } - [BooleanDataFieldDefinition(field: 5, revision: 1, Index = 70, Size = 1)] - [BooleanDataFieldDefinition(field: 9, revision: 2, Index = 93, Size = 1)] + [BooleanDataFieldDefinition(revision: 1, field: 5, Index = 70, Size = 1)] + [BooleanDataFieldDefinition(revision: 2, field: 9, Index = 93, Size = 1)] public bool TighteningStatus { get; set; } - [Int32DataFieldDefinition(field: 6, revision: 1, Index = 73, Size = 1)] - [Int32DataFieldDefinition(field: 11, revision: 2, Index = 99, Size = 1)] + [Int32DataFieldDefinition(revision: 1, field: 6, Index = 73, Size = 1)] + [Int32DataFieldDefinition(revision: 2, field: 11, Index = 99, Size = 1)] public TighteningValueStatus TorqueStatus { get; set; } - [Int32DataFieldDefinition(field: 7, revision: 1, Index = 76, Size = 1)] - [Int32DataFieldDefinition(field: 12, revision: 2, Index = 102, Size = 1)] + [Int32DataFieldDefinition(revision: 1, field: 7, Index = 76, Size = 1)] + [Int32DataFieldDefinition(revision: 2, field: 12, Index = 102, Size = 1)] public TighteningValueStatus AngleStatus { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 8, revision: 1, Index = 79, Size = 6)] - [TruncatedDecimalDataFieldDefinition(field: 19, revision: 2, Index = 132, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 1, field: 8, Index = 79, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 2, field: 19, Index = 132, Size = 6)] public decimal Torque { get; set; } - [Int32DataFieldDefinition(field: 9, revision: 1, Index = 87, Size = 5)] - [Int32DataFieldDefinition(field: 20, revision: 2, Index = 140, Size = 5)] + [Int32DataFieldDefinition(revision: 1, field: 9, Index = 87, Size = 5)] + [Int32DataFieldDefinition(revision: 2, field: 20, Index = 140, Size = 5)] public int Angle { get; set; } - [TimestampDataFieldDefinition(field: 10, revision: 1, Index = 94, Size = 19)] - [TimestampDataFieldDefinition(field: 28, revision: 2, Index = 205, Size = 19)] + [TimestampDataFieldDefinition(revision: 1, field: 10, Index = 94, Size = 19)] + [TimestampDataFieldDefinition(revision: 2, field: 28, Index = 205, Size = 19)] public DateTime Timestamp { get; set; } - [Int32DataFieldDefinition(field: 11, revision: 1, Index = 115, Size = 1)] - [Int32DataFieldDefinition(field: 10, revision: 2, Index = 96, Size = 1)] + [Int32DataFieldDefinition(revision: 1, field: 11, Index = 115, Size = 1)] + [Int32DataFieldDefinition(revision: 2, field: 10, Index = 96, Size = 1)] public BatchStatus BatchStatus { get; set; } //Rev 2 - [Int32DataFieldDefinition(field: 3, revision: 2, Index = 59, Size = 4)] + [Int32DataFieldDefinition(revision: 2, field: 3, Index = 59, Size = 4)] public int JobId { get; set; } - [Int32DataFieldDefinition(field: 5, revision: 2, Index = 70, Size = 2)] + [Int32DataFieldDefinition(revision: 2, field: 5, Index = 70, Size = 2)] public Strategy Strategy { get; set; } - [StrategyOptionsDefinition(field: 6, revision: 2, Index = 74, Size = 5, PaddingChar = '0', PaddingOrientation = PaddingOrientation.LeftPadded)] + [StrategyOptionsDefinition(revision: 2, field: 6, Index = 74, Size = 5, PaddingChar = '0', PaddingOrientation = PaddingOrientation.LeftPadded)] public StrategyOptions StrategyOptions { get; set; } - [Int32DataFieldDefinition(field: 7, revision: 2, Index = 81, Size = 4)] + [Int32DataFieldDefinition(revision: 2, field: 7, Index = 81, Size = 4)] public int BatchSize { get; set; } - [Int32DataFieldDefinition(field: 13, revision: 2, Index = 105, Size = 1)] + [Int32DataFieldDefinition(revision: 2, field: 13, Index = 105, Size = 1)] public TighteningValueStatus RundownAngleStatus { get; set; } - [Int32DataFieldDefinition(field: 14, revision: 2, Index = 108, Size = 1)] + [Int32DataFieldDefinition(revision: 2, field: 14, Index = 108, Size = 1)] public TighteningValueStatus CurrentMonitoringStatus { get; set; } - [Int32DataFieldDefinition(field: 15, revision: 2, Index = 111, Size = 1)] + [Int32DataFieldDefinition(revision: 2, field: 15, Index = 111, Size = 1)] public TighteningValueStatus SelftapStatus { get; set; } - [Int32DataFieldDefinition(field: 16, revision: 2, Index = 114, Size = 1)] + [Int32DataFieldDefinition(revision: 2, field: 16, Index = 114, Size = 1)] public TighteningValueStatus PrevailTorqueMonitoringStatus { get; set; } - [Int32DataFieldDefinition(field: 17, revision: 2, Index = 117, Size = 1)] + [Int32DataFieldDefinition(revision: 2, field: 17, Index = 117, Size = 1)] public TighteningValueStatus PrevailTorqueCompensateStatus { get; set; } - [TighteningErrorStatusDefinition(field: 18, revision: 2, Index = 120, Size = 10, PaddingChar = '0', PaddingOrientation = PaddingOrientation.LeftPadded)] + [TighteningErrorStatusDefinition(revision: 2, field: 18, Index = 120, Size = 10, PaddingChar = '0', PaddingOrientation = PaddingOrientation.LeftPadded)] public TighteningErrorStatus TighteningErrorStatus { get; set; } - [Int32DataFieldDefinition(field: 21, revision: 2, Index = 147, Size = 5)] + [Int32DataFieldDefinition(revision: 2, field: 21, Index = 147, Size = 5)] public int RundownAngle { get; set; } - [Int32DataFieldDefinition(field: 22, revision: 2, Index = 154, Size = 3)] + [Int32DataFieldDefinition(revision: 2, field: 22, Index = 154, Size = 3)] public int CurrentMonitoringValue { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 23, revision: 2, Index = 159, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 2, field: 23, Index = 159, Size = 6)] public decimal SelftapTorque { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 24, revision: 2, Index = 167, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 2, field: 24, Index = 167, Size = 6)] public decimal PrevailTorque { get; set; } - [Int32DataFieldDefinition(field: 25, revision: 2, Index = 175, Size = 5)] + [Int32DataFieldDefinition(revision: 2, field: 25, Index = 175, Size = 5)] public int JobSequenceNumber { get; set; } - [Int32DataFieldDefinition(field: 26, revision: 2, Index = 182, Size = 5)] + [Int32DataFieldDefinition(revision: 2, field: 26, Index = 182, Size = 5)] public int SyncTighteningId { get; set; } - [StringDataFieldDefinition(field: 27, revision: 2, Index = 189, Size = 14)] + [StringDataFieldDefinition(revision: 2, field: 27, Index = 189, Size = 14)] public string ToolSerialNumber { get; set; } //Rev 3 - [Int32DataFieldDefinition(field: 29, revision: 3, Index = 226, Size = 1)] + [Int32DataFieldDefinition(revision: 3, field: 29, Index = 226, Size = 1)] public TorqueValuesUnit TorqueValuesUnit { get; set; } - [Int32DataFieldDefinition(field: 30, revision: 3, Index = 229, Size = 2)] + [Int32DataFieldDefinition(revision: 3, field: 30, Index = 229, Size = 2)] public ResultType ResultType { get; set; } //Rev 4 - [StringDataFieldDefinition(field: 31, revision: 4, Index = 233, Size = 25)] + [StringDataFieldDefinition(revision: 4, field: 31, Index = 233, Size = 25)] public string IdentifierResultPart2 { get; set; } - [StringDataFieldDefinition(field: 32, revision: 4, Index = 260, Size = 25)] + [StringDataFieldDefinition(revision: 4, field: 32, Index = 260, Size = 25)] public string IdentifierResultPart3 { get; set; } - [StringDataFieldDefinition(field: 33, revision: 4, Index = 287, Size = 25)] + [StringDataFieldDefinition(revision: 4, field: 33, Index = 287, Size = 25)] public string IdentifierResultPart4 { get; set; } //Rev 5 - [StringDataFieldDefinition(field: 34, revision: 5, Index = 314, Size = 4)] + [StringDataFieldDefinition(revision: 5, field: 34, Index = 314, Size = 4)] public string CustomerTighteningErrorCode { get; set; } //Rev 6 - [TruncatedDecimalDataFieldDefinition(field: 35, revision: 6, Index = 320, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 6, field: 35, Index = 320, Size = 6)] public decimal PrevailTorqueCompensateValue { get; set; } - [TighteningErrorStatus2Definition(field: 36, revision: 6, Index = 328, Size = 10, PaddingChar = '0', PaddingOrientation = PaddingOrientation.LeftPadded)] + [TighteningErrorStatus2Definition(revision: 6, field: 36, Index = 328, Size = 10, PaddingChar = '0', PaddingOrientation = PaddingOrientation.LeftPadded)] public TighteningErrorStatus2 TighteningErrorStatus2 { get; set; } //Rev 7 - [Int64DataFieldDefinition(field: 37, revision: 7, Index = 340, Size = 10)] + [Int64DataFieldDefinition(revision: 7, field: 37, Index = 340, Size = 10)] public long StationId { get; set; } - [StringDataFieldDefinition(field: 38, revision: 7, Index = 352, Size = 25)] + [StringDataFieldDefinition(revision: 7, field: 38, Index = 352, Size = 25)] public string StationName { get; set; } //Rev 8 - [TruncatedDecimalDataFieldDefinition(field: 39, revision: 8, Index = 379, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 8, field: 39, Index = 379, Size = 6)] public decimal StartFinalAngle { get; set; } - [Int32DataFieldDefinition(field: 40, revision: 8, Index = 387, Size = 1)] + [Int32DataFieldDefinition(revision: 8, field: 40, Index = 387, Size = 1)] public PostViewTorque PostViewTorqueActivated { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 41, revision: 8, Index = 390, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 8, field: 41, Index = 390, Size = 6)] public decimal PostViewTorqueHigh { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 42, revision: 8, Index = 398, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 8, field: 42, Index = 398, Size = 6)] public decimal PostViewTorqueLow { get; set; } //Rev 9 - [TruncatedDecimalDataFieldDefinition(field: 43, revision: 9, Index = 406, Size = 5)] + [TruncatedDecimalDataFieldDefinition(revision: 9, field: 43, Index = 406, Size = 5)] public decimal CurrentMonitoringAmpere { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 44, revision: 9, Index = 413, Size = 5)] + [TruncatedDecimalDataFieldDefinition(revision: 9, field: 44, Index = 413, Size = 5)] public decimal CurrentMonitoringAmpereMin { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 45, revision: 9, Index = 420, Size = 5)] + [TruncatedDecimalDataFieldDefinition(revision: 9, field: 45, Index = 420, Size = 5)] public decimal CurrentMonitoringAmpereMax { get; set; } //Rev 10 addition - [Int32DataFieldDefinition(field: 46, revision: 10, Index = 427, Size = 5)] + [Int32DataFieldDefinition(revision: 10, field: 46, Index = 427, Size = 5)] public int AngleNumeratorScaleFactor { get; set; } - [Int32DataFieldDefinition(field: 47, revision: 10, Index = 434, Size = 5)] + [Int32DataFieldDefinition(revision: 10, field: 47, Index = 434, Size = 5)] public int AngleDenominatorScaleFactor { get; set; } - [Int32DataFieldDefinition(field: 48, revision: 10, Index = 441, Size = 1)] + [Int32DataFieldDefinition(revision: 10, field: 48, Index = 441, Size = 1)] public TighteningValueStatus OverallAngleStatus { get; set; } - [Int32DataFieldDefinition(field: 49, revision: 10, Index = 444, Size = 5)] + [Int32DataFieldDefinition(revision: 10, field: 49, Index = 444, Size = 5)] public int OverallAngleMin { get; set; } - [Int32DataFieldDefinition(field: 50, revision: 10, Index = 451, Size = 5)] + [Int32DataFieldDefinition(revision: 10, field: 50, Index = 451, Size = 5)] public int OverallAngleMax { get; set; } - [Int32DataFieldDefinition(field: 51, revision: 10, Index = 458, Size = 5)] + [Int32DataFieldDefinition(revision: 10, field: 51, Index = 458, Size = 5)] public int OverallAngle { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 52, revision: 10, Index = 465, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 10, field: 52, Index = 465, Size = 6)] public decimal PeakTorque { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 53, revision: 10, Index = 473, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 10, field: 53, Index = 473, Size = 6)] public decimal ResidualBreakawayTorque { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 54, revision: 10, Index = 481, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 10, field: 54, Index = 481, Size = 6)] public decimal StartRundownAngle { get; set; } - [TruncatedDecimalDataFieldDefinition(field: 55, revision: 10, Index = 489, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 10, field: 55, Index = 489, Size = 6)] public decimal RundownAngleComplete { get; set; } //Rev 11 - [TruncatedDecimalDataFieldDefinition(field: 56, revision: 11, Index = 497, Size = 6)] + [TruncatedDecimalDataFieldDefinition(revision: 11, field: 56, Index = 497, Size = 6)] public decimal ClickTorque { get; set; } - [Int32DataFieldDefinition(field: 57, revision: 11, Index = 505, Size = 5)] + [Int32DataFieldDefinition(revision: 11, field: 57, Index = 505, Size = 5)] public int ClickAngle { get; set; } //Rev 12 - [Int32DataFieldDefinition(field: 58, revision: 12, Index = 512, Size = 4)] + [Int32DataFieldDefinition(revision: 12, field: 58, Index = 512, Size = 4)] public int SelectedIdentifierNumber { get; set; } - [StringDataFieldDefinition(field: 59, revision: 12, Index = 518, Size = 25)] + [StringDataFieldDefinition(revision: 12, field: 59, Index = 518, Size = 25)] public string JointId { get; set; } //Rev 998 addition - [Int32DataFieldDefinition(field: 37, revision: 998, Index = 340, Size = 2)] + [Int32DataFieldDefinition(revision: 998, field: 37, Index = 340, Size = 2)] public int NumberOfStagesInMultistage { get; set; } - [Int32DataFieldDefinition(field: 38, revision: 998, Index = 344, Size = 2)] + [Int32DataFieldDefinition(revision: 998, field: 38, Index = 344, Size = 2)] public int NumberOfStageResults { get; set; } - [StageResultCollectionDefinition(field: 39, revision: 998, Index = 348, Size = 0)] + [StageResultCollectionDefinition(revision: 998, field: 39, Index = 348, Size = 0)] public List StageResults { get; set; } public Mid0065() : this(DEFAULT_REVISION) @@ -308,84 +308,5 @@ private IEnumerable DataFieldsByRevision() yield return dataField; } } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - TighteningId, - VinNumber, - ParameterSetId, - BatchCounter, - TighteningStatus, - TorqueStatus, - AngleStatus, - Torque, - Angle, - Timestamp, - BatchStatus, - //Rev 2 Additions - JobId, - Strategy, - StrategyOptions, - BatchSize, - RundownAngleStatus, - CurrentMonitoringStatus, - SelftapStatus, - PrevailTorqueMonitoringStatus, - PrevaiTorqueMonitoringStatus, - TighteningErrorStatus, - RundownAngle, - CurrentMonitoringValue, - SelftapTorque, - PrevailTorque, - JobSequenceNumber, - SyncTighteningId, - ToolSerialNumber, - //Rev 3 Additions - TorqueValuesUnit, - ResultType, - //Rev 4 Additions - IdentifierResulPart2, - IdentifierResulPart3, - IdentifierResulPart4, - //Rev 5 Additions - CustomerTighteningErrorCode, - //Rev 6 Additions - PrevailTorqueCompensateValue, - TighteningErrorStatus2, - //Rev 7 Additions - StationId, - StationName, - //Rev 8 Additions - StartFinalAngle, - PostViewTorqueActivated, - PostViewTorqueHigh, - PostViewTorqueLow, - //Rev 9 - CurrentMonitoringAmp, - CurrentMonitoringAmpMin, - CurrentMonitoringAmpMax, - //Rev 10 - AngleNumeratorScaleFactor, - AngleDenominatorScaleFactor, - OverallAngleStatus, - OverallAngleMin, - OverallAngleMax, - OverallAngle, - PeakTorque, - ResidualBreakawayTorque, - StartRundownAngle, - RundownAngleComplete, - //Rev 11 - ClickTorque, - ClickAngle, - //Rev 12 - SelectedIdentifierNumber, - JointId, - //Rev 998 (Go over rev 7) - NumberOfStagesInMultistage, - NumberOfStageResults, - StageResult - } } } diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0066.cs b/src/OpenProtocolInterpreter/Tightening/Mid0066.cs index 8af94560..71250f57 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0066.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0066.cs @@ -15,11 +15,11 @@ public class Mid0066 : Mid, ITightening, IController { public const int MID = 66; - [Int32DataFieldDefinition(field: 1, revision: 1, Index = 20, Size = 2)] - [Int32DataFieldDefinition(field: 1, revision: 2, Index = 20, Size = 3)] + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 2)] + [Int32DataFieldDefinition(revision: 2, field: 1, Index = 20, Size = 3)] public int NumberOfOfflineResults { get; set; } - [Int32DataFieldDefinition(field: 2, revision: 2, Index = 25, Size = 3)] + [Int32DataFieldDefinition(revision: 2, field: 2, Index = 25, Size = 3)] public int NumberOfOfflineCurves { get; set; } @@ -58,12 +58,5 @@ public override string Pack() builder.Append(Pack(fields)); return builder.ToString(); } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - NumberOfOfflineResults, - NumberOfOfflineCurves - } } } diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0902.cs b/src/OpenProtocolInterpreter/Tightening/Mid0902.cs index 1389eb7a..afbbad4c 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0902.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0902.cs @@ -15,25 +15,25 @@ public class Mid0902 : Mid, ITightening, IController { public const int MID = 902; - [Int64DataFieldDefinition(field: 1, revision: 1, Size = 10, HasPrefix = false)] + [Int64DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 10, HasPrefix = false)] public long Capacity { get; set; } - [Int64DataFieldDefinition(field: 2, revision: 1, Size = 10, HasPrefix = false)] + [Int64DataFieldDefinition(revision: 1, field: 2, Index = 30, Size = 10, HasPrefix = false)] public long OldestSequenceNumber { get; set; } - [TimestampDataFieldDefinition(field: 3, revision: 1, HasPrefix = false)] + [TimestampDataFieldDefinition(revision: 1, field: 3, Index = 40, HasPrefix = false)] public DateTime OldestTime { get; set; } - [Int64DataFieldDefinition(field: 4, revision: 1, Size = 10, HasPrefix = false)] + [Int64DataFieldDefinition(revision: 1, field: 4, Index = 59, Size = 10, HasPrefix = false)] public long NewestSequenceNumber { get; set; } - [TimestampDataFieldDefinition(field: 5, revision: 1, HasPrefix = false)] + [TimestampDataFieldDefinition(revision: 1, field: 5, Index = 69, HasPrefix = false)] public DateTime NewestTime { get; set; } - [Int32DataFieldDefinition(field: 6, revision: 1, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 6, Index = 88, Size = 3, HasPrefix = false)] public int NumberOfPIDs { get; set; } - [VariableDataFieldCollectionDefinition(field: 7, revision: 1, HasPrefix = false)] + [VariableDataFieldCollectionDefinition(revision: 1, field: 7, Index = 91, Size = 0, HasPrefix = false)] public List VariableDataFields { get; set; } public Mid0902() : this(new Header() @@ -65,17 +65,5 @@ protected override void ProcessDataField(DataField dataField, ReadOnlySpan } base.ProcessDataField(dataField, package); } - - [Obsolete("Use DataFieldDefinition attributes instead")] - protected enum DataFields - { - Capacity, - OldestSequenceNumber, - OldestTime, - NewestSequenceNumber, - NewestTime, - NumberOfPIDs, - VariableDataFields - } } } From a8f34991feb362eb2c7803c11edd12648d659e48 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Tue, 28 Jul 2026 19:18:10 -0300 Subject: [PATCH 56/78] revamped logo and icon --- media/icon.png | Bin 15815 -> 16511 bytes media/logo.png | Bin 58052 -> 1077202 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/media/icon.png b/media/icon.png index 508640ee2e477c24e50e9f98b0f173f602e9686b..f2dd5363f99d7fc41dc8e88b680b9950514dc633 100644 GIT binary patch literal 16511 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

a@gE!mxN2wN`0WRf0yIZAE~#+A+EyEUH;M9>x; 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%0D7f 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≀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

JH)360={g%!Y1ZFx5~8yf;C2}cN}y5-Nu+^W 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

N?S=s#@EVakdqS@vu>w>*78UISsGHn7Vvz3 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

B|v&SZq;IfKR;#qOy2}&vdclyGI$#6$M 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

NVp^9 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

(UFmfojn*^=WD4nrn!V5; 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 %wC#23rg3oh1nM$ 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 z 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 D1czuV|T#r63YQN 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�w%@-Rr%lvJ3?GLu{ufv{ottwbaR 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⪙(#Wsm5ZE?BHQ|t0Uq1Fo{M*H|`px=gC=1PEu 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 b@gM&O@$S0O!{^zR00000NkvXXu0mjf#f^>X literal 58052 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 Date: Tue, 28 Jul 2026 19:18:49 -0300 Subject: [PATCH 57/78] updated badges and linking --- README.md | 59 +++++++----------- ...0.1.pdf => OpenProtocol_Specification.pdf} | Bin 2 files changed, 21 insertions(+), 38 deletions(-) rename docs/{OpenProtocolSpecification_Release_2.20.1.pdf => OpenProtocol_Specification.pdf} (100%) diff --git a/README.md b/README.md index 49023b88..b881d059 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,14 @@ -Open Protocol Interpreter - -

- - +Open Protocol Interpreter + +[![Version](https://img.shields.io/nuget/vpre/OpenProtocolInterpreter.svg)](https://www.nuget.org/packages/OpenProtocolInterpreter) +[![Downloads](https://img.shields.io/nuget/dt/OpenProtocolInterpreter.svg)](https://www.nuget.org/packages/OpenProtocolInterpreter) +[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/rickedb/OpenProtocolInterpreter/release.yml?branch=master)](https://github.com/Rickedb/OpenProtocolInterpreter/actions/workflows/release.yml) +[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/Rickedb/OpenProtocolIntepreter/master/LICENSE) +[![Sponsors](https://img.shields.io/github/sponsors/rickedb?style=social)](https://github.com/sponsors/rickedb) + > OpenProtocol communication utility - 1. [What is Open Protocol at all?](#what-is-open-protocol-at-all) + 1. [What is Open Protocol at all?](#what-is-open-protocol-at-all) 2. [What is OpenProtocolInterpreter?](#what-is-openprotocolinterpreter) 3. [How does it work?](#how-does-it-work) 4. [Usage examples](#lets-see-some-examples-of-usage) @@ -44,13 +34,13 @@ Most common Tightening Controllers from Atlas Copco company are **PowerFocus4000 OpenProtocolInterpreter is a **library that converts the ugly string** that came from Open Protocol packages, which is commonly called **MID**, to an **object**. *"Substringing"* packages is such a boring thing to do, so let OpenProtocolIntepreter do it for you! -**[If you're curious, just take a look at their documentation.](https://github.com/Rickedb/OpenProtocolIntepreter/blob/master/docs/OpenProtocol_Specification.pdf)** +**[If you're curious, just take a look at their documentation.](docs/OpenProtocol_Specification.pdf)** ## How does it work? It's simple, you give us your byte[] or string package and we deliver you an object, simple as that! -For example, let's imagine you received the following string package: +For example, let's imagine you received the following string package: ``` csharp string package = "00240005001 0018"; ``` @@ -69,14 +59,14 @@ var myMid04 = interpreter.Parse(midPackage); int myFailedMid = myMid04.FailedMid; //An enum with Error Code Error errorCode = myMid04.ErrorCode; -``` +``` It can generate an object from a string, but can it make it to the other way?? FOR SURE! ``` csharp var jobUploadRequest = new Mid0032(1, 2); //Job id 1, revision 2 var package = jobUploadRequest.Pack(); //Generated package => 00240032002 0001 -``` +``` ## Get it on [NuGet](https://www.nuget.org/packages/OpenProtocolInterpreter)! ``` @@ -96,12 +86,12 @@ For now, instead of iterating through all Mids of the same category, it relies o #### MIDs Identifying Customization -We have several MIDs inside Open Protocol documentation, but do you really need all of them? +We have several MIDs inside Open Protocol documentation, but do you really need all of them? The answer is... **NO!** You will probably need only to use a range of MIDs, with this in mind, we did something to make things faster. You can tell us which MIDs we should considerate! -> *NOTE: You can register only mids you need to call "Parse" method +> *NOTE: You can register only mids you need to call "Parse" method Here is an example: ``` csharp @@ -118,7 +108,7 @@ var myCustomInterpreter = new MidInterpreter() //Will work: var myMid04 = myCustomInterpreter.Parse(package); //Won't work, will throw NotImplementedException: -var myMid30 = myCustomInterpreter.Parse(package); +var myMid30 = myCustomInterpreter.Parse(package); //Won't work, will throw InvalidCastException: var myMid01 = myCustomInterpreter.Parse(package); ``` @@ -127,7 +117,7 @@ When you don't know which package will come, use ``` Parse ``` overload, not ``` #### MIDs Overriding -Maybe you have a totally crazy controller that does not implement the Mid as the documentation says or you might want to inject your own Mid inheriting another Mid, +Maybe you have a totally crazy controller that does not implement the Mid as the documentation says or you might want to inject your own Mid inheriting another Mid, so you can customize it and add more properties to handle some conversions. Anyway, if you need that, it's possible to override! Here is an example: @@ -147,7 +137,7 @@ public class OverridedMid0081 : Mid0081 public OverridedMid0081() { - + } public override string Pack() @@ -254,7 +244,7 @@ protected void OnPackageReceived(string message) //Get Registered delegate for the MID that was identified var action = OnReceivedMid.FirstOrDefault(x => x.Key == mid.GetType()); - + if (action.Equals(default(KeyValuePair))) return; //Stop if there is no delegate registered for the message that arrived @@ -266,7 +256,7 @@ protected void OnPackageReceived(string message) } } ``` -This would call the registered delegate which you're sure what mid it is. +This would call the registered delegate which you're sure what mid it is. For example when a **MID_0061** (last tightening) pop up, the **onTighteningReceived** delegate will be called: ``` csharp @@ -277,7 +267,7 @@ protected void OnTighteningReceived(ReceivedMidEventArgs e) Mid0061 tighteningMid = e.ReceivedMID as Mid0061; //Casting to the right mid //This method just send the ack from tightening mid - BuildAndSendAcknowledge(tighteningMid); + BuildAndSendAcknowledge(tighteningMid); Console.log("TIGHTENING ARRIVED") } catch (Exception ex) @@ -300,13 +290,6 @@ protected void BuildAndSendAcknowledge(Mid mid) > **Integrator Implementation Tip:** Always **DO** register used MIDs, I'm pretty sure you won't need all of them to your application. -### Contribute to the project - -Lot's of effort were given to this project and by seen people using it motivated me a lot to improve it more and more. - -Does it help you a lot? That's awesome and very rewarding! -But if you wish, you can support and help to motivate the constant improving of this library by contributing in [OpenCollective](https://opencollective.com/open-protocol-interpreter). - ### List of still unavailable Mids | MID | Description | Notes | diff --git a/docs/OpenProtocolSpecification_Release_2.20.1.pdf b/docs/OpenProtocol_Specification.pdf similarity index 100% rename from docs/OpenProtocolSpecification_Release_2.20.1.pdf rename to docs/OpenProtocol_Specification.pdf From e30cb66e204d713dcbd1f48f5db73b086b35f2ec Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Tue, 28 Jul 2026 19:18:55 -0300 Subject: [PATCH 58/78] added funding.yml --- .github/FUNDING.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..5df9cb86 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: rickedb +buy_me_a_coffee: rickedb From 5f3e0277781c5728ed78814d1a2380143160db33 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 2 Aug 2026 19:56:30 -0300 Subject: [PATCH 59/78] Using span widely for better memory performance --- .../ApplicationSelector/Mid0251.cs | 7 +- .../IOInterface/DigitalInput.cs | 22 +----- .../IOInterface/Relay.cs | 22 +----- .../Job/Advanced/AdvancedJob.cs | 67 +++++++++++------ .../Job/ParameterSet.cs | 55 +++++++++----- src/OpenProtocolInterpreter/MidInterpreter.cs | 10 +++ .../MultipleIdentifiers/IdentifierStatus.cs | 15 +--- .../OpenProtocolConvert.cs | 71 ++++++++++++------- .../PowerMACS/SpecialValue.cs | 52 ++++++++------ .../Result/ObjectData.cs | 24 ++++--- .../Tightening/StageResult.cs | 17 +---- .../Tightening/StrategyOptions.cs | 6 +- .../Tightening/TighteningErrorStatus.cs | 14 +--- src/OpenProtocolInterpreter/Tool/ToolData.cs | 24 +------ .../VariableDataField.cs | 34 +-------- .../_internals/StringExtensions.cs | 29 +------- 16 files changed, 206 insertions(+), 263 deletions(-) diff --git a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0251.cs b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0251.cs index c0635c83..9f4f73fb 100644 --- a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0251.cs +++ b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0251.cs @@ -90,9 +90,10 @@ protected static string PackSocketStatus(char paddingChar, int size, PaddingOrie protected static List ParseSocketStatus(string section) { - var list = new List(); - foreach (var c in section) - list.Add(OpenProtocolConvert.ToBoolean(c.ToString())); + var span = section.AsSpan(); + var list = new List(span.Length); + for (int i = 0; i < span.Length; i++) + list.Add(OpenProtocolConvert.ToBoolean(span.Slice(i, 1))); return list; } diff --git a/src/OpenProtocolInterpreter/IOInterface/DigitalInput.cs b/src/OpenProtocolInterpreter/IOInterface/DigitalInput.cs index 63344836..5aaf97b6 100644 --- a/src/OpenProtocolInterpreter/IOInterface/DigitalInput.cs +++ b/src/OpenProtocolInterpreter/IOInterface/DigitalInput.cs @@ -32,13 +32,7 @@ public string Pack() } public static DigitalInput Parse(string section) - { - return new DigitalInput() - { - Number = (DigitalInputNumber)OpenProtocolConvert.ToInt32(section.Substring(0, 3)), - Status = OpenProtocolConvert.ToBoolean(section.Substring(3, 1)) - }; - } + => Parse(section.AsSpan()); public static DigitalInput Parse(ReadOnlySpan section) { @@ -50,19 +44,7 @@ public static DigitalInput Parse(ReadOnlySpan section) } public static IEnumerable ParseAll(string value) - { - if (string.IsNullOrEmpty(value)) - { - yield break; - } - - const int sectionSize = 4; - for (int i = 0; i < value.Length; i += sectionSize) - { - var section = value.Substring(i, sectionSize); - yield return Parse(section); - } - } + => ParseAll(value.AsSpan()); public static IEnumerable ParseAll(ReadOnlySpan value) { diff --git a/src/OpenProtocolInterpreter/IOInterface/Relay.cs b/src/OpenProtocolInterpreter/IOInterface/Relay.cs index 7626eab8..e48b220d 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Relay.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Relay.cs @@ -32,13 +32,7 @@ public string Pack() } public static Relay Parse(string section) - { - return new Relay() - { - Number = (RelayNumber)OpenProtocolConvert.ToInt32(section.Substring(0, 3)), - Status = OpenProtocolConvert.ToBoolean(section.Substring(3, 1)) - }; - } + => Parse(section.AsSpan()); public static Relay Parse(ReadOnlySpan section) { @@ -50,19 +44,7 @@ public static Relay Parse(ReadOnlySpan section) } public static IEnumerable ParseAll(string value) - { - if (string.IsNullOrEmpty(value)) - { - yield break; - } - - const int sectionSize = 4; - for (int i = 0; i < value.Length; i += sectionSize) - { - var section = value.Substring(i, sectionSize); - yield return Parse(section); - } - } + => ParseAll(value.AsSpan()); public static IEnumerable ParseAll(ReadOnlySpan value) { diff --git a/src/OpenProtocolInterpreter/Job/Advanced/AdvancedJob.cs b/src/OpenProtocolInterpreter/Job/Advanced/AdvancedJob.cs index 66e7e8f2..8c50fcc9 100644 --- a/src/OpenProtocolInterpreter/Job/Advanced/AdvancedJob.cs +++ b/src/OpenProtocolInterpreter/Job/Advanced/AdvancedJob.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -61,32 +62,35 @@ public string Pack(int revision) } public static AdvancedJob Parse(string section, int revision) + => Parse(section.AsSpan(), revision); + + public static AdvancedJob Parse(ReadOnlySpan section, int revision) { - var fields = section.Split(':'); + var remaining = section; var obj = new AdvancedJob() { - ChannelId = OpenProtocolConvert.ToInt32(fields[0]), - ProgramId = OpenProtocolConvert.ToInt32(fields[1]), - AutoSelect = (AutoSelect)OpenProtocolConvert.ToInt32(fields[2]), - BatchSize = OpenProtocolConvert.ToInt32(fields[3]), - MaxCoherentNok = OpenProtocolConvert.ToInt32(fields[4]) + ChannelId = OpenProtocolConvert.ToInt32(NextField(ref remaining, ':')), + ProgramId = OpenProtocolConvert.ToInt32(NextField(ref remaining, ':')), + AutoSelect = (AutoSelect)OpenProtocolConvert.ToInt32(NextField(ref remaining, ':')), + BatchSize = OpenProtocolConvert.ToInt32(NextField(ref remaining, ':')), + MaxCoherentNok = OpenProtocolConvert.ToInt32(NextField(ref remaining, ':')) }; if (revision > 1) { - obj.BatchCounter = OpenProtocolConvert.ToInt32(fields[5]); + obj.BatchCounter = OpenProtocolConvert.ToInt32(NextField(ref remaining, ':')); if (revision != 999) { - obj.IdentifierNumber = OpenProtocolConvert.ToInt32(fields[6]); - obj.JobStepName = fields[7]; - obj.JobStepType = OpenProtocolConvert.ToInt32(fields[8]); + obj.IdentifierNumber = OpenProtocolConvert.ToInt32(NextField(ref remaining, ':')); + obj.JobStepName = NextField(ref remaining, ':').ToString(); + obj.JobStepType = OpenProtocolConvert.ToInt32(NextField(ref remaining, ':')); if (revision > 2) { - obj.ToolLoosening = (ToolLoosening)OpenProtocolConvert.ToInt32(fields[9]); - obj.JobBatchMode = (BatchMode)OpenProtocolConvert.ToInt32(fields[10]); - obj.BatchStatusAtIncrement = (BatchStatusAtIncrement)OpenProtocolConvert.ToInt32(fields[11]); - obj.DecrementBatchAfterLoosening = (DecrementBatchAfterLoosening)OpenProtocolConvert.ToInt32(fields[12]); - obj.CurrentBatchStatus = (CurrentBatchStatus)OpenProtocolConvert.ToInt32(fields[13]); + obj.ToolLoosening = (ToolLoosening)OpenProtocolConvert.ToInt32(NextField(ref remaining, ':')); + obj.JobBatchMode = (BatchMode)OpenProtocolConvert.ToInt32(NextField(ref remaining, ':')); + obj.BatchStatusAtIncrement = (BatchStatusAtIncrement)OpenProtocolConvert.ToInt32(NextField(ref remaining, ':')); + obj.DecrementBatchAfterLoosening = (DecrementBatchAfterLoosening)OpenProtocolConvert.ToInt32(NextField(ref remaining, ':')); + obj.CurrentBatchStatus = (CurrentBatchStatus)OpenProtocolConvert.ToInt32(NextField(ref remaining, ':')); } } } @@ -95,18 +99,37 @@ public static AdvancedJob Parse(string section, int revision) } public static IEnumerable ParseAll(string section, int revision) + => ParseAll(section.AsSpan(), revision); + + public static IEnumerable ParseAll(ReadOnlySpan section, int revision) { - if (string.IsNullOrEmpty(section)) + if (section.IsEmpty) + return Array.Empty(); + + var result = new List(); + var remaining = section; + while (!remaining.IsEmpty) { - yield break; + var job = NextField(ref remaining, ';'); + if (!job.IsWhiteSpace()) + result.Add(Parse(job, revision)); } + return result; + } - var jobs = section.Split(';').ToList(); - jobs.RemoveAll(string.IsNullOrWhiteSpace); //remove last one which will probably be empty - foreach (var advancedJob in jobs) + private static ReadOnlySpan NextField(ref ReadOnlySpan remaining, char separator) + { + int idx = remaining.IndexOf(separator); + if (idx < 0) { - yield return Parse(advancedJob, revision); + var last = remaining; + remaining = ReadOnlySpan.Empty; + return last; } + + var field = remaining.Slice(0, idx); + remaining = remaining.Slice(idx + 1); + return field; } internal static int GetDefaultSize(int revision) diff --git a/src/OpenProtocolInterpreter/Job/ParameterSet.cs b/src/OpenProtocolInterpreter/Job/ParameterSet.cs index 7735445c..2f543880 100644 --- a/src/OpenProtocolInterpreter/Job/ParameterSet.cs +++ b/src/OpenProtocolInterpreter/Job/ParameterSet.cs @@ -55,29 +55,33 @@ public string Pack(int revision) } public static ParameterSet Parse(string section, int revision) + => Parse(section.AsSpan(), revision); + + public static ParameterSet Parse(ReadOnlySpan section, int revision) { - string[] fields = section.Split(':'); + var remaining = section; var pset = new ParameterSet() { - ChannelId = OpenProtocolConvert.ToInt32(fields[0]), - TypeId = OpenProtocolConvert.ToInt32(fields[1]), - AutoValue = OpenProtocolConvert.ToBoolean(fields[2]), - BatchSize = OpenProtocolConvert.ToInt32(fields[3]) + ChannelId = OpenProtocolConvert.ToInt32(NextField(ref remaining, ':')), + TypeId = OpenProtocolConvert.ToInt32(NextField(ref remaining, ':')), + AutoValue = OpenProtocolConvert.ToBoolean(NextField(ref remaining, ':')), + BatchSize = OpenProtocolConvert.ToInt32(NextField(ref remaining, ':')) }; if (revision > 2) { - pset.JobStepName = fields[5]; - pset.JobStepType = OpenProtocolConvert.ToInt32(fields[6]); + var socketOrIdentifierNumber = NextField(ref remaining, ':'); + pset.JobStepName = NextField(ref remaining, ':').ToString(); + pset.JobStepType = OpenProtocolConvert.ToInt32(NextField(ref remaining, ':')); if (revision > 3) { - pset.IdentifierNumber = OpenProtocolConvert.ToInt32(fields[4]); - pset.MaxCoherentNok = OpenProtocolConvert.ToInt32(fields[7]); + pset.IdentifierNumber = OpenProtocolConvert.ToInt32(socketOrIdentifierNumber); + pset.MaxCoherentNok = OpenProtocolConvert.ToInt32(NextField(ref remaining, ':')); } else { - pset.Socket = OpenProtocolConvert.ToInt32(fields[4]); + pset.Socket = OpenProtocolConvert.ToInt32(socketOrIdentifierNumber); } } @@ -85,18 +89,37 @@ public static ParameterSet Parse(string section, int revision) } public static IEnumerable ParseAll(string section, int revision) + => ParseAll(section.AsSpan(), revision); + + public static IEnumerable ParseAll(ReadOnlySpan section, int revision) { - if (string.IsNullOrWhiteSpace(section)) + if (section.IsWhiteSpace() || section.IsEmpty) + return Array.Empty(); + + var result = new List(); + var remaining = section; + while (!remaining.IsEmpty) { - yield break; + var psetData = NextField(ref remaining, ';'); + if (!psetData.IsWhiteSpace()) + result.Add(Parse(psetData, revision)); } + return result; + } - var parameterSets = section.Split(';').ToList(); - parameterSets.RemoveAll(string.IsNullOrWhiteSpace); //remove last one which will probably be empty - foreach (string psetData in parameterSets) + private static ReadOnlySpan NextField(ref ReadOnlySpan remaining, char separator) + { + int idx = remaining.IndexOf(separator); + if (idx < 0) { - yield return Parse(psetData, revision); + var last = remaining; + remaining = ReadOnlySpan.Empty; + return last; } + + var field = remaining.Slice(0, idx); + remaining = remaining.Slice(idx + 1); + return field; } public static int Size(int revision) diff --git a/src/OpenProtocolInterpreter/MidInterpreter.cs b/src/OpenProtocolInterpreter/MidInterpreter.cs index 704497b7..341546fb 100644 --- a/src/OpenProtocolInterpreter/MidInterpreter.cs +++ b/src/OpenProtocolInterpreter/MidInterpreter.cs @@ -27,7 +27,11 @@ public MidInterpreter() public Mid Parse(string package) { +#if NETSTANDARD2_0 int mid = int.Parse(package.Substring(4, 4)); +#else + int mid = int.Parse(package.AsSpan(4, 4)); +#endif var instance = TryParseStandaloneMid(mid); if (instance != default) return instance; @@ -38,7 +42,13 @@ public Mid Parse(string package) public Mid Parse(byte[] package) { +#if NETSTANDARD2_0 int mid = int.Parse(Encoding.ASCII.GetString(package, 4, 4)); +#else + Span buffer = stackalloc char[4]; + Encoding.ASCII.GetChars(package.AsSpan(4, 4), buffer); + int mid = int.Parse(buffer); +#endif var instance = TryParseStandaloneMid(mid); if (instance != default) return instance; diff --git a/src/OpenProtocolInterpreter/MultipleIdentifiers/IdentifierStatus.cs b/src/OpenProtocolInterpreter/MultipleIdentifiers/IdentifierStatus.cs index 25bfd8c6..38e06ef9 100644 --- a/src/OpenProtocolInterpreter/MultipleIdentifiers/IdentifierStatus.cs +++ b/src/OpenProtocolInterpreter/MultipleIdentifiers/IdentifierStatus.cs @@ -22,20 +22,7 @@ public string Pack() } public static IdentifierStatus Parse(string section) - { - if (string.IsNullOrEmpty(section)) - { - return default; - } - - return new IdentifierStatus() - { - IdentifierTypeNumber = OpenProtocolConvert.ToInt32(section.Substring(0, 1)), - IncludedInWorkOrder = OpenProtocolConvert.ToBoolean(section.Substring(1, 2)), - StatusInWorkOrder = (StatusInWorkOrder)OpenProtocolConvert.ToInt32(section.Substring(3, 2)), - ResultPart = section.SafeSubstring(5, 25) - }; - } + => Parse(section.AsSpan()); public static IdentifierStatus Parse(ReadOnlySpan section) { diff --git a/src/OpenProtocolInterpreter/OpenProtocolConvert.cs b/src/OpenProtocolInterpreter/OpenProtocolConvert.cs index 8d2c4c51..bfb76a2b 100644 --- a/src/OpenProtocolInterpreter/OpenProtocolConvert.cs +++ b/src/OpenProtocolInterpreter/OpenProtocolConvert.cs @@ -18,13 +18,7 @@ public static string ToString(char paddingChar, int size, PaddingOrientation ori => ToString(value); public static bool ToBoolean(string value) - { - int intValue = 0; - if (value != null) - int.TryParse(value.ToString(), out intValue); - - return Convert.ToBoolean(intValue); - } + => ToBoolean(value.AsSpan()); public static bool ToBoolean(ReadOnlySpan value) { @@ -44,14 +38,24 @@ public static string ToString(char paddingChar, int size, PaddingOrientation ori => ToString(value); public static DateTime ToDateTime(string value) + => ToDateTime(value.AsSpan()); + + public static DateTime ToDateTime(ReadOnlySpan value) { var convertedValue = DateTime.Now; - if (!string.IsNullOrWhiteSpace(value.ToString())) - { - var date = value.ToString(); - DateTime.TryParse($"{date.Substring(0, 10)} {date.Substring(11, 8)}", out convertedValue); - } + if (value.IsWhiteSpace()) + return convertedValue; + + Span buffer = stackalloc char[19]; + value.Slice(0, 10).CopyTo(buffer); + buffer[10] = ' '; + value.Slice(11, 8).CopyTo(buffer.Slice(11)); +#if NETSTANDARD2_0 + DateTime.TryParse(buffer.ToString(), out convertedValue); +#else + DateTime.TryParse(buffer, out convertedValue); +#endif return convertedValue; } @@ -66,15 +70,27 @@ public static string ToString(char paddingChar, int size, PaddingOrientation ori return TruncatePadded(paddingChar, size, orientation, str); } - public static decimal ToDecimal(ReadOnlySpan value) - => ToDecimal(value.ToString()); - public static decimal ToDecimal(string value) + => ToDecimal(value.AsSpan()); + + public static decimal ToDecimal(ReadOnlySpan value) { decimal decimalValue = 0; - if (!string.IsNullOrWhiteSpace(value)) - decimal.TryParse(value.Replace(',', '.'), NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign | NumberStyles.AllowExponent, _formatProvider, out decimalValue); + if (value.IsWhiteSpace()) + return decimalValue; +#if NETSTANDARD2_0 + decimal.TryParse(value.ToString().Replace(',', '.'), NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign | NumberStyles.AllowExponent, _formatProvider, out decimalValue); +#else + Span buffer = value.Length <= 64 ? stackalloc char[value.Length] : new char[value.Length]; + value.CopyTo(buffer); + for (int i = 0; i < buffer.Length; i++) + { + if (buffer[i] == ',') + buffer[i] = '.'; + } + decimal.TryParse(buffer, NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign | NumberStyles.AllowExponent, _formatProvider, out decimalValue); +#endif return decimalValue; } @@ -105,7 +121,7 @@ public static decimal ToTruncatedDecimal(string value) => ToTruncatedDecimal(value, 2); public static decimal ToTruncatedDecimal(string value, int decimalDigits) - => ToTruncatedDecimal(value.AsSpan(), 2); + => ToTruncatedDecimal(value.AsSpan(), decimalDigits); public static decimal ToTruncatedDecimal(ReadOnlySpan value) => ToTruncatedDecimal(value, 2); @@ -211,19 +227,22 @@ public static byte ToByte(bool[] values) } public static string TruncatePadded(char paddingChar, int size, PaddingOrientation orientation, string value) + => TruncatePadded(paddingChar, size, orientation, value.AsSpan()); + + public static string TruncatePadded(char paddingChar, int size, PaddingOrientation orientation, ReadOnlySpan value) { - if (value == null) - return string.Empty.PadLeft(size, paddingChar); + var truncated = size > 0 && value.Length > size ? value.Slice(0, size) : value; + int resultLength = Math.Max(size, truncated.Length); - if (size > 0 && value.Length > size) - { - value = value.Substring(0, size); - } + Span buffer = resultLength <= 128 ? stackalloc char[resultLength] : new char[resultLength]; + buffer.Fill(paddingChar); if (orientation == PaddingOrientation.RightPadded) - return value.PadRight(size, paddingChar); + truncated.CopyTo(buffer); + else + truncated.CopyTo(buffer.Slice(resultLength - truncated.Length)); - return value.PadLeft(size, paddingChar); + return buffer.ToString(); } } } diff --git a/src/OpenProtocolInterpreter/PowerMACS/SpecialValue.cs b/src/OpenProtocolInterpreter/PowerMACS/SpecialValue.cs index cf2c8924..13a8ee58 100644 --- a/src/OpenProtocolInterpreter/PowerMACS/SpecialValue.cs +++ b/src/OpenProtocolInterpreter/PowerMACS/SpecialValue.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; @@ -33,59 +34,68 @@ public string Pack(bool useStepNumber) } public static SpecialValue Parse(string value, bool useStepNumber) + => Parse(value.AsSpan(), useStepNumber); + + public static SpecialValue Parse(ReadOnlySpan value, bool useStepNumber) { var obj = new SpecialValue { - VariableName = value.Substring(0, 20), - Type = (DataType)value.Substring(20, 2), - Length = OpenProtocolConvert.ToInt32(value.Substring(22, 2)) + VariableName = value.Slice(0, 20).ToString(), + Type = (DataType)value.Slice(20, 2).ToString(), + Length = OpenProtocolConvert.ToInt32(value.Slice(22, 2)) }; - obj.Value = value.Substring(24, obj.Length); + obj.Value = value.Slice(24, obj.Length).ToString(); if (useStepNumber) { - obj.StepNumber = OpenProtocolConvert.ToInt32(value.Substring(24 + obj.Length, 2)); + obj.StepNumber = OpenProtocolConvert.ToInt32(value.Slice(24 + obj.Length, 2)); } return obj; } public static IEnumerable ParseAll(string value, bool useStepNumber) + => ParseAll(value.AsSpan(), useStepNumber); + + public static IEnumerable ParseAll(ReadOnlySpan value, bool useStepNumber) { - if (string.IsNullOrWhiteSpace(value)) - { - yield break; - } + if (value.IsWhiteSpace() || value.IsEmpty) + return Array.Empty(); - var totalSpecialValues = OpenProtocolConvert.ToInt32(value.Substring(0, 2)); + var result = new List(); + var totalSpecialValues = OpenProtocolConvert.ToInt32(value.Slice(0, 2)); int index = 2; const int sectionSize = 24; for (int i = 0; i < totalSpecialValues; i++) { - var length = OpenProtocolConvert.ToInt32(value.Substring(22 + index, 2)); + var length = OpenProtocolConvert.ToInt32(value.Slice(22 + index, 2)); var totalSize = length + (useStepNumber ? sectionSize + 2 : sectionSize); - var section = value.Substring(index, totalSize); + var section = value.Slice(index, totalSize); index += totalSize; - yield return Parse(section, useStepNumber); + result.Add(Parse(section, useStepNumber)); } + return result; } public static IEnumerable ParseAll(string value, int totalSpecialValues, bool useStepNumber) + => ParseAll(value.AsSpan(), totalSpecialValues, useStepNumber); + + public static IEnumerable ParseAll(ReadOnlySpan value, int totalSpecialValues, bool useStepNumber) { - if (string.IsNullOrWhiteSpace(value)) - { - yield break; - } + if (value.IsWhiteSpace() || value.IsEmpty) + return Array.Empty(); + var result = new List(); int index = 0; const int sectionSize = 24; for (int i = 0; i < totalSpecialValues; i++) { - var length = OpenProtocolConvert.ToInt32(value.Substring(22 + index, 2)); + var length = OpenProtocolConvert.ToInt32(value.Slice(22 + index, 2)); var totalSize = length + (useStepNumber ? sectionSize + 2 : sectionSize); - var section = value.Substring(index, totalSize); + var section = value.Slice(index, totalSize); index += totalSize; - yield return Parse(section, useStepNumber); + result.Add(Parse(section, useStepNumber)); } + return result; } } diff --git a/src/OpenProtocolInterpreter/Result/ObjectData.cs b/src/OpenProtocolInterpreter/Result/ObjectData.cs index b56b75f7..2c4a0dc4 100644 --- a/src/OpenProtocolInterpreter/Result/ObjectData.cs +++ b/src/OpenProtocolInterpreter/Result/ObjectData.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Text; namespace OpenProtocolInterpreter.Result @@ -33,30 +34,35 @@ public static ObjectData Parse(string value) => Parse(1, value); public static ObjectData Parse(int revision, string value) + => Parse(revision, value.AsSpan()); + + public static ObjectData Parse(int revision, ReadOnlySpan value) { var obj = new ObjectData() { - Id = OpenProtocolConvert.ToInt32(value.Substring(0, 4)), - Status = OpenProtocolConvert.ToBoolean(value.Substring(4, 1)) + Id = OpenProtocolConvert.ToInt32(value.Slice(0, 4)), + Status = OpenProtocolConvert.ToBoolean(value.Slice(4, 1)) }; if (revision > 2) { - obj.ObjectType = (ObjectType)OpenProtocolConvert.ToInt32(value.Substring(5, 1)); - obj.ReferenceObjectId = OpenProtocolConvert.ToInt32(value.Substring(6, 4)); + obj.ObjectType = (ObjectType)OpenProtocolConvert.ToInt32(value.Slice(5, 1)); + obj.ReferenceObjectId = OpenProtocolConvert.ToInt32(value.Slice(6, 4)); } return obj; } public static IEnumerable ParseAll(int revision, string value) + => ParseAll(revision, value.AsSpan()); + + public static IEnumerable ParseAll(int revision, ReadOnlySpan value) { int sectionSize = Size(revision); + var result = new List(); for (int i = 0; i < value.Length; i += sectionSize) - { - var section = value.Substring(i, sectionSize); - yield return Parse(revision, section); - } + result.Add(Parse(revision, value.Slice(i, sectionSize))); + return result; } public static IEnumerable ParseAll(string value) diff --git a/src/OpenProtocolInterpreter/Tightening/StageResult.cs b/src/OpenProtocolInterpreter/Tightening/StageResult.cs index 41007822..6aaef21a 100644 --- a/src/OpenProtocolInterpreter/Tightening/StageResult.cs +++ b/src/OpenProtocolInterpreter/Tightening/StageResult.cs @@ -21,13 +21,7 @@ public string Pack() } public static StageResult Parse(string value) - { - return new StageResult() - { - Torque = OpenProtocolConvert.ToTruncatedDecimal(value.Substring(0, 6)), - Angle = OpenProtocolConvert.ToInt32(value.Substring(6, 5)) - }; - } + => Parse(value.AsSpan()); public static StageResult Parse(ReadOnlySpan value) { @@ -39,14 +33,7 @@ public static StageResult Parse(ReadOnlySpan value) } public static IEnumerable ParseAll(string value) - { - const int sectionSize = 11; - for (int i = 0; i < value.Length; i += sectionSize) - { - var section = value.Substring(i, sectionSize); - yield return Parse(section); - } - } + => ParseAll(value.AsSpan()); public static IEnumerable ParseAll(ReadOnlySpan value) { diff --git a/src/OpenProtocolInterpreter/Tightening/StrategyOptions.cs b/src/OpenProtocolInterpreter/Tightening/StrategyOptions.cs index db2e5a94..1fc59b54 100644 --- a/src/OpenProtocolInterpreter/Tightening/StrategyOptions.cs +++ b/src/OpenProtocolInterpreter/Tightening/StrategyOptions.cs @@ -60,11 +60,7 @@ public byte[] PackBytes() } public static StrategyOptions Parse(string value) - { - var intValue = OpenProtocolConvert.ToInt32(value); - var bytes = BitConverter.GetBytes(intValue); - return Parse(bytes); - } + => Parse(value.AsSpan()); public static StrategyOptions Parse(ReadOnlySpan value) { diff --git a/src/OpenProtocolInterpreter/Tightening/TighteningErrorStatus.cs b/src/OpenProtocolInterpreter/Tightening/TighteningErrorStatus.cs index 4f101854..67200696 100644 --- a/src/OpenProtocolInterpreter/Tightening/TighteningErrorStatus.cs +++ b/src/OpenProtocolInterpreter/Tightening/TighteningErrorStatus.cs @@ -105,16 +105,12 @@ public byte[] PackBytes() } public static TighteningErrorStatus Parse(string value) - { - var longValue = OpenProtocolConvert.ToInt64(value); - var bytes = System.BitConverter.GetBytes(longValue); - return Parse(bytes); - } + => Parse(value.AsSpan()); public static TighteningErrorStatus Parse(ReadOnlySpan value) { var longValue = OpenProtocolConvert.ToInt64(value); - var bytes = System.BitConverter.GetBytes(longValue); + var bytes = BitConverter.GetBytes(longValue); return Parse(bytes); } @@ -274,11 +270,7 @@ public byte[] PackBytes() } public static TighteningErrorStatus2 Parse(string value) - { - var longValue = OpenProtocolConvert.ToInt64(value); - var bytes = System.BitConverter.GetBytes(longValue); - return Parse(bytes); - } + => Parse(value.AsSpan()); public static TighteningErrorStatus2 Parse(ReadOnlySpan value) { diff --git a/src/OpenProtocolInterpreter/Tool/ToolData.cs b/src/OpenProtocolInterpreter/Tool/ToolData.cs index 1246e7d0..67074464 100644 --- a/src/OpenProtocolInterpreter/Tool/ToolData.cs +++ b/src/OpenProtocolInterpreter/Tool/ToolData.cs @@ -22,15 +22,7 @@ public string Pack() } public static ToolData Parse(string value) - { - return new ToolData() - { - Number = OpenProtocolConvert.ToInt32(value.Substring(0, 4)), - SerialNumber = value.Substring(4, 30), - ModelName = value.Substring(34, 30), - ModelArticleNumber = value.Substring(64, 30) - }; - } + => Parse(value.AsSpan()); public static ToolData Parse(ReadOnlySpan value) { @@ -44,19 +36,7 @@ public static ToolData Parse(ReadOnlySpan value) } public static IEnumerable ParseAll(string value) - { - if(string.IsNullOrEmpty(value)) - { - yield break; - } - - const int sectionSize = 94; - for (int i = 0; i < value.Length; i += sectionSize) - { - var section = value.Substring(i, sectionSize); - yield return Parse(section); - } - } + => ParseAll(value.AsSpan()); public static IEnumerable ParseAll(ReadOnlySpan value) { diff --git a/src/OpenProtocolInterpreter/VariableDataField.cs b/src/OpenProtocolInterpreter/VariableDataField.cs index eddaf5b0..51c24c28 100644 --- a/src/OpenProtocolInterpreter/VariableDataField.cs +++ b/src/OpenProtocolInterpreter/VariableDataField.cs @@ -64,10 +64,7 @@ public string Pack() } public static VariableDataField Parse(string value) - { - var length = OpenProtocolConvert.ToInt32(value.Substring(5, 3)); - return Parse(value, length); - } + => Parse(value.AsSpan()); public static VariableDataField Parse(ReadOnlySpan value) { @@ -76,21 +73,7 @@ public static VariableDataField Parse(ReadOnlySpan value) } public static List ParseAll(string value) - { - var list = new List(); - if (!string.IsNullOrWhiteSpace(value)) - { - int valueLength; - const int fixedLength = 17; - for (int i = 0; i < value.Length; i += fixedLength + valueLength) - { - valueLength = OpenProtocolConvert.ToInt32(value.Substring(i + 5, 3)); - var section = value.Substring(i, fixedLength + valueLength); - list.Add(Parse(section, valueLength)); - } - } - return list; - } + => ParseAll(value.AsSpan()); public static List ParseAll(ReadOnlySpan value) { @@ -108,19 +91,6 @@ public static List ParseAll(ReadOnlySpan value) return result; } - private static VariableDataField Parse(string value, int length) - { - return new VariableDataField() - { - ParameterId = OpenProtocolConvert.ToInt32(value.Substring(0, 5)), - Length = length, - DataType = (DataTypeDefinition)OpenProtocolConvert.ToInt32(value.Substring(8, 2)), - Unit = (DataUnitType)OpenProtocolConvert.ToInt32(value.Substring(10, 3)), - StepNumber = OpenProtocolConvert.ToInt32(value.Substring(13, 4)), - DataValue = value.Substring(17, length) - }; - } - private static VariableDataField Parse(ReadOnlySpan value, int length) { return new VariableDataField() diff --git a/src/OpenProtocolInterpreter/_internals/StringExtensions.cs b/src/OpenProtocolInterpreter/_internals/StringExtensions.cs index 05b85c11..3a2ee189 100644 --- a/src/OpenProtocolInterpreter/_internals/StringExtensions.cs +++ b/src/OpenProtocolInterpreter/_internals/StringExtensions.cs @@ -5,20 +5,7 @@ namespace OpenProtocolInterpreter public static class StringExtensions { public static string TruncatePadded(this string value, char paddingChar, int size, PaddingOrientation orientation) - { - if (value == null) - return string.Empty.PadLeft(size, paddingChar); - - if (size > 0 && value.Length > size) - { - value = value.Substring(0, size); - } - - if (orientation == PaddingOrientation.RightPadded) - return value.PadRight(size, paddingChar); - - return value.PadLeft(size, paddingChar); - } + => OpenProtocolConvert.TruncatePadded(paddingChar, size, orientation, value.AsSpan()); internal static string SafePadRight(this string value, int length) => SafePadRight(value, length, ' '); @@ -34,19 +21,7 @@ internal static string SafePadRight(this string value, int length, char characte } internal static string SafeSubstring(this string value, int startIndex, int length) - { - if(string.IsNullOrEmpty(value)) - { - return string.Empty; - } - - if (value.Length < startIndex + length) - { - return value.Substring(startIndex, value.Length - startIndex); - } - - return value.Substring(startIndex, length); - } + => SafeSubstring(value.AsSpan(), startIndex, length); internal static string SafeSubstring(this ReadOnlySpan value, int startIndex, int length) { From 955ca3ad1ec9fd955679a1948bc3dc269159eb75 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Thu, 6 Aug 2026 20:20:54 -0300 Subject: [PATCH 60/78] Updated with extra data handling --- .../Communication/TestMid0006.cs | 1 + .../Converters/BooleanConverterTest.cs | 2 +- .../Converters/ByteConverterTest.cs | 2 +- .../Converters/DateTimeConverterTest.cs | 2 +- .../Converters/DecimalConverterTest.cs | 2 +- .../Converters/Int32ConverterTest.cs | 2 +- .../Converters/Int64ConverterTest.cs | 2 +- .../Converters/PaddingConverterTest.cs | 2 +- .../TruncatedDecimalConverterTest.cs | 2 +- .../ParameterSet/TestMid2501.cs | 63 ++++++ .../ParameterSet/TestMid2504.cs | 1 + .../ParameterSet/TestMid2505.cs | 28 +++ .../ParameterSet/TestMid2506.cs | 28 +++ src/MIDTesters.Core/Result/TestMid1201.cs | 2 +- src/MIDTesters.Core/TestCustomMid.cs | 35 +--- src/MIDTesters.Core/Tool/TestMid0702.cs | 51 ++++- src/MIDTesters.Core/Tool/TestMid0703.cs | 6 +- src/MIDTesters.Core/Tool/TestMid0704.cs | 183 +++++++++++++++++- .../Alarm/AlarmMessages.cs | 24 +-- src/OpenProtocolInterpreter/Alarm/Mid1000.cs | 4 +- src/OpenProtocolInterpreter/Alarm/Mid1001.cs | 2 +- .../ApplicationControllerMessages.cs | 4 +- .../ApplicationSelectorMessages.cs | 14 +- .../ApplicationSelector/Mid0251.cs | 4 +- .../ApplicationToolLocationSystemMessages.cs | 14 +- .../AutomaticManualModeMessages.cs | 14 +- .../Communication/CommunicationMessages.cs | 18 +- .../Communication/Mid0006.cs | 10 +- .../Communication/Mid0008.cs | 12 +- .../Communication/Mid0009.cs | 15 +- src/OpenProtocolInterpreter/CustomMessages.cs | 14 +- src/OpenProtocolInterpreter/DataField.cs | 6 +- .../DataFieldDefinitionAttribute.cs | 56 +++--- src/OpenProtocolInterpreter/ExtraData.cs | 130 +++++++++++++ .../IMarkerInterfaces.cs | 31 ++- .../IOInterface/DigitalInput.cs | 4 +- .../IOInterface/IOInterfaceMessages.cs | 36 ++-- .../IOInterface/Mid0215.cs | 50 ++--- .../IOInterface/Relay.cs | 4 +- .../Job/Advanced/AdvancedJob.cs | 4 +- .../Job/Advanced/AdvancedJobMessages.cs | 32 +-- .../Job/JobMessages.cs | 22 +-- .../Job/ParameterSet.cs | 4 +- .../LinkCommunicationMessages.cs | 6 +- .../{MID.cs => Mid.cs} | 46 ++--- src/OpenProtocolInterpreter/MidExtensions.cs | 5 +- src/OpenProtocolInterpreter/MidInterpreter.cs | 80 +++++++- .../MotorTuning/MotorTuningMessages.cs | 12 +- .../MultiSpindle/MultiSpindleMessages.cs | 20 +- .../MultiSpindle/SpindleOrPressStatus.cs | 4 +- .../MultiSpindle/SpindleStatus.cs | 4 +- .../MultipleIdentifiers/IdentifierStatus.cs | 4 +- .../MultipleIdentifierMessages.cs | 18 +- .../OpenProtocolCommandsDisabledMessages.cs | 10 +- .../PLCUserData/PLCUserDataMessages.cs | 14 +- .../ParameterSet/Mid2501.cs | 39 +++- .../ParameterSet/Mid2502.cs | 23 +++ .../ParameterSet/Mid2503.cs | 40 ++++ .../ParameterSet/Mid2504.cs | 5 +- .../ParameterSet/Mid2505.cs | 2 +- .../ParameterSet/Mid2506.cs | 2 +- .../ParameterSet/ParameterSetMessages.cs | 41 ++-- .../PowerMACS/BoltData.cs | 4 +- .../PowerMACS/BoltResult.cs | 4 +- .../PowerMACS/PowerMACSMessages.cs | 12 +- .../PowerMACS/SpecialValue.cs | 4 +- .../PowerMACS/StepResult.cs | 4 +- .../Result/ResultMessages.cs | 8 +- .../Statistic/StatisticMessages.cs | 6 +- .../Tightening/StageResult.cs | 4 +- .../Tightening/StrategyOptions.cs | 4 +- .../Tightening/TighteningErrorStatus.cs | 8 +- .../Tightening/TighteningMessages.cs | 18 +- .../Time/TimeMessages.cs | 8 +- src/OpenProtocolInterpreter/Tool/Mid0701.cs | 58 ++---- src/OpenProtocolInterpreter/Tool/Mid0702.cs | 70 ++++--- src/OpenProtocolInterpreter/Tool/Mid0703.cs | 55 ++---- src/OpenProtocolInterpreter/Tool/Mid0704.cs | 149 +++++++++++--- .../Tool/OpenEndData.cs | 4 +- .../Tool/PIDRestriction.cs | 83 ++++++++ src/OpenProtocolInterpreter/Tool/ToolData.cs | 40 +++- .../Tool/ToolMessages.cs | 46 +++-- .../UserInterface/UserInterfaceMessages.cs | 8 +- .../Vin/VINMessages.cs | 12 +- .../_internals/DataFieldMetadata.cs | 7 +- .../_internals/Messages/IMessagesTemplate.cs | 2 + .../_internals/Messages/MessagesTemplate.cs | 89 ++++++--- .../Messages/MidCompiledInstance.cs | 8 +- 88 files changed, 1446 insertions(+), 586 deletions(-) create mode 100644 src/MIDTesters.Core/ParameterSet/TestMid2501.cs create mode 100644 src/OpenProtocolInterpreter/ExtraData.cs rename src/OpenProtocolInterpreter/{MID.cs => Mid.cs} (91%) create mode 100644 src/OpenProtocolInterpreter/ParameterSet/Mid2502.cs create mode 100644 src/OpenProtocolInterpreter/ParameterSet/Mid2503.cs create mode 100644 src/OpenProtocolInterpreter/Tool/PIDRestriction.cs diff --git a/src/MIDTesters.Core/Communication/TestMid0006.cs b/src/MIDTesters.Core/Communication/TestMid0006.cs index 0afe23d5..3b01e472 100644 --- a/src/MIDTesters.Core/Communication/TestMid0006.cs +++ b/src/MIDTesters.Core/Communication/TestMid0006.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; using OpenProtocolInterpreter.Communication; namespace MIDTesters.Communication diff --git a/src/MIDTesters.Core/Converters/BooleanConverterTest.cs b/src/MIDTesters.Core/Converters/BooleanConverterTest.cs index 7603dd24..78339411 100644 --- a/src/MIDTesters.Core/Converters/BooleanConverterTest.cs +++ b/src/MIDTesters.Core/Converters/BooleanConverterTest.cs @@ -3,7 +3,7 @@ using System.Threading; using System.Threading.Tasks; -namespace MIDTesters.Core.Converters +namespace MIDTesters.Converters { [TestClass] [TestCategory("Converters")] diff --git a/src/MIDTesters.Core/Converters/ByteConverterTest.cs b/src/MIDTesters.Core/Converters/ByteConverterTest.cs index a769489a..24ad2668 100644 --- a/src/MIDTesters.Core/Converters/ByteConverterTest.cs +++ b/src/MIDTesters.Core/Converters/ByteConverterTest.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; using System.Threading; -namespace MIDTesters.Core.Converters +namespace MIDTesters.Converters { [TestClass] [TestCategory("Converters")] diff --git a/src/MIDTesters.Core/Converters/DateTimeConverterTest.cs b/src/MIDTesters.Core/Converters/DateTimeConverterTest.cs index f7c893ea..8c5faf54 100644 --- a/src/MIDTesters.Core/Converters/DateTimeConverterTest.cs +++ b/src/MIDTesters.Core/Converters/DateTimeConverterTest.cs @@ -4,7 +4,7 @@ using System.Threading; using System.Threading.Tasks; -namespace MIDTesters.Core.Converters +namespace MIDTesters.Converters { [TestClass] [TestCategory("Converters")] diff --git a/src/MIDTesters.Core/Converters/DecimalConverterTest.cs b/src/MIDTesters.Core/Converters/DecimalConverterTest.cs index f8d0ca44..b44dfa90 100644 --- a/src/MIDTesters.Core/Converters/DecimalConverterTest.cs +++ b/src/MIDTesters.Core/Converters/DecimalConverterTest.cs @@ -3,7 +3,7 @@ using System.Threading; using System.Threading.Tasks; -namespace MIDTesters.Core.Converters +namespace MIDTesters.Converters { [TestClass] [TestCategory("Converters")] diff --git a/src/MIDTesters.Core/Converters/Int32ConverterTest.cs b/src/MIDTesters.Core/Converters/Int32ConverterTest.cs index 06c7405f..168ba220 100644 --- a/src/MIDTesters.Core/Converters/Int32ConverterTest.cs +++ b/src/MIDTesters.Core/Converters/Int32ConverterTest.cs @@ -3,7 +3,7 @@ using System.Threading; using System.Threading.Tasks; -namespace MIDTesters.Core.Converters +namespace MIDTesters.Converters { [TestClass] [TestCategory("Converters")] diff --git a/src/MIDTesters.Core/Converters/Int64ConverterTest.cs b/src/MIDTesters.Core/Converters/Int64ConverterTest.cs index 93bd89fd..9bde847e 100644 --- a/src/MIDTesters.Core/Converters/Int64ConverterTest.cs +++ b/src/MIDTesters.Core/Converters/Int64ConverterTest.cs @@ -3,7 +3,7 @@ using System.Threading; using System.Threading.Tasks; -namespace MIDTesters.Core.Converters +namespace MIDTesters.Converters { [TestClass] [TestCategory("Converters")] diff --git a/src/MIDTesters.Core/Converters/PaddingConverterTest.cs b/src/MIDTesters.Core/Converters/PaddingConverterTest.cs index 79a9b1e5..6b1a3d1d 100644 --- a/src/MIDTesters.Core/Converters/PaddingConverterTest.cs +++ b/src/MIDTesters.Core/Converters/PaddingConverterTest.cs @@ -1,7 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter; -namespace MIDTesters.Core.Converters +namespace MIDTesters.Converters { [TestClass] [TestCategory("Converters")] diff --git a/src/MIDTesters.Core/Converters/TruncatedDecimalConverterTest.cs b/src/MIDTesters.Core/Converters/TruncatedDecimalConverterTest.cs index 0c6cc426..825c50d8 100644 --- a/src/MIDTesters.Core/Converters/TruncatedDecimalConverterTest.cs +++ b/src/MIDTesters.Core/Converters/TruncatedDecimalConverterTest.cs @@ -3,7 +3,7 @@ using System.Threading; using System.Threading.Tasks; -namespace MIDTesters.Core.Converters +namespace MIDTesters.Converters { [TestClass] [TestCategory("Converters")] diff --git a/src/MIDTesters.Core/ParameterSet/TestMid2501.cs b/src/MIDTesters.Core/ParameterSet/TestMid2501.cs new file mode 100644 index 00000000..738e6d30 --- /dev/null +++ b/src/MIDTesters.Core/ParameterSet/TestMid2501.cs @@ -0,0 +1,63 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; +using OpenProtocolInterpreter.Communication; +using OpenProtocolInterpreter.ParameterSet; + +namespace MIDTesters.ParameterSet +{ + [TestClass] + [TestCategory("ParameterSet")] + public class TestMid2501 : DefaultMidTests + { + // [TestMethod] + // [TestCategory("Revision 1"), TestCategory("ASCII")] + // public void Mid2501Revision1() + // { + // string package = "00272501001 0010001"; + // var mid = _midInterpreter.Parse(package); + + // Assert.AreEqual(10, mid.ProgramId); + // Assert.AreEqual(NodeType.ParameterSet, mid.NodeType); + // AssertEqualPackages(package, mid); + // } + + // [TestMethod] + // [TestCategory("Revision 1"), TestCategory("ByteArray")] + // public void Mid2501ByteRevision1() + // { + // string package = "00272501001 0010001"; + // byte[] bytes = GetAsciiBytes(package); + // var mid = _midInterpreter.Parse(bytes); + + // Assert.AreEqual(10, mid.ProgramId); + // Assert.AreEqual(NodeType.ParameterSet, mid.NodeType); + // AssertEqualPackages(bytes, mid); + // } + + // [TestMethod] + // [TestCategory("Revision 1"), TestCategory("ASCII")] + // public void Mid2501ExtraDataRevision1() + // { + // string package = "00360006001 2501001070010001"; + // var mid = _midInterpreter.Parse(package); + // var extraDataMid = _midInterpreter.ParseExtra(mid); + // Assert.AreEqual(10, extraDataMid.ProgramId); + // Assert.AreEqual(NodeType.ParameterSet, extraDataMid.NodeType); + // AssertEqualPackages(package, mid); + // } + + // [TestMethod] + // [TestCategory("Revision 1"), TestCategory("ByteArray")] + // public void Mid2501ExtraDataByteRevision1() + // { + // string package = "00360006001 2501001070010001"; + // byte[] bytes = GetAsciiBytes(package); + // var mid = _midInterpreter.Parse(bytes); + // var extraDataMid = _midInterpreter.ParseExtra(mid); + + // Assert.AreEqual(10, extraDataMid.ProgramId); + // Assert.AreEqual(NodeType.ParameterSet, extraDataMid.NodeType); + // AssertEqualPackages(bytes, mid); + // } + } +} diff --git a/src/MIDTesters.Core/ParameterSet/TestMid2504.cs b/src/MIDTesters.Core/ParameterSet/TestMid2504.cs index 3cfc61cb..33c657a6 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid2504.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid2504.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter.Communication; using OpenProtocolInterpreter.ParameterSet; namespace MIDTesters.ParameterSet diff --git a/src/MIDTesters.Core/ParameterSet/TestMid2505.cs b/src/MIDTesters.Core/ParameterSet/TestMid2505.cs index 0e5af0ba..73ae0b7b 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid2505.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid2505.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter.Communication; using OpenProtocolInterpreter.ParameterSet; namespace MIDTesters.ParameterSet @@ -31,5 +32,32 @@ public void Mid2505ByteRevision1() Assert.AreEqual(10, mid.ParameterSetId); AssertEqualPackages(bytes, mid); } + + // [TestMethod] + // [TestCategory("Revision 1"), TestCategory("ASCII")] + // public void Mid2505ExtraDataRevision1() + // { + // string package = "00910006001 25050016201000201000003010000000005010030190500000002022-08-12:13:33:22"; + // var mid = _midInterpreter.Parse(package); + + // var extraDataMid = _midInterpreter.ParseExtra(mid); + // Assert.AreEqual(10, extraDataMid.ParameterSetId); + // Assert.AreEqual(2, extraDataMid.NumberOfParameterDataFields); + // Assert.AreEqual(2, extraDataMid.VariableDataFields.Count); + // AssertEqualPackages(package, mid); + // } + + // [TestMethod] + // [TestCategory("Revision 1"), TestCategory("ByteArray")] + // public void Mid2505ExtraDataByteRevision1() + // { + // string package = "00350006001 250500106010000"; + // byte[] bytes = GetAsciiBytes(package); + // var mid = _midInterpreter.Parse(package); + + // var extraDataMid = _midInterpreter.ParseExtra(mid); + // Assert.AreEqual(10, extraDataMid.ParameterSetId); + // AssertEqualPackages(bytes, mid); + // } } } diff --git a/src/MIDTesters.Core/ParameterSet/TestMid2506.cs b/src/MIDTesters.Core/ParameterSet/TestMid2506.cs index 9b03e35f..94c0a0b8 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid2506.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid2506.cs @@ -1,5 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter; +using OpenProtocolInterpreter.Communication; using OpenProtocolInterpreter.ParameterSet; namespace MIDTesters.ParameterSet @@ -32,5 +33,32 @@ public void Mid2506ByteRevision1() Assert.AreEqual(NodeType.MultistepTighteningProgram, mid.NodeType); AssertEqualPackages(bytes, mid); } + + // [TestMethod] + // [TestCategory("Revision 1"), TestCategory("ASCII")] + // public void Mid2506ExtraDataRevision1() + // { + // string package = "00360006001 2506001070030201"; + // var mid = _midInterpreter.Parse(package); + + // var extraDataMid = _midInterpreter.ParseExtra(mid); + // Assert.AreEqual(30, extraDataMid.ProgramId); + // Assert.AreEqual(NodeType.MultistepTighteningProgram, extraDataMid.NodeType); + // AssertEqualPackages(package, mid); + // } + + // [TestMethod] + // [TestCategory("Revision 1"), TestCategory("ByteArray")] + // public void Mid2506ExtraDataByteRevision1() + // { + // string package = "00360006001 2506001070030201"; + // byte[] bytes = GetAsciiBytes(package); + // var mid = _midInterpreter.Parse(bytes); + + // var extraDataMid = _midInterpreter.ParseExtra(mid); + // Assert.AreEqual(30, extraDataMid.ProgramId); + // Assert.AreEqual(NodeType.MultistepTighteningProgram, extraDataMid.NodeType); + // AssertEqualPackages(bytes, mid); + // } } } diff --git a/src/MIDTesters.Core/Result/TestMid1201.cs b/src/MIDTesters.Core/Result/TestMid1201.cs index 2ab65e2a..78fdac65 100644 --- a/src/MIDTesters.Core/Result/TestMid1201.cs +++ b/src/MIDTesters.Core/Result/TestMid1201.cs @@ -1,7 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter.Result; -namespace MIDTesters.Core.Result +namespace MIDTesters.Result { [TestClass] [TestCategory("Result")] diff --git a/src/MIDTesters.Core/TestCustomMid.cs b/src/MIDTesters.Core/TestCustomMid.cs index 4e84c337..a2b528ef 100644 --- a/src/MIDTesters.Core/TestCustomMid.cs +++ b/src/MIDTesters.Core/TestCustomMid.cs @@ -75,39 +75,12 @@ public class NewMid0083 : Mid private const int LAST_REVISION = 1; public const int MID = 83; - public DateTime Time - { - get => GetField(1, (int)DataFields.TIME).GetValue(OpenProtocolConvert.ToDateTime); - set => GetField(1, (int)DataFields.TIME).SetValue(OpenProtocolConvert.ToString, value); - } - public string TimeZone - { - get => GetField(1, (int)DataFields.TIMEZONE).Value; - set => GetField(1, (int)DataFields.TIMEZONE).SetValue(value); - } - + [TimestampDataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 19)] + public DateTime Time { get; set; } + [StringDataFieldDefinition(revision: 1, field: 2, Index = 41, Size = 2)] + public string TimeZone { get; set; } public NewMid0083() : base(MID, LAST_REVISION) { } - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - new DataField((int)DataFields.TIME, 20, 19), - new DataField((int)DataFields.TIMEZONE, 41, 2) - } - } - }; - } - - public enum DataFields - { - TIME, - TIMEZONE - } } } diff --git a/src/MIDTesters.Core/Tool/TestMid0702.cs b/src/MIDTesters.Core/Tool/TestMid0702.cs index 31ddce66..44345334 100644 --- a/src/MIDTesters.Core/Tool/TestMid0702.cs +++ b/src/MIDTesters.Core/Tool/TestMid0702.cs @@ -1,7 +1,8 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter.Communication; using OpenProtocolInterpreter.Tool; -namespace MIDTesters.Core.Tool +namespace MIDTesters.Tool { [TestClass] [TestCategory("Tool")] @@ -9,29 +10,61 @@ public class TestMid0702 : DefaultMidTests { [TestMethod] [TestCategory("Revision 1"), TestCategory("ASCII")] - public void Mid0701Revision1() + public void Mid0702Revision1() { - string package = "01050702001 0100400301200012040000000QST50-150CTT01202012040000000SERIALNUMBER0120300201000000011"; + string package = "01000702001 00301200012040000000QST50-150CTT01202012040000000SERIALNUMBER0120300201000000011"; var mid = _midInterpreter.Parse(package); Assert.AreEqual(3, mid.ToolDataUpload.Count); - Assert.AreEqual(3, mid.NumberOfToolParameters); - Assert.AreEqual(40, mid.ToolNumber); + Assert.AreEqual(3, mid.NumberOfToolPIDs); AssertEqualPackages(package, mid); } [TestMethod] [TestCategory("Revision 1"), TestCategory("ByteArray")] - public void Mid0701ByteRevision1() + public void Mid0702ByteRevision1() { - string package = "01050702001 0100400301200012040000000QST50-150CTT01202012040000000SERIALNUMBER0120300201000000011"; + string package = "01000702001 00301200012040000000QST50-150CTT01202012040000000SERIALNUMBER0120300201000000011"; byte[] bytes = GetAsciiBytes(package); var mid = _midInterpreter.Parse(bytes); Assert.AreEqual(3, mid.ToolDataUpload.Count); - Assert.AreEqual(3, mid.NumberOfToolParameters); - Assert.AreEqual(40, mid.ToolNumber); + Assert.AreEqual(3, mid.NumberOfToolPIDs); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("ASCII")] + public void Mid0702ExtraDataRequestRevision1() + { + string package = "00350006001 070200106010003"; + var mid = _midInterpreter.Parse(package); + + var extraData = _midInterpreter.ParseExtraData(mid); + Assert.AreEqual(Mid0006.MID, mid.Header.Mid); + Assert.AreEqual(Mid0702.MID, mid.RequestedMid); + Assert.AreEqual(1, mid.WantedRevision); + Assert.AreEqual(6, mid.ExtraDataLength); + Assert.AreEqual(3, extraData.ToolNumber); + AssertEqualPackages(package, mid); + } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("ByteArray")] + public void Mid0702ExtraDataRequestByteRevision1() + { + string package = "00350006001 070200106010003"; + byte[] bytes = GetAsciiBytes(package); + var mid = _midInterpreter.Parse(bytes); + + var extraData = _midInterpreter.ParseExtraData(mid); + + Assert.AreEqual(Mid0702.MID, mid.RequestedMid); + Assert.AreEqual(1, mid.WantedRevision); + Assert.AreEqual(6, mid.ExtraDataLength); + Assert.AreEqual(3, extraData.ToolNumber); + AssertEqualPackages(bytes, mid); + } + } } diff --git a/src/MIDTesters.Core/Tool/TestMid0703.cs b/src/MIDTesters.Core/Tool/TestMid0703.cs index ee502e04..8445ed2d 100644 --- a/src/MIDTesters.Core/Tool/TestMid0703.cs +++ b/src/MIDTesters.Core/Tool/TestMid0703.cs @@ -1,7 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter.Tool; -namespace MIDTesters.Core.Tool +namespace MIDTesters.Tool { [TestClass] [TestCategory("Tool")] @@ -9,7 +9,7 @@ public class TestMid0703 : DefaultMidTests { [TestMethod] [TestCategory("Revision 1"), TestCategory("ASCII")] - public void Mid0701Revision1() + public void Mid0703Revision1() { string package = "01050703001 0100400301200012040000000QST50-150CTT01202012040000000SERIALNUMBER0120300201000000011"; var mid = _midInterpreter.Parse(package); @@ -22,7 +22,7 @@ public void Mid0701Revision1() [TestMethod] [TestCategory("Revision 1"), TestCategory("ByteArray")] - public void Mid0701ByteRevision1() + public void Mid0703ByteRevision1() { string package = "01050703001 0100400301200012040000000QST50-150CTT01202012040000000SERIALNUMBER0120300201000000011"; byte[] bytes = GetAsciiBytes(package); diff --git a/src/MIDTesters.Core/Tool/TestMid0704.cs b/src/MIDTesters.Core/Tool/TestMid0704.cs index 1b21595d..b4cfb653 100644 --- a/src/MIDTesters.Core/Tool/TestMid0704.cs +++ b/src/MIDTesters.Core/Tool/TestMid0704.cs @@ -1,7 +1,9 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter.Communication; using OpenProtocolInterpreter.Tool; +using System.Collections.Generic; -namespace MIDTesters.Core.Tool +namespace MIDTesters.Tool { [TestClass] [TestCategory("Tool")] @@ -31,5 +33,184 @@ public void Mid0704ByteRevision1() Assert.AreEqual(2, mid.VariableDataFields.Count); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("ASCII")] + public void Mid0704ExtraDataRequestRevision1() + { + string package = "00480006001 0704001190001002001213001215"; + var mid = _midInterpreter.Parse(package); + + var extraData = _midInterpreter.ParseExtraData(mid); + + Assert.AreEqual(Mid0006.MID, mid.Header.Mid); + Assert.AreEqual(Mid0704.MID, mid.RequestedMid); + Assert.AreEqual(1, mid.WantedRevision); + Assert.AreEqual(19, mid.ExtraDataLength); + Assert.AreEqual(1, extraData.ToolNumber); + Assert.AreEqual(2, extraData.TotalRequestedPIDs); + Assert.AreEqual(2, extraData.RequestedPIDs.Count); + Assert.AreEqual(1213, extraData.RequestedPIDs[0]); + Assert.AreEqual(1215, extraData.RequestedPIDs[1]); + AssertEqualPackages(package, mid); + } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("ByteArray")] + public void Mid0704ExtraDataRequestByteRevision1() + { + string package = "00480006001 0704001190001002001213001215"; + byte[] bytes = GetAsciiBytes(package); + var mid = _midInterpreter.Parse(bytes); + + var extraData = _midInterpreter.ParseExtraData(mid); + + Assert.AreEqual(Mid0704.MID, mid.RequestedMid); + Assert.AreEqual(19, mid.ExtraDataLength); + Assert.AreEqual(1, extraData.ToolNumber); + Assert.AreEqual(2, extraData.TotalRequestedPIDs); + Assert.AreEqual(2, extraData.RequestedPIDs.Count); + Assert.AreEqual(1213, extraData.RequestedPIDs[0]); + Assert.AreEqual(1215, extraData.RequestedPIDs[1]); + AssertEqualPackages(bytes, mid); + } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("ASCII")] + public void Mid0704ExtraDataRequestPackRevision1() + { + string package = "00480006001 0704001190001002001213001215"; + var mid = new Mid0006(); + + mid.SetExtraData(new Mid0704ExtraDataRequest(1) + { + ToolNumber = 1, + RequestedPIDs = new List() { 1213, 1215 } + }); + + Assert.AreEqual(Mid0704.MID, mid.RequestedMid); + Assert.AreEqual(19, mid.ExtraDataLength); + Assert.AreEqual("0001002001213001215", mid.ExtraData); + AssertEqualPackages(package, mid); + } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("ASCII")] + public void Mid0704ExtraDataSubscriptionRevision1() + { + string package = "00540008001 0704001250001002001213005001215000"; + var mid = _midInterpreter.Parse(package); + + var extraData = _midInterpreter.ParseExtraData(mid); + + Assert.AreEqual(Mid0008.MID, mid.Header.Mid); + Assert.AreEqual(Mid0704.MID, mid.SubscriptionMid); + Assert.AreEqual(1, mid.WantedRevision); + Assert.AreEqual(25, mid.ExtraDataLength); + Assert.AreEqual(1, extraData.ToolNumber); + Assert.AreEqual(2, extraData.NumberOfPIDs); + Assert.AreEqual(2, extraData.PIDRestrictions.Count); + Assert.AreEqual(1213, extraData.PIDRestrictions[0].PID); + Assert.AreEqual(5, extraData.PIDRestrictions[0].Restriction); + Assert.AreEqual(1215, extraData.PIDRestrictions[1].PID); + Assert.AreEqual(0, extraData.PIDRestrictions[1].Restriction); + AssertEqualPackages(package, mid); + } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("ByteArray")] + public void Mid0704ExtraDataSubscriptionByteRevision1() + { + string package = "00540008001 0704001250001002001213005001215000"; + byte[] bytes = GetAsciiBytes(package); + var mid = _midInterpreter.Parse(bytes); + + var extraData = _midInterpreter.ParseExtraData(mid); + + Assert.AreEqual(Mid0704.MID, mid.SubscriptionMid); + Assert.AreEqual(25, mid.ExtraDataLength); + Assert.AreEqual(1, extraData.ToolNumber); + Assert.AreEqual(2, extraData.NumberOfPIDs); + Assert.AreEqual(2, extraData.PIDRestrictions.Count); + Assert.AreEqual(1213, extraData.PIDRestrictions[0].PID); + Assert.AreEqual(5, extraData.PIDRestrictions[0].Restriction); + Assert.AreEqual(1215, extraData.PIDRestrictions[1].PID); + Assert.AreEqual(0, extraData.PIDRestrictions[1].Restriction); + AssertEqualPackages(bytes, mid); + } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("ASCII")] + public void Mid0704ExtraDataSubscriptionPackRevision1() + { + string package = "00540008001 0704001250001002001213005001215000"; + var mid = new Mid0008(); + + mid.SetExtraData(new Mid0704ExtraDataSubscription(1) + { + ToolNumber = 1, + PIDRestrictions = new List() + { + new PIDRestriction(1213, 5), + new PIDRestriction(1215, 0) + } + }); + + Assert.AreEqual(Mid0704.MID, mid.SubscriptionMid); + Assert.AreEqual(25, mid.ExtraDataLength); + Assert.AreEqual("0001002001213005001215000", mid.ExtraData); + AssertEqualPackages(package, mid); + } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("ASCII")] + public void Mid0704ExtraDataUnsubscriptionRevision1() + { + string package = "00330009001 0704001040001"; + var mid = _midInterpreter.Parse(package); + + var extraData = _midInterpreter.ParseExtraData(mid); + + Assert.AreEqual(Mid0009.MID, mid.Header.Mid); + Assert.AreEqual(Mid0704.MID, mid.UnsubscriptionMid); + Assert.AreEqual(1, mid.ExtraDataRevision); + Assert.AreEqual(4, mid.ExtraDataLength); + Assert.AreEqual(1, extraData.ToolNumber); + AssertEqualPackages(package, mid); + } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("ByteArray")] + public void Mid0704ExtraDataUnsubscriptionByteRevision1() + { + string package = "00330009001 0704001040001"; + byte[] bytes = GetAsciiBytes(package); + var mid = _midInterpreter.Parse(bytes); + + var extraData = _midInterpreter.ParseExtraData(mid); + + Assert.AreEqual(Mid0704.MID, mid.UnsubscriptionMid); + Assert.AreEqual(4, mid.ExtraDataLength); + Assert.AreEqual(1, extraData.ToolNumber); + AssertEqualPackages(bytes, mid); + } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("ASCII")] + public void Mid0704ExtraDataUnsubscriptionPackRevision1() + { + string package = "00330009001 0704001040001"; + var mid = new Mid0009(); + + mid.SetExtraData(new Mid0704ExtraDataUnsubscription(1) + { + ToolNumber = 1 + }); + + Assert.AreEqual(Mid0704.MID, mid.UnsubscriptionMid); + Assert.AreEqual(4, mid.ExtraDataLength); + Assert.AreEqual("0001", mid.ExtraData); + AssertEqualPackages(package, mid); + } } } diff --git a/src/OpenProtocolInterpreter/Alarm/AlarmMessages.cs b/src/OpenProtocolInterpreter/Alarm/AlarmMessages.cs index afaa1bcd..f944d579 100644 --- a/src/OpenProtocolInterpreter/Alarm/AlarmMessages.cs +++ b/src/OpenProtocolInterpreter/Alarm/AlarmMessages.cs @@ -11,19 +11,19 @@ internal class AlarmMessages : MessagesTemplate { public AlarmMessages() : base() { - _templates = new Dictionary() + _templates = new Dictionary>() { - { Mid0070.MID, new MidCompiledInstance(typeof(Mid0070)) }, - { Mid0071.MID, new MidCompiledInstance(typeof(Mid0071)) }, - { Mid0072.MID, new MidCompiledInstance(typeof(Mid0072)) }, - { Mid0073.MID, new MidCompiledInstance(typeof(Mid0073)) }, - { Mid0074.MID, new MidCompiledInstance(typeof(Mid0074)) }, - { Mid0075.MID, new MidCompiledInstance(typeof(Mid0075)) }, - { Mid0076.MID, new MidCompiledInstance(typeof(Mid0076)) }, - { Mid0077.MID, new MidCompiledInstance(typeof(Mid0077)) }, - { Mid0078.MID, new MidCompiledInstance(typeof(Mid0078)) }, - { Mid1000.MID, new MidCompiledInstance(typeof(Mid1000)) }, - { Mid1001.MID, new MidCompiledInstance(typeof(Mid1001)) }, + { Mid0070.MID, new CompiledInstance(typeof(Mid0070)) }, + { Mid0071.MID, new CompiledInstance(typeof(Mid0071)) }, + { Mid0072.MID, new CompiledInstance(typeof(Mid0072)) }, + { Mid0073.MID, new CompiledInstance(typeof(Mid0073)) }, + { Mid0074.MID, new CompiledInstance(typeof(Mid0074)) }, + { Mid0075.MID, new CompiledInstance(typeof(Mid0075)) }, + { Mid0076.MID, new CompiledInstance(typeof(Mid0076)) }, + { Mid0077.MID, new CompiledInstance(typeof(Mid0077)) }, + { Mid0078.MID, new CompiledInstance(typeof(Mid0078)) }, + { Mid1000.MID, new CompiledInstance(typeof(Mid1000)) }, + { Mid1001.MID, new CompiledInstance(typeof(Mid1001)) }, }; } diff --git a/src/OpenProtocolInterpreter/Alarm/Mid1000.cs b/src/OpenProtocolInterpreter/Alarm/Mid1000.cs index 90906d16..819c9235 100644 --- a/src/OpenProtocolInterpreter/Alarm/Mid1000.cs +++ b/src/OpenProtocolInterpreter/Alarm/Mid1000.cs @@ -7,9 +7,9 @@ namespace OpenProtocolInterpreter.Alarm /// /// Alarm /// An alarm has appeared in the controller. The current alarm is uploaded from the controller to the integrator. - /// This MID replace the old alarm MID 0071. + /// This MID replace the old alarm . /// Message sent by: Controller - /// Answer: Alarm status acknowledge + /// Answer: Alarm acknowledge /// public class Mid1000 : Mid, IAlarm, IController, IAcknowledgeable { diff --git a/src/OpenProtocolInterpreter/Alarm/Mid1001.cs b/src/OpenProtocolInterpreter/Alarm/Mid1001.cs index 2e87cc91..8e4d4b18 100644 --- a/src/OpenProtocolInterpreter/Alarm/Mid1001.cs +++ b/src/OpenProtocolInterpreter/Alarm/Mid1001.cs @@ -1,7 +1,7 @@ namespace OpenProtocolInterpreter.Alarm { /// - /// Alarm status acknowledge + /// Alarm acknowledge /// Acknowledge for Alarm /// Message sent by: Integrator /// Answer : None diff --git a/src/OpenProtocolInterpreter/ApplicationController/ApplicationControllerMessages.cs b/src/OpenProtocolInterpreter/ApplicationController/ApplicationControllerMessages.cs index 1ae5e836..de164a1c 100644 --- a/src/OpenProtocolInterpreter/ApplicationController/ApplicationControllerMessages.cs +++ b/src/OpenProtocolInterpreter/ApplicationController/ApplicationControllerMessages.cs @@ -11,9 +11,9 @@ internal class ApplicationControllerMessages : MessagesTemplate { public ApplicationControllerMessages() : base() { - _templates = new Dictionary() + _templates = new Dictionary>() { - { Mid0270.MID, new MidCompiledInstance(typeof(Mid0270)) } + { Mid0270.MID, new CompiledInstance(typeof(Mid0270)) } }; } diff --git a/src/OpenProtocolInterpreter/ApplicationSelector/ApplicationSelectorMessages.cs b/src/OpenProtocolInterpreter/ApplicationSelector/ApplicationSelectorMessages.cs index 0c65896a..7fedb7f1 100644 --- a/src/OpenProtocolInterpreter/ApplicationSelector/ApplicationSelectorMessages.cs +++ b/src/OpenProtocolInterpreter/ApplicationSelector/ApplicationSelectorMessages.cs @@ -11,14 +11,14 @@ internal class ApplicationSelectorMessages : MessagesTemplate { public ApplicationSelectorMessages() : base() { - _templates = new Dictionary() + _templates = new Dictionary>() { - { Mid0250.MID, new MidCompiledInstance(typeof(Mid0250)) }, - { Mid0251.MID, new MidCompiledInstance(typeof(Mid0251)) }, - { Mid0252.MID, new MidCompiledInstance(typeof(Mid0252)) }, - { Mid0253.MID, new MidCompiledInstance(typeof(Mid0253)) }, - { Mid0254.MID, new MidCompiledInstance(typeof(Mid0254)) }, - { Mid0255.MID, new MidCompiledInstance(typeof(Mid0255)) } + { Mid0250.MID, new CompiledInstance(typeof(Mid0250)) }, + { Mid0251.MID, new CompiledInstance(typeof(Mid0251)) }, + { Mid0252.MID, new CompiledInstance(typeof(Mid0252)) }, + { Mid0253.MID, new CompiledInstance(typeof(Mid0253)) }, + { Mid0254.MID, new CompiledInstance(typeof(Mid0254)) }, + { Mid0255.MID, new CompiledInstance(typeof(Mid0255)) } }; } diff --git a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0251.cs b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0251.cs index 9f4f73fb..83869e76 100644 --- a/src/OpenProtocolInterpreter/ApplicationSelector/Mid0251.cs +++ b/src/OpenProtocolInterpreter/ApplicationSelector/Mid0251.cs @@ -70,13 +70,13 @@ public SocketStatusListDefinitionAttribute(int field, int revision) : base(field } - internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { return new DataField>(Field, index, Size, HasPrefix) { DefaultConverter = PackSocketStatus, DefaultParser = ParseSocketStatus - }.Bind(mid, propertyInfo); + }.Bind(owner, propertyInfo); } protected static string PackSocketStatus(char paddingChar, int size, PaddingOrientation orientation, List socketStatus) diff --git a/src/OpenProtocolInterpreter/ApplicationToolLocationSystem/ApplicationToolLocationSystemMessages.cs b/src/OpenProtocolInterpreter/ApplicationToolLocationSystem/ApplicationToolLocationSystemMessages.cs index e5e6b24f..04828634 100644 --- a/src/OpenProtocolInterpreter/ApplicationToolLocationSystem/ApplicationToolLocationSystemMessages.cs +++ b/src/OpenProtocolInterpreter/ApplicationToolLocationSystem/ApplicationToolLocationSystemMessages.cs @@ -11,14 +11,14 @@ internal class ApplicationToolLocationSystemMessages : MessagesTemplate { public ApplicationToolLocationSystemMessages() : base() { - _templates = new Dictionary() + _templates = new Dictionary>() { - { Mid0260.MID, new MidCompiledInstance(typeof(Mid0260)) }, - { Mid0261.MID, new MidCompiledInstance(typeof(Mid0261)) }, - { Mid0262.MID, new MidCompiledInstance(typeof(Mid0262)) }, - { Mid0263.MID, new MidCompiledInstance(typeof(Mid0263)) }, - { Mid0264.MID, new MidCompiledInstance(typeof(Mid0264)) }, - { Mid0265.MID, new MidCompiledInstance(typeof(Mid0265)) } + { Mid0260.MID, new CompiledInstance(typeof(Mid0260)) }, + { Mid0261.MID, new CompiledInstance(typeof(Mid0261)) }, + { Mid0262.MID, new CompiledInstance(typeof(Mid0262)) }, + { Mid0263.MID, new CompiledInstance(typeof(Mid0263)) }, + { Mid0264.MID, new CompiledInstance(typeof(Mid0264)) }, + { Mid0265.MID, new CompiledInstance(typeof(Mid0265)) } }; } diff --git a/src/OpenProtocolInterpreter/AutomaticManualMode/AutomaticManualModeMessages.cs b/src/OpenProtocolInterpreter/AutomaticManualMode/AutomaticManualModeMessages.cs index 07d63e8d..881ea0a0 100644 --- a/src/OpenProtocolInterpreter/AutomaticManualMode/AutomaticManualModeMessages.cs +++ b/src/OpenProtocolInterpreter/AutomaticManualMode/AutomaticManualModeMessages.cs @@ -11,14 +11,14 @@ internal class AutomaticManualModeMessages : MessagesTemplate { public AutomaticManualModeMessages() : base() { - _templates = new Dictionary() + _templates = new Dictionary>() { - { Mid0400.MID, new MidCompiledInstance(typeof(Mid0400)) }, - { Mid0401.MID, new MidCompiledInstance(typeof(Mid0401)) }, - { Mid0402.MID, new MidCompiledInstance(typeof(Mid0402)) }, - { Mid0403.MID, new MidCompiledInstance(typeof(Mid0403)) }, - { Mid0410.MID, new MidCompiledInstance(typeof(Mid0410)) }, - { Mid0411.MID, new MidCompiledInstance(typeof(Mid0411)) } + { Mid0400.MID, new CompiledInstance(typeof(Mid0400)) }, + { Mid0401.MID, new CompiledInstance(typeof(Mid0401)) }, + { Mid0402.MID, new CompiledInstance(typeof(Mid0402)) }, + { Mid0403.MID, new CompiledInstance(typeof(Mid0403)) }, + { Mid0410.MID, new CompiledInstance(typeof(Mid0410)) }, + { Mid0411.MID, new CompiledInstance(typeof(Mid0411)) } }; } diff --git a/src/OpenProtocolInterpreter/Communication/CommunicationMessages.cs b/src/OpenProtocolInterpreter/Communication/CommunicationMessages.cs index f210b320..3fcef47c 100644 --- a/src/OpenProtocolInterpreter/Communication/CommunicationMessages.cs +++ b/src/OpenProtocolInterpreter/Communication/CommunicationMessages.cs @@ -11,16 +11,16 @@ internal class CommunicationMessages : MessagesTemplate { public CommunicationMessages() : base() { - _templates = new Dictionary() + _templates = new Dictionary>() { - { Mid0001.MID, new MidCompiledInstance(typeof(Mid0001)) }, - { Mid0002.MID, new MidCompiledInstance(typeof(Mid0002)) }, - { Mid0003.MID, new MidCompiledInstance(typeof(Mid0003)) }, - { Mid0004.MID, new MidCompiledInstance(typeof(Mid0004)) }, - { Mid0005.MID, new MidCompiledInstance(typeof(Mid0005)) }, - { Mid0006.MID, new MidCompiledInstance(typeof(Mid0006)) }, - { Mid0008.MID, new MidCompiledInstance(typeof(Mid0008)) }, - { Mid0009.MID, new MidCompiledInstance(typeof(Mid0009)) } + { Mid0001.MID, new CompiledInstance(typeof(Mid0001)) }, + { Mid0002.MID, new CompiledInstance(typeof(Mid0002)) }, + { Mid0003.MID, new CompiledInstance(typeof(Mid0003)) }, + { Mid0004.MID, new CompiledInstance(typeof(Mid0004)) }, + { Mid0005.MID, new CompiledInstance(typeof(Mid0005)) }, + { Mid0006.MID, new CompiledInstance(typeof(Mid0006)) }, + { Mid0008.MID, new CompiledInstance(typeof(Mid0008)) }, + { Mid0009.MID, new CompiledInstance(typeof(Mid0009)) } }; } diff --git a/src/OpenProtocolInterpreter/Communication/Mid0006.cs b/src/OpenProtocolInterpreter/Communication/Mid0006.cs index 3275b9af..f55c9051 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0006.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0006.cs @@ -15,7 +15,7 @@ namespace OpenProtocolInterpreter.Communication /// Message sent by: Integrator /// Answer: MID Requested for or Command error. Error described at each MID description. /// - public class Mid0006 : Mid, ICommunication, IIntegrator + public class Mid0006 : Mid, ICommunication, IIntegrator, IExtraDataContainer { public const int MID = 6; @@ -51,6 +51,14 @@ public override string Pack() return base.Pack(); } + public void SetExtraData(TExtraData extraData) where TExtraData : ExtraData, IExtraDataRequest + { + RequestedMid = extraData.Mid; + WantedRevision = extraData.Revision; + ExtraData = extraData.Pack(); + ExtraDataLength = ExtraData?.Length ?? 0; + } + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { base.ProcessDataField(dataField, package); diff --git a/src/OpenProtocolInterpreter/Communication/Mid0008.cs b/src/OpenProtocolInterpreter/Communication/Mid0008.cs index 3cfe305a..5919a577 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0008.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0008.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Runtime.Versioning; namespace OpenProtocolInterpreter.Communication { @@ -20,7 +18,7 @@ namespace OpenProtocolInterpreter.Communication /// MID revision unsupported or Invalid data code and the MID subscribed for /// /// - public class Mid0008 : Mid, ICommunication, IIntegrator + public class Mid0008 : Mid, ICommunication, IIntegrator, IExtraDataContainer { public const int MID = 8; @@ -49,6 +47,14 @@ public Mid0008(Header header) : base(header) { } + public void SetExtraData(TExtraData extraData) where TExtraData : ExtraData, IExtraDataSubscription + { + SubscriptionMid = extraData.Mid; + WantedRevision = extraData.Revision; + ExtraData = extraData.Pack(); + ExtraDataLength = ExtraData?.Length ?? 0; + } + public override string Pack() { ExtraDataLength = ExtraData?.Length ?? 0; diff --git a/src/OpenProtocolInterpreter/Communication/Mid0009.cs b/src/OpenProtocolInterpreter/Communication/Mid0009.cs index 2c420cb4..daf6c867 100644 --- a/src/OpenProtocolInterpreter/Communication/Mid0009.cs +++ b/src/OpenProtocolInterpreter/Communication/Mid0009.cs @@ -19,15 +19,16 @@ namespace OpenProtocolInterpreter.Communication /// MID revision unsupported or Invalid data code and the MID subscribed for /// /// - public class Mid0009 : Mid, ICommunication, IIntegrator + public class Mid0009 : Mid, ICommunication, IIntegrator, IExtraDataContainer { + private int _extraDataRevision; public const int MID = 9; [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 4, HasPrefix = false)] public int UnsubscriptionMid { get; set; } [Int32DataFieldDefinition(revision: 1, field: 2, Index = 24, Size = 3, HasPrefix = false)] - public int ExtraDataRevision { get; set; } + public int ExtraDataRevision { get => _extraDataRevision; set => _extraDataRevision = value; } [Int32DataFieldDefinition(revision: 1, field: 3, Index = 27, Size = 2, HasPrefix = false)] public int ExtraDataLength { get; set; } @@ -35,6 +36,7 @@ public class Mid0009 : Mid, ICommunication, IIntegrator [StringDataFieldDefinition(revision: 1, field: 4, Index = 29, Size = 0, HasPrefix = false)] public string ExtraData { get; set; } + int IExtraDataContainer.WantedRevision { get => _extraDataRevision; set => _extraDataRevision = value; } public Mid0009() : this(new Header() { Mid = MID, @@ -55,6 +57,15 @@ public override string Pack() return base.Pack(); } + public void SetExtraData(TExtraData extraData) where TExtraData : ExtraData, IExtraDataUnsubscription + { + UnsubscriptionMid = extraData.Mid; + ExtraDataRevision = extraData.Revision; + ExtraData = extraData.Pack(); + ExtraDataLength = ExtraData?.Length ?? 0; + } + + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { base.ProcessDataField(dataField, package); diff --git a/src/OpenProtocolInterpreter/CustomMessages.cs b/src/OpenProtocolInterpreter/CustomMessages.cs index cde16ef4..45fceab8 100644 --- a/src/OpenProtocolInterpreter/CustomMessages.cs +++ b/src/OpenProtocolInterpreter/CustomMessages.cs @@ -1,19 +1,25 @@ using OpenProtocolInterpreter.Messages; using System; using System.Collections.Generic; +using System.Linq; namespace OpenProtocolInterpreter { internal class CustomMessages : MessagesTemplate { - public CustomMessages(IDictionary midTypes) + public CustomMessages(IDictionary types) { - foreach(var type in midTypes) + foreach (var type in types.Where(x => typeof(Mid).IsAssignableFrom(x.Value))) { - _templates.Add(type.Key, new MidCompiledInstance(type.Value)); + _templates.Add(type.Key, new CompiledInstance(type.Value)); + } + + foreach (var type in types.Where(x => typeof(ExtraData).IsAssignableFrom(x.Value))) + { + AddExtraDataTemplate(type.Key, new CompiledInstance(type.Value)); } } - public override bool IsAssignableTo(int mid) => _templates.ContainsKey(mid); + public override bool IsAssignableTo(int mid) => _templates.ContainsKey(mid); } } diff --git a/src/OpenProtocolInterpreter/DataField.cs b/src/OpenProtocolInterpreter/DataField.cs index 68a1630f..e18979e3 100644 --- a/src/OpenProtocolInterpreter/DataField.cs +++ b/src/OpenProtocolInterpreter/DataField.cs @@ -201,7 +201,7 @@ public static DataField Volatile(Enum field, bool hasPrefix = true) public class DataField : DataField, IBackedPropertyDataField { - private Mid _owner; + private object _owner; private PropertyInfo _backingProperty; protected internal Func DefaultConverter; protected internal Func DefaultParser; @@ -244,10 +244,10 @@ private void SetBackingPropertyValueIfBound(object value) _backingProperty.SetValue(_owner, value); } - protected internal DataField Bind(Mid owner, string propertyName) + protected internal DataField Bind(object owner, string propertyName) => Bind(owner, owner.GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance)); - protected internal DataField Bind(Mid owner, PropertyInfo propertyInfo) + protected internal DataField Bind(object owner, PropertyInfo propertyInfo) { _owner = owner; _backingProperty = propertyInfo; diff --git a/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs b/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs index 818f56d9..11dc9f71 100644 --- a/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs +++ b/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs @@ -35,14 +35,17 @@ public DataFieldDefinitionAttribute(int field, int revision) : this(revision) Field = field; } - internal DataField CreateAndBind(Mid mid, PropertyInfo propertyInfo) - => CreateAndBind(mid, propertyInfo, Index); - internal DataField CreateAndBind(Mid mid, PropertyInfo propertyInfo, int index) + /// + /// Creates the data field and binds it to the decorated property of its owner ( or ). + /// + internal DataField CreateAndBind(object owner, PropertyInfo propertyInfo) + => CreateAndBind(owner, propertyInfo, Index); + internal DataField CreateAndBind(object owner, PropertyInfo propertyInfo, int index) { - return Build(mid, propertyInfo, index); + return Build(owner, propertyInfo, index); } - internal virtual DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal virtual DataField Build(object owner, PropertyInfo propertyInfo, int index) { return new DataField(Field, index, Size, PaddingChar, PaddingOrientation, HasPrefix); } @@ -57,10 +60,10 @@ public BooleanDataFieldDefinitionAttribute(int field, int revision) : base(field { Size = 1; } - internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { return DataField.Boolean(Field, Index, HasPrefix) - .Bind(mid, propertyInfo); + .Bind(owner, propertyInfo); } } public class StringDataFieldDefinitionAttribute : DataFieldDefinitionAttribute @@ -72,10 +75,10 @@ public StringDataFieldDefinitionAttribute(int field, int revision) : base(field, { } - internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { return DataField.String(Field, index, Size, PaddingOrientation, HasPrefix) - .Bind(mid, propertyInfo); + .Bind(owner, propertyInfo); } } public class Int32DataFieldDefinitionAttribute : DataFieldDefinitionAttribute @@ -90,10 +93,10 @@ public Int32DataFieldDefinitionAttribute(int field, int revision) : base(field, PaddingChar = '0'; PaddingOrientation = PaddingOrientation.LeftPadded; } - internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { return DataField.Int32(Field, index, Size, HasPrefix) - .Bind(mid, propertyInfo); + .Bind(owner, propertyInfo); } } public class Int64DataFieldDefinitionAttribute : DataFieldDefinitionAttribute @@ -108,10 +111,10 @@ public Int64DataFieldDefinitionAttribute(int field, int revision) : base(field, PaddingChar = '0'; PaddingOrientation = PaddingOrientation.LeftPadded; } - internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { return DataField.Int64(Field, index, Size, HasPrefix) - .Bind(mid, propertyInfo); + .Bind(owner, propertyInfo); } } public class DecimalDataFieldDefinitionAttribute : DataFieldDefinitionAttribute @@ -126,10 +129,10 @@ public DecimalDataFieldDefinitionAttribute(int field, int revision) : base(field PaddingChar = '0'; PaddingOrientation = PaddingOrientation.LeftPadded; } - internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { return DataField.Decimal(Field, index, Size, HasPrefix) - .Bind(mid, propertyInfo); + .Bind(owner, propertyInfo); } } @@ -150,10 +153,10 @@ public TruncatedDecimalDataFieldDefinitionAttribute(int field, int revision) : b PaddingOrientation = PaddingOrientation.LeftPadded; } - internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { return DataField.TruncatedDecimal(Field, index, Size, DecimalPoints, HasPrefix) - .Bind(mid, propertyInfo); + .Bind(owner, propertyInfo); } } @@ -167,10 +170,10 @@ public TimestampDataFieldDefinitionAttribute(int field, int revision) : base(fie { Size = 19; } - internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { return DataField.Timestamp(Field, index, HasPrefix) - .Bind(mid, propertyInfo); + .Bind(owner, propertyInfo); } } @@ -185,13 +188,13 @@ public VariableDataFieldCollectionDefinitionAttribute(int field, int revision) : } - internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { return new DataField>(Field, index, Size, HasPrefix) { DefaultConverter = OpenProtocolConvert.ToString, DefaultParser = VariableDataField.ParseAll - }.Bind(mid, propertyInfo); + }.Bind(owner, propertyInfo); } } @@ -211,13 +214,16 @@ public Int32CollectionDefinitionAttribute(int field, int revision) : base(field, PaddingOrientation = PaddingOrientation.LeftPadded; } - internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { + if (EachFieldSize <= 0) + throw new InvalidOperationException("EachFieldSize must be greater than 0 for Int32CollectionDefinitionAttribute."); + return new DataField>(Field, index, Size, PaddingChar, PaddingOrientation, HasPrefix) { DefaultConverter = PackList, DefaultParser = ParseList - }.Bind(mid, propertyInfo); + }.Bind(owner, propertyInfo); } private string PackList(char paddingChar, int size, PaddingOrientation orientation, List list) @@ -254,13 +260,13 @@ public EnumCollectionDefinitionAttribute(int field, int revision) : base(field, } - internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { return new DataField>(Field, index, Size, HasPrefix) { DefaultConverter = PackEnums, DefaultParser = ParseEnums - }.Bind(mid, propertyInfo); + }.Bind(owner, propertyInfo); } private static string PackEnums(char paddingChar, int size, PaddingOrientation orientation, List list) diff --git a/src/OpenProtocolInterpreter/ExtraData.cs b/src/OpenProtocolInterpreter/ExtraData.cs new file mode 100644 index 00000000..5e4312c4 --- /dev/null +++ b/src/OpenProtocolInterpreter/ExtraData.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace OpenProtocolInterpreter +{ + public abstract class ExtraData + { + private static readonly ConcurrentDictionary _metadataCache = new(); + private readonly Lazy>> _lazyFields; + + protected internal Dictionary> RevisionsByFields => _lazyFields.Value; + + public abstract int Mid { get; } + public int Revision { get; set; } + public int StandardizedRevision => Revision > 0 ? Revision : 1; + + public ExtraData() : this(1) + { + + } + + public ExtraData(int revision) + { + Revision = revision; + _lazyFields = new Lazy>>(() => new SafeAccessRevisionsFields(RegisterDatafields())); + } + + public ExtraData Parse(string extraData) + => Parse(extraData.AsSpan()); + + public virtual ExtraData Parse(ReadOnlySpan package) + { + if (!RevisionsByFields.Any()) + return this; + + for (int i = 1; i <= StandardizedRevision; i++) + { + foreach (var dataField in RevisionsByFields[i]) + { + ProcessDataField(dataField, package); + } + } + + return this; + } + + public virtual string Pack() + { + if (!RevisionsByFields.TryGetValue(StandardizedRevision, out var dataFields)) + { + return string.Empty; + } + + var builder = new StringBuilder(); + foreach (var dataField in dataFields) + { + if (dataField is IBackedPropertyDataField backedPropertyDataField) + backedPropertyDataField.SyncWithBackingPropertyIfBound(); + + if (dataField.HasPrefix) + { + builder.Append(dataField.Field.ToString("D2")); + } + + builder.Append(dataField.Value); + } + + return builder.ToString(); + } + + protected virtual void ProcessDataField(DataField dataField, ReadOnlySpan package) + { + try + { + var value = dataField.HasPrefix ? package.Slice(2 + dataField.Index, dataField.Size) : package.Slice(dataField.Index, dataField.Size); + dataField.SetValue(value); + } + catch (ArgumentOutOfRangeException) + { + //Ignore failure + } + } + + protected virtual Dictionary> RegisterDatafields() + { + var metadata = _metadataCache.GetOrAdd(GetType(), static type => + { + var result = new List(); + var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(x => x.CustomAttributes.Any(a => typeof(DataFieldDefinitionAttribute).IsAssignableFrom(a.AttributeType))); + + foreach (var prop in properties) + { + var attributes = prop.GetCustomAttributes(); + if (!attributes.Any()) + continue; + + foreach (var attr in attributes) + { + result.Add(new DataFieldMetadata(attr.Index, attr, prop)); + } + } + return result.ToArray(); + }); + + var fields = new Dictionary>(); + foreach (var m in metadata) + { + if (!fields.TryGetValue(m.Attribute.Revision, out var revisionFields)) + fields.Add(m.Attribute.Revision, revisionFields = new List()); + revisionFields.Add(m.CreateAndBind(this)); + } + return fields; + } + + protected DataField GetField(int revision, int field) + { + if (!RevisionsByFields.TryGetValue(revision, out var fields)) + { + return DataField.Default; + } + + return fields.FirstOrDefault(x => x.Field == field) ?? DataField.Default; + } + } +} diff --git a/src/OpenProtocolInterpreter/IMarkerInterfaces.cs b/src/OpenProtocolInterpreter/IMarkerInterfaces.cs index 08754fd8..cbac3bf7 100644 --- a/src/OpenProtocolInterpreter/IMarkerInterfaces.cs +++ b/src/OpenProtocolInterpreter/IMarkerInterfaces.cs @@ -69,10 +69,35 @@ public interface IUnsubscription } /// - /// Contract which a mid can be packed as extra data in a . + /// Contract which every message that has extra data implements. /// - public interface IExtraData + public interface IExtraDataContainer { - string PackAsExtraData(); + int WantedRevision { get; set; } + int ExtraDataLength { get; set; } + string ExtraData { get; set; } + } + + /// + /// Contract which a mid can be packed as extra data in , or . + /// + public interface IExtraDataSource + { + + } + + public interface IExtraDataRequest : IExtraDataSource + { + + } + + public interface IExtraDataSubscription : IExtraDataSource + { + + } + + public interface IExtraDataUnsubscription : IExtraDataSource + { + } } diff --git a/src/OpenProtocolInterpreter/IOInterface/DigitalInput.cs b/src/OpenProtocolInterpreter/IOInterface/DigitalInput.cs index 5aaf97b6..d7d87d00 100644 --- a/src/OpenProtocolInterpreter/IOInterface/DigitalInput.cs +++ b/src/OpenProtocolInterpreter/IOInterface/DigitalInput.cs @@ -70,13 +70,13 @@ public DigitalInputCollectionDefinitionAttribute(int field, int revision) : base } - internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { return new DataField>(Field, index, Size, HasPrefix) { DefaultConverter = PackDigitalInputs, DefaultParser = ParseDigitalInputs - }.Bind(mid, propertyInfo); + }.Bind(owner, propertyInfo); } private static string PackDigitalInputs(char paddingChar, int size, PaddingOrientation orientation, List digitalInputs) diff --git a/src/OpenProtocolInterpreter/IOInterface/IOInterfaceMessages.cs b/src/OpenProtocolInterpreter/IOInterface/IOInterfaceMessages.cs index 2e80eb0b..87311913 100644 --- a/src/OpenProtocolInterpreter/IOInterface/IOInterfaceMessages.cs +++ b/src/OpenProtocolInterpreter/IOInterface/IOInterfaceMessages.cs @@ -11,25 +11,25 @@ internal class IOInterfaceMessages : MessagesTemplate { public IOInterfaceMessages() : base() { - _templates = new Dictionary() + _templates = new Dictionary>() { - { Mid0200.MID, new MidCompiledInstance(typeof(Mid0200)) }, - { Mid0210.MID, new MidCompiledInstance(typeof(Mid0210)) }, - { Mid0211.MID, new MidCompiledInstance(typeof(Mid0211)) }, - { Mid0212.MID, new MidCompiledInstance(typeof(Mid0212)) }, - { Mid0213.MID, new MidCompiledInstance(typeof(Mid0213)) }, - { Mid0214.MID, new MidCompiledInstance(typeof(Mid0214)) }, - { Mid0215.MID, new MidCompiledInstance(typeof(Mid0215)) }, - { Mid0216.MID, new MidCompiledInstance(typeof(Mid0216)) }, - { Mid0217.MID, new MidCompiledInstance(typeof(Mid0217)) }, - { Mid0218.MID, new MidCompiledInstance(typeof(Mid0218)) }, - { Mid0219.MID, new MidCompiledInstance(typeof(Mid0219)) }, - { Mid0220.MID, new MidCompiledInstance(typeof(Mid0220)) }, - { Mid0221.MID, new MidCompiledInstance(typeof(Mid0221)) }, - { Mid0222.MID, new MidCompiledInstance(typeof(Mid0222)) }, - { Mid0223.MID, new MidCompiledInstance(typeof(Mid0223)) }, - { Mid0224.MID, new MidCompiledInstance(typeof(Mid0224)) }, - { Mid0225.MID, new MidCompiledInstance(typeof(Mid0225)) } + { Mid0200.MID, new CompiledInstance(typeof(Mid0200)) }, + { Mid0210.MID, new CompiledInstance(typeof(Mid0210)) }, + { Mid0211.MID, new CompiledInstance(typeof(Mid0211)) }, + { Mid0212.MID, new CompiledInstance(typeof(Mid0212)) }, + { Mid0213.MID, new CompiledInstance(typeof(Mid0213)) }, + { Mid0214.MID, new CompiledInstance(typeof(Mid0214)) }, + { Mid0215.MID, new CompiledInstance(typeof(Mid0215)) }, + { Mid0216.MID, new CompiledInstance(typeof(Mid0216)) }, + { Mid0217.MID, new CompiledInstance(typeof(Mid0217)) }, + { Mid0218.MID, new CompiledInstance(typeof(Mid0218)) }, + { Mid0219.MID, new CompiledInstance(typeof(Mid0219)) }, + { Mid0220.MID, new CompiledInstance(typeof(Mid0220)) }, + { Mid0221.MID, new CompiledInstance(typeof(Mid0221)) }, + { Mid0222.MID, new CompiledInstance(typeof(Mid0222)) }, + { Mid0223.MID, new CompiledInstance(typeof(Mid0223)) }, + { Mid0224.MID, new CompiledInstance(typeof(Mid0224)) }, + { Mid0225.MID, new CompiledInstance(typeof(Mid0225)) } }; } diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs index 63937f6f..c0c7c93b 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs @@ -28,18 +28,18 @@ public class Mid0215 : Mid, IIOInterface, IController public int IODeviceId { get; set; } [RelayCollectionDefinition(revision: 1, field: 2, Index = 24, Size = 4 * 8)] - [RelayCollectionDefinition(revision: 2, field: 2, Index = 28, Size = 4 * 8)] + [RelayCollectionDefinition(revision: 2, field: 3, Index = 28, Size = 4 * 8)] public List Relays { get; set; } [DigitalInputCollectionDefinition(revision: 1, field: 3, Index = 58, Size = 4 * 8)] - [DigitalInputCollectionDefinition(revision: 2, field: 3, Index = 0, Size = 4 * 8)] + [DigitalInputCollectionDefinition(revision: 2, field: 5, Index = 0, Size = 4 * 8)] public List DigitalInputs { get; set; } //At revision 2 number of relays/digital inputs comes before their lists - [Int32DataFieldDefinition(revision: 2, field: 4, Index = 24, Size = 2)] + [Int32DataFieldDefinition(revision: 2, field: 2, Index = 24, Size = 2)] public int NumberOfRelays { get; set; } - [Int32DataFieldDefinition(revision: 2, field: 5, Index = 0, Size = 2)] + [Int32DataFieldDefinition(revision: 2, field: 4, Index = 26, Size = 2)] public int NumberOfDigitalInputs { get; set; } public Mid0215() : this(DEFAULT_REVISION) @@ -79,19 +79,19 @@ public override string Pack() NumberOfRelays = Relays.Count; NumberOfDigitalInputs = DigitalInputs.Count; - var relaysField = GetField(nameof(Relays)); + var relaysField = GetField(revision: 2, field: 3); relaysField.Size = NumberOfRelays * 4; - var numberOfDigitalInputsField = GetField(nameof(NumberOfDigitalInputs)); + var numberOfDigitalInputsField = GetField(revision: 2, field: 4); numberOfDigitalInputsField.Index = relaysField.Index + relaysField.TotalSize; - var digitalInputsField = GetField(nameof(DigitalInputs)); + var digitalInputsField = GetField(revision: 2, field: 5); digitalInputsField.Index = numberOfDigitalInputsField.Index + numberOfDigitalInputsField.TotalSize; digitalInputsField.Size = NumberOfDigitalInputs * 4; var builder = new StringBuilder(BuildHeader()); - var fields = OrderedDataFieldsByRevision().ToList(); + var fields = RevisionsByFields[Header.StandardizedRevision].OrderBy(x => x.Field); builder.Append(base.Pack(fields)); return builder.ToString(); } @@ -108,56 +108,36 @@ protected override void ProcessDataFields(ReadOnlySpan package) EnsureEightRelaysAndDigitalInputs(); } - var fields = OrderedDataFieldsByRevision(); + var fields = RevisionsByFields[Header.StandardizedRevision].OrderBy(x => x.Field); var enumerator = fields.GetEnumerator(); - var index = 0; while (enumerator.MoveNext()) { - index++; var field = enumerator.Current; base.ProcessDataField(field, package); if (Header.StandardizedRevision <= 1) continue; - if (index == 2) + if (field.Field == 2) { - var relays = GetField(nameof(Relays)); + var relays = GetField(revision: 2, field: 3); relays.Index = field.Index + field.TotalSize; relays.Size = NumberOfRelays * 4; } - else if (index == 3) + else if (field.Field == 3) { - var numberOfDigitalInputs = GetField(nameof(NumberOfDigitalInputs)); + var numberOfDigitalInputs = GetField(revision: 2, field: 4); numberOfDigitalInputs.Index = field.Index + field.TotalSize; } - else if (index == 4) + else if (field.Field == 4) { - var digitalInputs = GetField(nameof(DigitalInputs)); + var digitalInputs = GetField(revision: 2, field: 5); digitalInputs.Index = field.Index + field.TotalSize; digitalInputs.Size = NumberOfDigitalInputs * 4; } } } - private IEnumerable OrderedDataFieldsByRevision() - { - if (Header.StandardizedRevision == 1) - { - yield return GetField(nameof(IODeviceId)); - yield return GetField(nameof(Relays)); - yield return GetField(nameof(DigitalInputs)); - } - else - { - yield return GetField(nameof(IODeviceId)); - yield return GetField(nameof(NumberOfRelays)); - yield return GetField(nameof(Relays)); - yield return GetField(nameof(NumberOfDigitalInputs)); - yield return GetField(nameof(DigitalInputs)); - } - } - private void EnsureEightRelaysAndDigitalInputs() { if (Relays.Count > 8 || DigitalInputs.Count > 8) diff --git a/src/OpenProtocolInterpreter/IOInterface/Relay.cs b/src/OpenProtocolInterpreter/IOInterface/Relay.cs index e48b220d..b2315f98 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Relay.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Relay.cs @@ -70,13 +70,13 @@ public RelayCollectionDefinitionAttribute(int field, int revision) : base(field, } - internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { return new DataField>(Field, index, Size, HasPrefix) { DefaultConverter = PackRelays, DefaultParser = ParseRelays - }.Bind(mid, propertyInfo); + }.Bind(owner, propertyInfo); } private static string PackRelays(char paddingChar, int size, PaddingOrientation orientation, List relays) diff --git a/src/OpenProtocolInterpreter/Job/Advanced/AdvancedJob.cs b/src/OpenProtocolInterpreter/Job/Advanced/AdvancedJob.cs index 8c50fcc9..3615c4be 100644 --- a/src/OpenProtocolInterpreter/Job/Advanced/AdvancedJob.cs +++ b/src/OpenProtocolInterpreter/Job/Advanced/AdvancedJob.cs @@ -157,13 +157,13 @@ public AdvancedJobCollectionDefinitionAttribute(int field, int revision) : base( } - internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { return new DataField>(Field, index, Size, HasPrefix) { DefaultConverter = PackAdvancedJobs, DefaultParser = ParseAdvancedJobs - }.Bind(mid, propertyInfo); + }.Bind(owner, propertyInfo); } private string PackAdvancedJobs(char paddingChar, int size, PaddingOrientation orientation, List advancedJobs) diff --git a/src/OpenProtocolInterpreter/Job/Advanced/AdvancedJobMessages.cs b/src/OpenProtocolInterpreter/Job/Advanced/AdvancedJobMessages.cs index 512eae44..e121ea22 100644 --- a/src/OpenProtocolInterpreter/Job/Advanced/AdvancedJobMessages.cs +++ b/src/OpenProtocolInterpreter/Job/Advanced/AdvancedJobMessages.cs @@ -11,23 +11,23 @@ internal class AdvancedJobMessages : MessagesTemplate { public AdvancedJobMessages() : base() { - _templates = new Dictionary() + _templates = new Dictionary>() { - { Mid0120.MID, new MidCompiledInstance(typeof(Mid0120)) }, - { Mid0121.MID, new MidCompiledInstance(typeof(Mid0121)) }, - { Mid0122.MID, new MidCompiledInstance(typeof(Mid0122)) }, - { Mid0123.MID, new MidCompiledInstance(typeof(Mid0123)) }, - { Mid0124.MID, new MidCompiledInstance(typeof(Mid0124)) }, - { Mid0125.MID, new MidCompiledInstance(typeof(Mid0125)) }, - { Mid0126.MID, new MidCompiledInstance(typeof(Mid0126)) }, - { Mid0127.MID, new MidCompiledInstance(typeof(Mid0127)) }, - { Mid0128.MID, new MidCompiledInstance(typeof(Mid0128)) }, - { Mid0129.MID, new MidCompiledInstance(typeof(Mid0129)) }, - { Mid0130.MID, new MidCompiledInstance(typeof(Mid0130)) }, - { Mid0131.MID, new MidCompiledInstance(typeof(Mid0131)) }, - { Mid0132.MID, new MidCompiledInstance(typeof(Mid0132)) }, - { Mid0133.MID, new MidCompiledInstance(typeof(Mid0133)) }, - { Mid0140.MID, new MidCompiledInstance(typeof(Mid0140)) } + { Mid0120.MID, new CompiledInstance(typeof(Mid0120)) }, + { Mid0121.MID, new CompiledInstance(typeof(Mid0121)) }, + { Mid0122.MID, new CompiledInstance(typeof(Mid0122)) }, + { Mid0123.MID, new CompiledInstance(typeof(Mid0123)) }, + { Mid0124.MID, new CompiledInstance(typeof(Mid0124)) }, + { Mid0125.MID, new CompiledInstance(typeof(Mid0125)) }, + { Mid0126.MID, new CompiledInstance(typeof(Mid0126)) }, + { Mid0127.MID, new CompiledInstance(typeof(Mid0127)) }, + { Mid0128.MID, new CompiledInstance(typeof(Mid0128)) }, + { Mid0129.MID, new CompiledInstance(typeof(Mid0129)) }, + { Mid0130.MID, new CompiledInstance(typeof(Mid0130)) }, + { Mid0131.MID, new CompiledInstance(typeof(Mid0131)) }, + { Mid0132.MID, new CompiledInstance(typeof(Mid0132)) }, + { Mid0133.MID, new CompiledInstance(typeof(Mid0133)) }, + { Mid0140.MID, new CompiledInstance(typeof(Mid0140)) } }; } diff --git a/src/OpenProtocolInterpreter/Job/JobMessages.cs b/src/OpenProtocolInterpreter/Job/JobMessages.cs index 1c7f1e10..e3012e82 100644 --- a/src/OpenProtocolInterpreter/Job/JobMessages.cs +++ b/src/OpenProtocolInterpreter/Job/JobMessages.cs @@ -11,18 +11,18 @@ internal class JobMessages : MessagesTemplate { public JobMessages() : base() { - _templates = new Dictionary() + _templates = new Dictionary>() { - { Mid0030.MID, new MidCompiledInstance(typeof(Mid0030)) }, - { Mid0031.MID, new MidCompiledInstance(typeof(Mid0031)) }, - { Mid0032.MID, new MidCompiledInstance(typeof(Mid0032)) }, - { Mid0033.MID, new MidCompiledInstance(typeof(Mid0033)) }, - { Mid0034.MID, new MidCompiledInstance(typeof(Mid0034)) }, - { Mid0035.MID, new MidCompiledInstance(typeof(Mid0035)) }, - { Mid0036.MID, new MidCompiledInstance(typeof(Mid0036)) }, - { Mid0037.MID, new MidCompiledInstance(typeof(Mid0037)) }, - { Mid0038.MID, new MidCompiledInstance(typeof(Mid0038)) }, - { Mid0039.MID, new MidCompiledInstance(typeof(Mid0039)) } + { Mid0030.MID, new CompiledInstance(typeof(Mid0030)) }, + { Mid0031.MID, new CompiledInstance(typeof(Mid0031)) }, + { Mid0032.MID, new CompiledInstance(typeof(Mid0032)) }, + { Mid0033.MID, new CompiledInstance(typeof(Mid0033)) }, + { Mid0034.MID, new CompiledInstance(typeof(Mid0034)) }, + { Mid0035.MID, new CompiledInstance(typeof(Mid0035)) }, + { Mid0036.MID, new CompiledInstance(typeof(Mid0036)) }, + { Mid0037.MID, new CompiledInstance(typeof(Mid0037)) }, + { Mid0038.MID, new CompiledInstance(typeof(Mid0038)) }, + { Mid0039.MID, new CompiledInstance(typeof(Mid0039)) } }; } diff --git a/src/OpenProtocolInterpreter/Job/ParameterSet.cs b/src/OpenProtocolInterpreter/Job/ParameterSet.cs index 2f543880..551ef71e 100644 --- a/src/OpenProtocolInterpreter/Job/ParameterSet.cs +++ b/src/OpenProtocolInterpreter/Job/ParameterSet.cs @@ -145,13 +145,13 @@ public ParameterSetCollectionDefinitionAttribute(int field, int revision) : base } - internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { return new DataField>(Field, index, Size, HasPrefix) { DefaultConverter = PackParameterSets, DefaultParser = ParseParameterSets - }.Bind(mid, propertyInfo); + }.Bind(owner, propertyInfo); } private string PackParameterSets(char paddingChar, int size, PaddingOrientation orientation, List parameterSets) diff --git a/src/OpenProtocolInterpreter/LinkCommunication/LinkCommunicationMessages.cs b/src/OpenProtocolInterpreter/LinkCommunication/LinkCommunicationMessages.cs index 3fc4f96f..8f7a808d 100644 --- a/src/OpenProtocolInterpreter/LinkCommunication/LinkCommunicationMessages.cs +++ b/src/OpenProtocolInterpreter/LinkCommunication/LinkCommunicationMessages.cs @@ -8,10 +8,10 @@ internal class LinkCommunicationMessages : MessagesTemplate { public LinkCommunicationMessages() : base() { - _templates = new Dictionary() + _templates = new Dictionary>() { - { Mid9997.MID, new MidCompiledInstance(typeof(Mid9997)) }, - { Mid9998.MID, new MidCompiledInstance(typeof(Mid9998)) } + { Mid9997.MID, new CompiledInstance(typeof(Mid9997)) }, + { Mid9998.MID, new CompiledInstance(typeof(Mid9998)) } }; } diff --git a/src/OpenProtocolInterpreter/MID.cs b/src/OpenProtocolInterpreter/Mid.cs similarity index 91% rename from src/OpenProtocolInterpreter/MID.cs rename to src/OpenProtocolInterpreter/Mid.cs index 71468796..c5bac3f2 100644 --- a/src/OpenProtocolInterpreter/MID.cs +++ b/src/OpenProtocolInterpreter/Mid.cs @@ -17,7 +17,7 @@ public abstract class Mid private readonly Lazy>> _lazyFields; protected const int DEFAULT_REVISION = 1; - protected Dictionary> RevisionsByFields => _lazyFields.Value; + protected internal Dictionary> RevisionsByFields => _lazyFields.Value; /// /// Header of the MID message containing standardized fields. @@ -59,6 +59,11 @@ protected virtual string BuildHeader() return Header.ToString(); } + /// + /// Packs the MID message into a string representation, including the header and all data fields for the current revision. + /// If the MID has no data fields, only the header will be packed. + /// + /// The string representing the packed MID message. public virtual string Pack() { var header = BuildHeader(); @@ -75,6 +80,12 @@ public virtual string Pack() return builder.ToString(); } + /// + /// Packs the MID message into a byte array representation, including the header and all data fields for the current revision. + /// If the MID has no data fields, only the header will be packed. + /// The byte array is encoded in ASCII. + /// + /// The byte array representing the packed MID message in ASCII encoding. public virtual byte[] PackBytes() => Encoding.ASCII.GetBytes(Pack()); protected virtual string Pack(int revision) @@ -87,7 +98,7 @@ protected virtual string Pack(int revision) return Pack(dataFields); } - protected virtual string Pack(IEnumerable dataFields) + protected internal virtual string Pack(IEnumerable dataFields) { var builder = new StringBuilder(); foreach (var dataField in dataFields) @@ -136,7 +147,6 @@ protected virtual Dictionary> RegisterDatafields() }); var fields = new Dictionary>(); - var currentRevision = Header.Revision; foreach (var m in metadata) { if (!fields.TryGetValue(m.Attribute.Revision, out var revisionFields)) @@ -188,18 +198,6 @@ public virtual Mid Parse(byte[] package) return Parse(pack); } - protected virtual void ProcessDataFields(string package) - { - if (!RevisionsByFields.Any()) - return; - - int revision = Header.Revision > 0 ? Header.Revision : 1; - for (int i = 1; i <= revision; i++) - { - ProcessDataFields(i, package); - } - } - protected virtual void ProcessDataFields(ReadOnlySpan package) { if (!RevisionsByFields.Any()) @@ -223,33 +221,15 @@ protected virtual void ProcessDataFields(int revision, ReadOnlySpan packag } } - protected virtual void ProcessDataFields(IEnumerable dataFields, string package) - => ProcessDataFields(dataFields, package.AsSpan()); - protected virtual void ProcessDataFields(IEnumerable dataFields, ReadOnlySpan package) { foreach (var dataField in dataFields) ProcessDataField(dataField, package); } - protected virtual void ProcessDataField(DataField dataField, string package) - => dataField.SetValue(GetValue(dataField, package)); - protected virtual void ProcessDataField(DataField dataField, ReadOnlySpan package) => dataField.SetValue(GetValue(dataField, package)); - protected string GetValue(DataField field, string package) - { - try - { - return field.HasPrefix ? package.Substring(2 + field.Index, field.Size) : package.Substring(field.Index, field.Size); - } - catch (ArgumentOutOfRangeException) - { - return null; - } - } - protected ReadOnlySpan GetValue(DataField field, ReadOnlySpan package) { try diff --git a/src/OpenProtocolInterpreter/MidExtensions.cs b/src/OpenProtocolInterpreter/MidExtensions.cs index 82f1bce6..c939f547 100644 --- a/src/OpenProtocolInterpreter/MidExtensions.cs +++ b/src/OpenProtocolInterpreter/MidExtensions.cs @@ -1,5 +1,6 @@ using OpenProtocolInterpreter.Communication; using System; +using System.Collections.Generic; using System.Linq; namespace OpenProtocolInterpreter @@ -50,7 +51,7 @@ public static byte[] PackBytesWithNul(this Mid mid) public static TAckMid GetAcknowledge(this IAcknowledgeable instance) where TAckMid : Mid, IAcknowledge, new() { var mid = new TAckMid(); - if(instance is Mid acknowledgeableMid) + if (instance is Mid acknowledgeableMid) mid.Header.Revision = acknowledgeableMid.Header.Revision; return mid; @@ -103,7 +104,7 @@ public static Mid0004 GetDeclineCommand(this TDeclinedMid mid, Err } /// - /// Assert that error code is a possible error for the current failed mid and generates a Communication Negative Acknowledge mid () + /// Assert that error code is a possible error for the current failed mid and generates a Communication Negative Acknowledge mid () /// instance for the failed mid and with the informed error code. /// /// instance and implementer diff --git a/src/OpenProtocolInterpreter/MidInterpreter.cs b/src/OpenProtocolInterpreter/MidInterpreter.cs index 341546fb..9ab7bc51 100644 --- a/src/OpenProtocolInterpreter/MidInterpreter.cs +++ b/src/OpenProtocolInterpreter/MidInterpreter.cs @@ -1,13 +1,15 @@ -using OpenProtocolInterpreter.Messages; +using OpenProtocolInterpreter.Communication; +using OpenProtocolInterpreter.Messages; using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Text; namespace OpenProtocolInterpreter { /// - /// Responsible for building and parsing any incoming Mid. + /// Responsible for building and parsing any incoming Mid. /// Message templates initialization must be done with methods. /// public class MidInterpreter @@ -40,6 +42,54 @@ public Mid Parse(string package) return template.ProcessPackage(mid, package); } + public TExtraData ParseExtraData(Mid0006 mid) where TExtraData : ExtraData, IExtraDataRequest + { + var instance = ParseExtraData(mid); + if (instance is TExtraData extraData) + return extraData; + + throw new InvalidCastException($"Extra data cannot be casted to {typeof(TExtraData).Name}"); + } + + public TExtraData ParseExtraData(Mid0008 mid) where TExtraData : ExtraData, IExtraDataSubscription + { + var instance = ParseExtraData(mid); + if (instance is TExtraData extraData) + return extraData; + + throw new InvalidCastException($"Extra data cannot be casted to {typeof(TExtraData).Name}"); + } + + public TExtraData ParseExtraData(Mid0009 mid) where TExtraData : ExtraData, IExtraDataUnsubscription + { + var expectedExtraData = ParseExtraData(mid); + if (expectedExtraData is TExtraData extraData) + return extraData; + + throw new InvalidCastException($"Extra data cannot be casted to {typeof(TExtraData).Name}"); + } + + public TExtraData ParseExtraData(TMid mid) where TMid : Mid, IExtraDataContainer + where TExtraData : ExtraData + { + var expectedExtraData = ParseExtraData(mid); + if (expectedExtraData is TExtraData extraData) + return extraData; + + throw new InvalidCastException($"Extra data cannot be casted to {typeof(TExtraData).Name}"); + } + + public ExtraData ParseExtraData(TMid mid) where TMid : Mid, IExtraDataContainer + { + var expectedMid = GetMidFromExtraDataMid(mid); + var template = GetMessageTemplate(expectedMid); + var instance = template.GetExtraDataInstance(expectedMid, GetExtraDataKind(mid)); + + var extraDataInstance = instance.CompiledConstructor(); + extraDataInstance.Revision = mid.WantedRevision; + return extraDataInstance.Parse(mid.ExtraData); + } + public Mid Parse(byte[] package) { #if NETSTANDARD2_0 @@ -60,8 +110,8 @@ public Mid Parse(byte[] package) public ExpectedMid Parse(string package) where ExpectedMid : Mid { Mid mid = Parse(package); - if (mid.GetType().Equals(typeof(ExpectedMid))) - return (ExpectedMid)mid; + if (mid is ExpectedMid expectedMid) + return expectedMid; throw new InvalidCastException($"Package is Mid {mid.GetType().Name}, cannot be casted to {typeof(ExpectedMid).Name}"); } @@ -155,5 +205,27 @@ private static Mid TryParseStandaloneMid(int mid) _ => default, }; } + + private static int GetMidFromExtraDataMid(TMid mid) where TMid : Mid, IExtraDataContainer + { + switch (mid) + { + case Communication.Mid0006 m: return m.RequestedMid; + case Communication.Mid0008 m: return m.SubscriptionMid; + case Communication.Mid0009 m: return m.UnsubscriptionMid; + default: throw new NotImplementedException($"Could not get the MID from {mid.GetType().Name} as extra data, please implement it"); + } + } + + private static Type GetExtraDataKind(TMid mid) where TMid : Mid, IExtraDataContainer + { + switch (mid) + { + case Communication.Mid0006 _: return typeof(IExtraDataRequest); + case Communication.Mid0008 _: return typeof(IExtraDataSubscription); + case Communication.Mid0009 _: return typeof(IExtraDataUnsubscription); + default: throw new NotImplementedException($"Could not get the extra data kind from {mid.GetType().Name}, please implement it"); + } + } } } diff --git a/src/OpenProtocolInterpreter/MotorTuning/MotorTuningMessages.cs b/src/OpenProtocolInterpreter/MotorTuning/MotorTuningMessages.cs index fa22f7b3..7df8441b 100644 --- a/src/OpenProtocolInterpreter/MotorTuning/MotorTuningMessages.cs +++ b/src/OpenProtocolInterpreter/MotorTuning/MotorTuningMessages.cs @@ -11,13 +11,13 @@ internal class MotorTuningMessages : MessagesTemplate { public MotorTuningMessages() : base() { - _templates = new Dictionary() + _templates = new Dictionary>() { - { Mid0500.MID, new MidCompiledInstance(typeof(Mid0500)) }, - { Mid0501.MID, new MidCompiledInstance(typeof(Mid0501)) }, - { Mid0502.MID, new MidCompiledInstance(typeof(Mid0502)) }, - { Mid0503.MID, new MidCompiledInstance(typeof(Mid0503)) }, - { Mid0504.MID, new MidCompiledInstance(typeof(Mid0504)) } + { Mid0500.MID, new CompiledInstance(typeof(Mid0500)) }, + { Mid0501.MID, new CompiledInstance(typeof(Mid0501)) }, + { Mid0502.MID, new CompiledInstance(typeof(Mid0502)) }, + { Mid0503.MID, new CompiledInstance(typeof(Mid0503)) }, + { Mid0504.MID, new CompiledInstance(typeof(Mid0504)) } }; } diff --git a/src/OpenProtocolInterpreter/MultiSpindle/MultiSpindleMessages.cs b/src/OpenProtocolInterpreter/MultiSpindle/MultiSpindleMessages.cs index a99f4f58..419083f7 100644 --- a/src/OpenProtocolInterpreter/MultiSpindle/MultiSpindleMessages.cs +++ b/src/OpenProtocolInterpreter/MultiSpindle/MultiSpindleMessages.cs @@ -11,17 +11,17 @@ internal class MultiSpindleMessages : MessagesTemplate { public MultiSpindleMessages() : base() { - _templates = new Dictionary() + _templates = new Dictionary>() { - { Mid0090.MID, new MidCompiledInstance(typeof(Mid0090)) }, - { Mid0091.MID, new MidCompiledInstance(typeof(Mid0091)) }, - { Mid0092.MID, new MidCompiledInstance(typeof(Mid0092)) }, - { Mid0093.MID, new MidCompiledInstance(typeof(Mid0093)) }, - { Mid0100.MID, new MidCompiledInstance(typeof(Mid0100)) }, - { Mid0101.MID, new MidCompiledInstance(typeof(Mid0101)) }, - { Mid0102.MID, new MidCompiledInstance(typeof(Mid0102)) }, - { Mid0103.MID, new MidCompiledInstance(typeof(Mid0103)) }, - { Mid0104.MID, new MidCompiledInstance(typeof(Mid0104)) }, + { Mid0090.MID, new CompiledInstance(typeof(Mid0090)) }, + { Mid0091.MID, new CompiledInstance(typeof(Mid0091)) }, + { Mid0092.MID, new CompiledInstance(typeof(Mid0092)) }, + { Mid0093.MID, new CompiledInstance(typeof(Mid0093)) }, + { Mid0100.MID, new CompiledInstance(typeof(Mid0100)) }, + { Mid0101.MID, new CompiledInstance(typeof(Mid0101)) }, + { Mid0102.MID, new CompiledInstance(typeof(Mid0102)) }, + { Mid0103.MID, new CompiledInstance(typeof(Mid0103)) }, + { Mid0104.MID, new CompiledInstance(typeof(Mid0104)) }, }; } diff --git a/src/OpenProtocolInterpreter/MultiSpindle/SpindleOrPressStatus.cs b/src/OpenProtocolInterpreter/MultiSpindle/SpindleOrPressStatus.cs index bf8dbd21..ec8232b6 100644 --- a/src/OpenProtocolInterpreter/MultiSpindle/SpindleOrPressStatus.cs +++ b/src/OpenProtocolInterpreter/MultiSpindle/SpindleOrPressStatus.cs @@ -76,13 +76,13 @@ public SpindleOrPressStatusCollectionDefinitionAttribute(int field, int revision } - internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { return new DataField>(Field, index, Size, HasPrefix) { DefaultConverter = Pack, DefaultParser = SpindleOrPressStatus.ParseAll - }.Bind(mid, propertyInfo); + }.Bind(owner, propertyInfo); } private static string Pack(char paddingChar, int size, PaddingOrientation orientation, List spindleOrPressStatus) diff --git a/src/OpenProtocolInterpreter/MultiSpindle/SpindleStatus.cs b/src/OpenProtocolInterpreter/MultiSpindle/SpindleStatus.cs index 88d7a373..931a5c94 100644 --- a/src/OpenProtocolInterpreter/MultiSpindle/SpindleStatus.cs +++ b/src/OpenProtocolInterpreter/MultiSpindle/SpindleStatus.cs @@ -64,13 +64,13 @@ public SpindleStatusCollectionDefinitionAttribute(int field, int revision) : bas } - internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { return new DataField>(Field, index, Size, HasPrefix) { DefaultConverter = Pack, DefaultParser = SpindleStatus.ParseAll - }.Bind(mid, propertyInfo); + }.Bind(owner, propertyInfo); } private static string Pack(char paddingChar, int size, PaddingOrientation orientation, List spindleStatus) diff --git a/src/OpenProtocolInterpreter/MultipleIdentifiers/IdentifierStatus.cs b/src/OpenProtocolInterpreter/MultipleIdentifiers/IdentifierStatus.cs index 38e06ef9..a9713d84 100644 --- a/src/OpenProtocolInterpreter/MultipleIdentifiers/IdentifierStatus.cs +++ b/src/OpenProtocolInterpreter/MultipleIdentifiers/IdentifierStatus.cs @@ -50,13 +50,13 @@ public IdentifierStatusDefinitionAttribute(int field, int revision) : base(field } - internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { return new DataField(Field, index, Size, HasPrefix) { DefaultConverter = Pack, DefaultParser = IdentifierStatus.Parse - }.Bind(mid, propertyInfo); + }.Bind(owner, propertyInfo); } private static string Pack(char paddingChar, int size, PaddingOrientation orientation, IdentifierStatus identifierStatus) diff --git a/src/OpenProtocolInterpreter/MultipleIdentifiers/MultipleIdentifierMessages.cs b/src/OpenProtocolInterpreter/MultipleIdentifiers/MultipleIdentifierMessages.cs index 2e977eb2..fb430c51 100644 --- a/src/OpenProtocolInterpreter/MultipleIdentifiers/MultipleIdentifierMessages.cs +++ b/src/OpenProtocolInterpreter/MultipleIdentifiers/MultipleIdentifierMessages.cs @@ -11,16 +11,16 @@ internal class MultipleIdentifierMessages : MessagesTemplate { public MultipleIdentifierMessages() : base() { - _templates = new Dictionary() + _templates = new Dictionary>() { - { Mid0150.MID, new MidCompiledInstance(typeof(Mid0150)) }, - { Mid0151.MID, new MidCompiledInstance(typeof(Mid0151)) }, - { Mid0152.MID, new MidCompiledInstance(typeof(Mid0152)) }, - { Mid0153.MID, new MidCompiledInstance(typeof(Mid0153)) }, - { Mid0154.MID, new MidCompiledInstance(typeof(Mid0154)) }, - { Mid0155.MID, new MidCompiledInstance(typeof(Mid0155)) }, - { Mid0156.MID, new MidCompiledInstance(typeof(Mid0156)) }, - { Mid0157.MID, new MidCompiledInstance(typeof(Mid0157)) } + { Mid0150.MID, new CompiledInstance(typeof(Mid0150)) }, + { Mid0151.MID, new CompiledInstance(typeof(Mid0151)) }, + { Mid0152.MID, new CompiledInstance(typeof(Mid0152)) }, + { Mid0153.MID, new CompiledInstance(typeof(Mid0153)) }, + { Mid0154.MID, new CompiledInstance(typeof(Mid0154)) }, + { Mid0155.MID, new CompiledInstance(typeof(Mid0155)) }, + { Mid0156.MID, new CompiledInstance(typeof(Mid0156)) }, + { Mid0157.MID, new CompiledInstance(typeof(Mid0157)) } }; } diff --git a/src/OpenProtocolInterpreter/OpenProtocolCommandsDisabled/OpenProtocolCommandsDisabledMessages.cs b/src/OpenProtocolInterpreter/OpenProtocolCommandsDisabled/OpenProtocolCommandsDisabledMessages.cs index 9ae52513..aa936f87 100644 --- a/src/OpenProtocolInterpreter/OpenProtocolCommandsDisabled/OpenProtocolCommandsDisabledMessages.cs +++ b/src/OpenProtocolInterpreter/OpenProtocolCommandsDisabled/OpenProtocolCommandsDisabledMessages.cs @@ -11,12 +11,12 @@ internal class OpenProtocolCommandsDisabledMessages : MessagesTemplate { public OpenProtocolCommandsDisabledMessages() : base() { - _templates = new Dictionary() + _templates = new Dictionary>() { - { Mid0420.MID, new MidCompiledInstance(typeof(Mid0420)) }, - { Mid0421.MID, new MidCompiledInstance(typeof(Mid0421)) }, - { Mid0422.MID, new MidCompiledInstance(typeof(Mid0422)) }, - { Mid0423.MID, new MidCompiledInstance(typeof(Mid0423)) } + { Mid0420.MID, new CompiledInstance(typeof(Mid0420)) }, + { Mid0421.MID, new CompiledInstance(typeof(Mid0421)) }, + { Mid0422.MID, new CompiledInstance(typeof(Mid0422)) }, + { Mid0423.MID, new CompiledInstance(typeof(Mid0423)) } }; } diff --git a/src/OpenProtocolInterpreter/PLCUserData/PLCUserDataMessages.cs b/src/OpenProtocolInterpreter/PLCUserData/PLCUserDataMessages.cs index d8beafae..293e52b2 100644 --- a/src/OpenProtocolInterpreter/PLCUserData/PLCUserDataMessages.cs +++ b/src/OpenProtocolInterpreter/PLCUserData/PLCUserDataMessages.cs @@ -11,14 +11,14 @@ internal class PLCUserDataMessages : MessagesTemplate { public PLCUserDataMessages() : base() { - _templates = new Dictionary() + _templates = new Dictionary>() { - { Mid0240.MID, new MidCompiledInstance(typeof(Mid0240)) }, - { Mid0241.MID, new MidCompiledInstance(typeof(Mid0241)) }, - { Mid0242.MID, new MidCompiledInstance(typeof(Mid0242)) }, - { Mid0243.MID, new MidCompiledInstance(typeof(Mid0243)) }, - { Mid0244.MID, new MidCompiledInstance(typeof(Mid0244)) }, - { Mid0245.MID, new MidCompiledInstance(typeof(Mid0245)) } + { Mid0240.MID, new CompiledInstance(typeof(Mid0240)) }, + { Mid0241.MID, new CompiledInstance(typeof(Mid0241)) }, + { Mid0242.MID, new CompiledInstance(typeof(Mid0242)) }, + { Mid0243.MID, new CompiledInstance(typeof(Mid0243)) }, + { Mid0244.MID, new CompiledInstance(typeof(Mid0244)) }, + { Mid0245.MID, new CompiledInstance(typeof(Mid0245)) } }; } diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid2501.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid2501.cs index acd8bc3c..681039e6 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid2501.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid2501.cs @@ -1,13 +1,48 @@ namespace OpenProtocolInterpreter.ParameterSet { + public class Mid2501ExtraDataRequest : ExtraData, IExtraDataRequest, IExtraDataSubscription, IExtraDataUnsubscription + { + public override int Mid => Mid2501.MID; + + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 0, Size = 4, HasPrefix = false)] + public int ProgramId { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 4, Size = 3, HasPrefix = false)] + public NodeType NodeType { get; set; } + + public Mid2501ExtraDataRequest() + { + + } + + public Mid2501ExtraDataRequest(int revision) : base(revision) + { + + } + } + /// /// MID: Tightening Program Message Upload - /// Description: + /// Description: /// Reset the subscription for Lock at batch done. /// Message sent by: Integrator /// Answer: MID 0005 Command accepted or MID 0004 Command error /// - class Mid2501 + public class Mid2501 : Mid, IParameterSet, IIntegrator { + public const int MID = 2501; + + public Mid2501() : this(new Header() + { + Mid = MID, + Revision = DEFAULT_REVISION + }) + { + + } + + public Mid2501(Header header) : base(header) + { + } } } diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid2502.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid2502.cs new file mode 100644 index 00000000..d311c2ba --- /dev/null +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid2502.cs @@ -0,0 +1,23 @@ +namespace OpenProtocolInterpreter.ParameterSet +{ + public class Mid2502 : Mid, IController, IParameterSet + { + public const int MID = 2502; + + public Mid2502() : this(DEFAULT_REVISION) + { + } + + public Mid2502(Header header) : base(header) + { + } + + public Mid2502(int revision) : this(new Header() + { + Mid = MID, + Revision = revision + }) + { + } + } +} diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid2503.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid2503.cs new file mode 100644 index 00000000..aee6d8f7 --- /dev/null +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid2503.cs @@ -0,0 +1,40 @@ +using System; + +namespace OpenProtocolInterpreter.ParameterSet +{ + public class Mid2503 : Mid, IIntegrator, IParameterSet + { + public const int MID = 2503; + + [StringDataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 0, HasPrefix = false)] + public string Password { get; set; } + + public Mid2503() : this(DEFAULT_REVISION) + { + } + + public Mid2503(Header header) : base(header) + { + } + + public Mid2503(int revision) : this(new Header() + { + Mid = MID, + Revision = revision + }) + { + } + + public override string Pack() + { + GetField(nameof(Password)).Size = Password?.Length ?? 0; + return base.Pack(); + } + + public override Mid Parse(ReadOnlySpan package) + { + GetField(nameof(Password)).Size = Header.Length - Header.DefaultSize; + return base.Parse(package); + } + } +} diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid2504.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid2504.cs index 7e3eba83..76e1d147 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid2504.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid2504.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace OpenProtocolInterpreter.ParameterSet { @@ -11,7 +10,7 @@ namespace OpenProtocolInterpreter.ParameterSet /// Command error, Dynamic Job cannot be created, non-existing pset /// /// - public class Mid2504 : Mid, IParameterSet, IIntegrator, IAcceptableCommand, IDeclinableCommand + public class Mid2504 : Mid, IParameterSet, IIntegrator, IAcceptableCommand, IDeclinableCommand, IExtraDataRequest { public const int MID = 2504; diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid2505.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid2505.cs index a4af76f3..3c4049ce 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid2505.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid2505.cs @@ -17,7 +17,7 @@ namespace OpenProtocolInterpreter.ParameterSet /// The message can substitute Pset selection, Set Identifier, Reset All Identifier, Reset Latest Identifier, Set Batch Size, Disable Tool and Enable Tool. /// /// - public class Mid2505 : Mid, IParameterSet, IIntegrator, IAcceptableCommand, IDeclinableCommand + public class Mid2505 : Mid, IParameterSet, IIntegrator, IAcceptableCommand, IDeclinableCommand, IExtraDataRequest { public const int MID = 2505; diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid2506.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid2506.cs index 64b47277..de95dba6 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid2506.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid2506.cs @@ -14,7 +14,7 @@ namespace OpenProtocolInterpreter.ParameterSet /// Note: If a running program is included in MID 2506 deletion the program shall finish before deletion. /// Note: Deleting programs included in other nodes may give unwanted behaviour. It will behave identical to a manual delete of the program. /// - public class Mid2506 : Mid, IParameterSet, IIntegrator, IAcceptableCommand, IDeclinableCommand + public class Mid2506 : Mid, IParameterSet, IIntegrator, IAcceptableCommand, IDeclinableCommand, IExtraDataRequest { public const int MID = 2506; diff --git a/src/OpenProtocolInterpreter/ParameterSet/ParameterSetMessages.cs b/src/OpenProtocolInterpreter/ParameterSet/ParameterSetMessages.cs index b5a16f0d..d0c40f7a 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/ParameterSetMessages.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/ParameterSetMessages.cs @@ -11,26 +11,29 @@ internal class ParameterSetMessages : MessagesTemplate { public ParameterSetMessages() : base() { - _templates = new Dictionary() + _templates = new Dictionary>() { - { Mid0010.MID, new MidCompiledInstance(typeof(Mid0010)) }, - { Mid0011.MID, new MidCompiledInstance(typeof(Mid0011)) }, - { Mid0012.MID, new MidCompiledInstance(typeof(Mid0012)) }, - { Mid0013.MID, new MidCompiledInstance(typeof(Mid0013)) }, - { Mid0014.MID, new MidCompiledInstance(typeof(Mid0014)) }, - { Mid0015.MID, new MidCompiledInstance(typeof(Mid0015)) }, - { Mid0016.MID, new MidCompiledInstance(typeof(Mid0016)) }, - { Mid0017.MID, new MidCompiledInstance(typeof(Mid0017)) }, - { Mid0018.MID, new MidCompiledInstance(typeof(Mid0018)) }, - { Mid0019.MID, new MidCompiledInstance(typeof(Mid0019)) }, - { Mid0020.MID, new MidCompiledInstance(typeof(Mid0020)) }, - { Mid0021.MID, new MidCompiledInstance(typeof(Mid0021)) }, - { Mid0022.MID, new MidCompiledInstance(typeof(Mid0022)) }, - { Mid0023.MID, new MidCompiledInstance(typeof(Mid0023)) }, - { Mid0024.MID, new MidCompiledInstance(typeof(Mid0024)) }, - { Mid2504.MID, new MidCompiledInstance(typeof(Mid2504)) }, - { Mid2505.MID, new MidCompiledInstance(typeof(Mid2505)) }, - { Mid2506.MID, new MidCompiledInstance(typeof(Mid2506)) } + { Mid0010.MID, new CompiledInstance(typeof(Mid0010)) }, + { Mid0011.MID, new CompiledInstance(typeof(Mid0011)) }, + { Mid0012.MID, new CompiledInstance(typeof(Mid0012)) }, + { Mid0013.MID, new CompiledInstance(typeof(Mid0013)) }, + { Mid0014.MID, new CompiledInstance(typeof(Mid0014)) }, + { Mid0015.MID, new CompiledInstance(typeof(Mid0015)) }, + { Mid0016.MID, new CompiledInstance(typeof(Mid0016)) }, + { Mid0017.MID, new CompiledInstance(typeof(Mid0017)) }, + { Mid0018.MID, new CompiledInstance(typeof(Mid0018)) }, + { Mid0019.MID, new CompiledInstance(typeof(Mid0019)) }, + { Mid0020.MID, new CompiledInstance(typeof(Mid0020)) }, + { Mid0021.MID, new CompiledInstance(typeof(Mid0021)) }, + { Mid0022.MID, new CompiledInstance(typeof(Mid0022)) }, + { Mid0023.MID, new CompiledInstance(typeof(Mid0023)) }, + { Mid0024.MID, new CompiledInstance(typeof(Mid0024)) }, + { Mid2501.MID, new CompiledInstance(typeof(Mid2501)) }, + { Mid2502.MID, new CompiledInstance(typeof(Mid2502)) }, + { Mid2503.MID, new CompiledInstance(typeof(Mid2503)) }, + { Mid2504.MID, new CompiledInstance(typeof(Mid2504)) }, + { Mid2505.MID, new CompiledInstance(typeof(Mid2505)) }, + { Mid2506.MID, new CompiledInstance(typeof(Mid2506)) } }; } diff --git a/src/OpenProtocolInterpreter/PowerMACS/BoltData.cs b/src/OpenProtocolInterpreter/PowerMACS/BoltData.cs index 8ad4da59..2b2ce58a 100644 --- a/src/OpenProtocolInterpreter/PowerMACS/BoltData.cs +++ b/src/OpenProtocolInterpreter/PowerMACS/BoltData.cs @@ -35,13 +35,13 @@ public BoltDataCollectionDefinitionAttribute(int field, int revision) : base(fie } - internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { return new DataField>(Field, index, Size, HasPrefix) { DefaultConverter = PackBoltData, DefaultParser = ParseBoltData - }.Bind(mid, propertyInfo); + }.Bind(owner, propertyInfo); } private static string PackBoltData(char paddingChar, int size, PaddingOrientation orientation, List boltsData) diff --git a/src/OpenProtocolInterpreter/PowerMACS/BoltResult.cs b/src/OpenProtocolInterpreter/PowerMACS/BoltResult.cs index 7f5d27b5..4054a677 100644 --- a/src/OpenProtocolInterpreter/PowerMACS/BoltResult.cs +++ b/src/OpenProtocolInterpreter/PowerMACS/BoltResult.cs @@ -27,13 +27,13 @@ public BoltResultCollectionDefinitionAttribute(int field, int revision) : base(f } - internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { return new DataField>(Field, index, Size, HasPrefix) { DefaultConverter = PackBoltResult, DefaultParser = ParseBoltResult - }.Bind(mid, propertyInfo); + }.Bind(owner, propertyInfo); } private static string PackBoltResult(char paddingChar, int size, PaddingOrientation orientation, List boltResults) diff --git a/src/OpenProtocolInterpreter/PowerMACS/PowerMACSMessages.cs b/src/OpenProtocolInterpreter/PowerMACS/PowerMACSMessages.cs index a9ac2fb3..1a54625e 100644 --- a/src/OpenProtocolInterpreter/PowerMACS/PowerMACSMessages.cs +++ b/src/OpenProtocolInterpreter/PowerMACS/PowerMACSMessages.cs @@ -11,13 +11,13 @@ internal class PowerMACSMessages : MessagesTemplate { public PowerMACSMessages() : base() { - _templates = new Dictionary() + _templates = new Dictionary>() { - { Mid0105.MID, new MidCompiledInstance(typeof(Mid0105)) }, - { Mid0106.MID, new MidCompiledInstance(typeof(Mid0106)) }, - { Mid0107.MID, new MidCompiledInstance(typeof(Mid0107)) }, - { Mid0108.MID, new MidCompiledInstance(typeof(Mid0108)) }, - { Mid0109.MID, new MidCompiledInstance(typeof(Mid0109)) } + { Mid0105.MID, new CompiledInstance(typeof(Mid0105)) }, + { Mid0106.MID, new CompiledInstance(typeof(Mid0106)) }, + { Mid0107.MID, new CompiledInstance(typeof(Mid0107)) }, + { Mid0108.MID, new CompiledInstance(typeof(Mid0108)) }, + { Mid0109.MID, new CompiledInstance(typeof(Mid0109)) } }; } diff --git a/src/OpenProtocolInterpreter/PowerMACS/SpecialValue.cs b/src/OpenProtocolInterpreter/PowerMACS/SpecialValue.cs index 13a8ee58..a91fd44a 100644 --- a/src/OpenProtocolInterpreter/PowerMACS/SpecialValue.cs +++ b/src/OpenProtocolInterpreter/PowerMACS/SpecialValue.cs @@ -112,13 +112,13 @@ public SpecialValueCollectionDefinitionAttribute(int field, int revision) : base } - internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { return new DataField>(Field, index, Size, HasPrefix) { DefaultConverter = PackSpecialValues, DefaultParser = ParseSpecialValues - }.Bind(mid, propertyInfo); + }.Bind(owner, propertyInfo); } private string PackSpecialValues(char paddingChar, int size, PaddingOrientation orientation, List specialValues) diff --git a/src/OpenProtocolInterpreter/PowerMACS/StepResult.cs b/src/OpenProtocolInterpreter/PowerMACS/StepResult.cs index 81149dfa..ced683dc 100644 --- a/src/OpenProtocolInterpreter/PowerMACS/StepResult.cs +++ b/src/OpenProtocolInterpreter/PowerMACS/StepResult.cs @@ -28,13 +28,13 @@ public StepResultCollectionDefinitionAttribute(int field, int revision) : base(f } - internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { return new DataField>(Field, index, Size, HasPrefix) { DefaultConverter = PackStepResults, DefaultParser = ParseStepResults - }.Bind(mid, propertyInfo); + }.Bind(owner, propertyInfo); } protected static string PackStepResults(char paddingChar, int size, PaddingOrientation orientation, List stepResults) diff --git a/src/OpenProtocolInterpreter/Result/ResultMessages.cs b/src/OpenProtocolInterpreter/Result/ResultMessages.cs index e0824101..1cdfe7d0 100644 --- a/src/OpenProtocolInterpreter/Result/ResultMessages.cs +++ b/src/OpenProtocolInterpreter/Result/ResultMessages.cs @@ -11,11 +11,11 @@ internal class ResultMessages : MessagesTemplate { public ResultMessages() : base() { - _templates = new Dictionary() + _templates = new Dictionary>() { - { Mid1201.MID, new MidCompiledInstance(typeof(Mid1201)) }, - { Mid1202.MID, new MidCompiledInstance(typeof(Mid1202)) }, - { Mid1203.MID, new MidCompiledInstance(typeof(Mid1203)) } + { Mid1201.MID, new CompiledInstance(typeof(Mid1201)) }, + { Mid1202.MID, new CompiledInstance(typeof(Mid1202)) }, + { Mid1203.MID, new CompiledInstance(typeof(Mid1203)) } }; } diff --git a/src/OpenProtocolInterpreter/Statistic/StatisticMessages.cs b/src/OpenProtocolInterpreter/Statistic/StatisticMessages.cs index 5e8234fb..d83daf20 100644 --- a/src/OpenProtocolInterpreter/Statistic/StatisticMessages.cs +++ b/src/OpenProtocolInterpreter/Statistic/StatisticMessages.cs @@ -11,10 +11,10 @@ internal class StatisticMessages : MessagesTemplate { public StatisticMessages() : base() { - _templates = new Dictionary() + _templates = new Dictionary>() { - { Mid0300.MID, new MidCompiledInstance(typeof(Mid0300)) }, - { Mid0301.MID, new MidCompiledInstance(typeof(Mid0301)) } + { Mid0300.MID, new CompiledInstance(typeof(Mid0300)) }, + { Mid0301.MID, new CompiledInstance(typeof(Mid0301)) } }; } diff --git a/src/OpenProtocolInterpreter/Tightening/StageResult.cs b/src/OpenProtocolInterpreter/Tightening/StageResult.cs index 6aaef21a..ced6cb7b 100644 --- a/src/OpenProtocolInterpreter/Tightening/StageResult.cs +++ b/src/OpenProtocolInterpreter/Tightening/StageResult.cs @@ -56,13 +56,13 @@ public StageResultCollectionDefinitionAttribute(int field, int revision) : base( } - internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { return new DataField>(Field, index, Size, HasPrefix) { DefaultConverter = PackStageResults, DefaultParser = ParseStageResults - }.Bind(mid, propertyInfo); + }.Bind(owner, propertyInfo); } private static string PackStageResults(char paddingChar, int size, PaddingOrientation orientation, List stageResults) diff --git a/src/OpenProtocolInterpreter/Tightening/StrategyOptions.cs b/src/OpenProtocolInterpreter/Tightening/StrategyOptions.cs index 1fc59b54..d8a4212d 100644 --- a/src/OpenProtocolInterpreter/Tightening/StrategyOptions.cs +++ b/src/OpenProtocolInterpreter/Tightening/StrategyOptions.cs @@ -101,13 +101,13 @@ public StrategyOptionsDefinitionAttribute(int field, int revision) : base(field, } - internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { return new DataField(Field, index, Size, HasPrefix) { DefaultConverter = PackStrategyOptions, DefaultParser = ParseStrategyOptions - }.Bind(mid, propertyInfo); + }.Bind(owner, propertyInfo); } private static string PackStrategyOptions(char paddingChar, int size, PaddingOrientation orientation, StrategyOptions strategyOptions) diff --git a/src/OpenProtocolInterpreter/Tightening/TighteningErrorStatus.cs b/src/OpenProtocolInterpreter/Tightening/TighteningErrorStatus.cs index 67200696..53896ec9 100644 --- a/src/OpenProtocolInterpreter/Tightening/TighteningErrorStatus.cs +++ b/src/OpenProtocolInterpreter/Tightening/TighteningErrorStatus.cs @@ -169,13 +169,13 @@ public TighteningErrorStatusDefinitionAttribute(int field, int revision) : base( } - internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { return new DataField(Field, index, Size, HasPrefix) { DefaultConverter = PackTighteningErrorStatus, DefaultParser = ParseTighteningErrorStatus - }.Bind(mid, propertyInfo); + }.Bind(owner, propertyInfo); } private static string PackTighteningErrorStatus(char paddingChar, int size, PaddingOrientation orientation, TighteningErrorStatus tighteningErrorStatus) @@ -325,13 +325,13 @@ public TighteningErrorStatus2DefinitionAttribute(int field, int revision) : base } - internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { return new DataField(Field, index, Size, HasPrefix) { DefaultConverter = PackTighteningErrorStatus2, DefaultParser = ParseTighteningErrorStatus2 - }.Bind(mid, propertyInfo); + }.Bind(owner, propertyInfo); } private static string PackTighteningErrorStatus2(char paddingChar, int size, PaddingOrientation orientation, TighteningErrorStatus2 tighteningErrorStatus) diff --git a/src/OpenProtocolInterpreter/Tightening/TighteningMessages.cs b/src/OpenProtocolInterpreter/Tightening/TighteningMessages.cs index 81ece324..cf300347 100644 --- a/src/OpenProtocolInterpreter/Tightening/TighteningMessages.cs +++ b/src/OpenProtocolInterpreter/Tightening/TighteningMessages.cs @@ -11,16 +11,16 @@ internal class TighteningMessages : MessagesTemplate { public TighteningMessages() : base() { - _templates = new Dictionary() + _templates = new Dictionary>() { - { Mid0060.MID, new MidCompiledInstance(typeof(Mid0060)) }, - { Mid0061.MID, new MidCompiledInstance(typeof(Mid0061)) }, - { Mid0062.MID, new MidCompiledInstance(typeof(Mid0062)) }, - { Mid0063.MID, new MidCompiledInstance(typeof(Mid0063)) }, - { Mid0064.MID, new MidCompiledInstance(typeof(Mid0064)) }, - { Mid0065.MID, new MidCompiledInstance(typeof(Mid0065)) }, - { Mid0066.MID, new MidCompiledInstance(typeof(Mid0066)) }, - { Mid0902.MID, new MidCompiledInstance(typeof(Mid0902)) } + { Mid0060.MID, new CompiledInstance(typeof(Mid0060)) }, + { Mid0061.MID, new CompiledInstance(typeof(Mid0061)) }, + { Mid0062.MID, new CompiledInstance(typeof(Mid0062)) }, + { Mid0063.MID, new CompiledInstance(typeof(Mid0063)) }, + { Mid0064.MID, new CompiledInstance(typeof(Mid0064)) }, + { Mid0065.MID, new CompiledInstance(typeof(Mid0065)) }, + { Mid0066.MID, new CompiledInstance(typeof(Mid0066)) }, + { Mid0902.MID, new CompiledInstance(typeof(Mid0902)) } }; } diff --git a/src/OpenProtocolInterpreter/Time/TimeMessages.cs b/src/OpenProtocolInterpreter/Time/TimeMessages.cs index e08f8968..c5cc8b2b 100644 --- a/src/OpenProtocolInterpreter/Time/TimeMessages.cs +++ b/src/OpenProtocolInterpreter/Time/TimeMessages.cs @@ -11,11 +11,11 @@ internal class TimeMessages : MessagesTemplate { public TimeMessages() : base() { - _templates = new Dictionary() + _templates = new Dictionary>() { - { Mid0080.MID, new MidCompiledInstance(typeof(Mid0080)) }, - { Mid0081.MID, new MidCompiledInstance(typeof(Mid0081)) }, - { Mid0082.MID, new MidCompiledInstance(typeof(Mid0082)) } + { Mid0080.MID, new CompiledInstance(typeof(Mid0080)) }, + { Mid0081.MID, new CompiledInstance(typeof(Mid0081)) }, + { Mid0082.MID, new CompiledInstance(typeof(Mid0082)) } }; } diff --git a/src/OpenProtocolInterpreter/Tool/Mid0701.cs b/src/OpenProtocolInterpreter/Tool/Mid0701.cs index ad8b4c32..5918130f 100644 --- a/src/OpenProtocolInterpreter/Tool/Mid0701.cs +++ b/src/OpenProtocolInterpreter/Tool/Mid0701.cs @@ -1,6 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; namespace OpenProtocolInterpreter.Tool { @@ -8,7 +7,7 @@ namespace OpenProtocolInterpreter.Tool /// Tool list upload reply /// /// Upload a list of connected tools from controller. - /// + /// /// Message sent by: Controller /// Answer: None /// The list will contain all tools that are connected to the controller or station. @@ -18,12 +17,15 @@ public class Mid0701 : Mid, ITool, IController { public const int MID = 701; - public int TotalTools { get => Tools.Count; } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 3, HasPrefix = false)] + public int TotalTools { get; set; } + + [ToolDataCollectionDefinition(revision: 1, field: 2, Index = 23, Size = 0, HasPrefix = false)] public List Tools { get; set; } public Mid0701() : this(new Header() { - Mid = MID, + Mid = MID, Revision = DEFAULT_REVISION }) { @@ -36,52 +38,18 @@ public Mid0701(Header header) : base(header) public override string Pack() { - GetField(1, DataFields.TotalTools).SetValue(OpenProtocolConvert.ToString, TotalTools); - var eachToolField = GetField(1, DataFields.EachTool); - eachToolField.Value = PackTools(); - eachToolField.Size = eachToolField.Value.Length; + TotalTools = Tools?.Count ?? 0; //Enforce list size even if modified + GetField(revision: 1, field: 2).Size = TotalTools * ToolData.SectionSize; //Enforce size of variable data fields return base.Pack(); } - public override Mid Parse(ReadOnlySpan package) - { - Header = ProcessHeader(package); - - var eachToolField = GetField(1, DataFields.EachTool); - eachToolField.Size = Header.Length - eachToolField.Index; - ProcessDataFields(package); - Tools = ToolData.ParseAll(eachToolField.Value).ToList(); - return this; - } - - protected virtual string PackTools() + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - string pack = string.Empty; - foreach (var tool in Tools) + if (dataField.Field == 2) //Tools { - pack += tool.Pack(); + dataField.Size = TotalTools * ToolData.SectionSize; } - - return pack; - } - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.TotalTools, 20, 3, false), - new(DataFields.EachTool, 23, 3, false) - } - } - }; - } - protected enum DataFields - { - TotalTools, - EachTool + base.ProcessDataField(dataField, package); } } } diff --git a/src/OpenProtocolInterpreter/Tool/Mid0702.cs b/src/OpenProtocolInterpreter/Tool/Mid0702.cs index aa0d4531..1cbffd92 100644 --- a/src/OpenProtocolInterpreter/Tool/Mid0702.cs +++ b/src/OpenProtocolInterpreter/Tool/Mid0702.cs @@ -1,19 +1,25 @@ using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; namespace OpenProtocolInterpreter.Tool { + /// + /// Tool Data upload reply with generic data + /// Upload a list of connected tools from controller. + /// Message sent by: Controller + /// Answer: None + /// The list will contain all tool parameters that are connected to the controller or station. + /// To request the data with required extra data () is used. + /// public class Mid0702 : Mid, ITool, IController { public const int MID = 702; - public int ToolNumber - { - get => GetField(1, DataFields.ToolNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.ToolNumber).SetValue(OpenProtocolConvert.ToString, value); - } - public int NumberOfToolParameters => ToolDataUpload.Count; + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 3, HasPrefix = false)] + public int NumberOfToolPIDs { get; set; } + + [VariableDataFieldCollectionDefinition(revision: 1, field: 2, Index = 23, Size = 0, HasPrefix = false)] public List ToolDataUpload { get; set; } public Mid0702() : this(new Header() @@ -31,41 +37,41 @@ public Mid0702(Header header) : base(header) public override string Pack() { - GetField(1, DataFields.NumberOfToolParameters).SetValue(OpenProtocolConvert.ToString, ToolDataUpload.Count); - GetField(1, DataFields.EachToolDataUpload).Value = OpenProtocolConvert.ToString(ToolDataUpload); + NumberOfToolPIDs = ToolDataUpload?.Count ?? 0; //Enforce list size even if modified + GetField(revision: 1, field: 2).Size = ToolDataUpload?.Sum(x => x.TotalSize) ?? 0; //Enforce size of variable data fields return base.Pack(); } - public override Mid Parse(ReadOnlySpan package) + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - Header = ProcessHeader(package); - var dataFieldsField = GetField(1, DataFields.EachToolDataUpload); - dataFieldsField.Size = Header.Length - dataFieldsField.Index; - ProcessDataFields(package); - ToolDataUpload = VariableDataField.ParseAll(dataFieldsField.Value).ToList(); - return this; + if (dataField.Field == 2) //VariableDataFields + { + dataField.Size = Header.Length - dataField.Index; + } + base.ProcessDataField(dataField, package); } + } + + /// + /// To request the data Application data message request with required extra data is used + /// A check for allowed PIDs to be included in this message should be done for each controller type. + /// + public class Mid0702ExtraData : ExtraData, IExtraDataRequest + { + public override int Mid => Mid0702.MID; + + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 0, Size = 4)] + public int ToolNumber { get; set; } - protected override Dictionary> RegisterDatafields() + public Mid0702ExtraData() { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.ToolNumber, 20, 4), - DataField.Number(DataFields.NumberOfToolParameters, 26, 2, false), - new(DataFields.EachToolDataUpload, 28, 0, false) - } - } - }; + } - protected enum DataFields + public Mid0702ExtraData(int revision) : base(revision) { - ToolNumber, - NumberOfToolParameters, - EachToolDataUpload + } } + } diff --git a/src/OpenProtocolInterpreter/Tool/Mid0703.cs b/src/OpenProtocolInterpreter/Tool/Mid0703.cs index 81eeafef..8f9184d7 100644 --- a/src/OpenProtocolInterpreter/Tool/Mid0703.cs +++ b/src/OpenProtocolInterpreter/Tool/Mid0703.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; namespace OpenProtocolInterpreter.Tool @@ -8,10 +8,10 @@ namespace OpenProtocolInterpreter.Tool /// Set calibration value request with generic data /// /// This message is sent by the integrator in order to set the calibration value of the tool. - /// + /// /// Message sent by: Integrator /// - /// Answer: Command accepted or + /// Answer: Command accepted or /// Command error, with code Calibration failed /// /// @@ -21,12 +21,13 @@ public class Mid0703 : Mid, ITool, IIntegrator, IAcceptableCommand, IDeclinableC public IEnumerable DocumentedPossibleErrors => new Error[] { Error.CalibrationFailed }; - public int ToolNumber - { - get => GetField(1, DataFields.ToolNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.ToolNumber).SetValue(OpenProtocolConvert.ToString, value); - } - public int NumberOfCalibrationParameters => CalibrationParameters.Count; + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 4)] + public int ToolNumber { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 26, Size = 2, HasPrefix = false)] + public int NumberOfCalibrationParameters { get; set; } + + [VariableDataFieldCollectionDefinition(revision: 1, field: 3, Index = 28, Size = 0, HasPrefix = false)] public List CalibrationParameters { get; set; } public Mid0703() : this(new Header() @@ -44,40 +45,18 @@ public Mid0703(Header header) : base(header) public override string Pack() { - GetField(1, DataFields.NumberOfCalibrationParameters).SetValue(OpenProtocolConvert.ToString, CalibrationParameters.Count); - GetField(1, DataFields.EachCalibrationParameter).Value = OpenProtocolConvert.ToString(CalibrationParameters); + NumberOfCalibrationParameters = CalibrationParameters?.Count ?? 0; //Enforce list size even if modified + GetField(revision: 1, field: 3).Size = CalibrationParameters?.Sum(x => x.TotalSize) ?? 0; //Enforce size of variable data fields return base.Pack(); } - public override Mid Parse(ReadOnlySpan package) + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - Header = ProcessHeader(package); - var dataFieldsField = GetField(1, DataFields.EachCalibrationParameter); - dataFieldsField.Size = Header.Length - dataFieldsField.Index; - ProcessDataFields(package); - CalibrationParameters = VariableDataField.ParseAll(dataFieldsField.Value).ToList(); - return this; - } - - protected override Dictionary> RegisterDatafields() - { - return new Dictionary>() + if (dataField.Field == 3) //CalibrationParameters { - { - 1, new List() - { - DataField.Number(DataFields.ToolNumber, 20, 4), - DataField.Number(DataFields.NumberOfCalibrationParameters, 26, 2, false), - new(DataFields.EachCalibrationParameter, 28, 0, false) - } - } - }; - } - protected enum DataFields - { - ToolNumber, - NumberOfCalibrationParameters, - EachCalibrationParameter + dataField.Size = Header.Length - dataField.Index; + } + base.ProcessDataField(dataField, package); } } } diff --git a/src/OpenProtocolInterpreter/Tool/Mid0704.cs b/src/OpenProtocolInterpreter/Tool/Mid0704.cs index 2f9dac4a..340fd504 100644 --- a/src/OpenProtocolInterpreter/Tool/Mid0704.cs +++ b/src/OpenProtocolInterpreter/Tool/Mid0704.cs @@ -21,11 +21,10 @@ public class Mid0704 : Mid, ITool, IController { public const int MID = 704; - public int NumberOfDataFields - { - get => GetField(1, DataFields.NumberOfDataFields).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(1, DataFields.NumberOfDataFields).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 3, HasPrefix = false)] + public int NumberOfDataFields { get; set; } + + [VariableDataFieldCollectionDefinition(revision: 1, field: 2, Index = 23, Size = 0, HasPrefix = false)] public List VariableDataFields { get; set; } public Mid0704() : this(new Header() @@ -43,41 +42,135 @@ public Mid0704(Header header) : base(header) public override string Pack() { - var revision = Header.StandardizedRevision; - GetField(revision, DataFields.VariableDataFields).SetValue(OpenProtocolConvert.ToString(VariableDataFields)); + NumberOfDataFields = VariableDataFields?.Count ?? 0; //Enforce list size even if modified + GetField(revision: 1, field: 2).Size = VariableDataFields?.Sum(x => x.TotalSize) ?? 0; //Enforce size of variable data fields + return base.Pack(); + } + + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) + { + if (dataField.Field == 2) //VariableDataFields + { + dataField.Size = Header.Length - dataField.Index; + } + base.ProcessDataField(dataField, package); + } + } + + /// + /// Use to request for uploads. + /// + public class Mid0704ExtraDataRequest : ExtraData, IExtraDataRequest + { + private const int PID_SIZE = 6; + + public override int Mid => Mid0704.MID; + + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 0, Size = 4, HasPrefix = false)] + public int ToolNumber { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 4, Size = 3, HasPrefix = false)] + public int TotalRequestedPIDs { get; set; } + + [Int32CollectionDefinition(revision: 1, field: 3, Index = 7, Size = 0, HasPrefix = false, EachFieldSize = PID_SIZE)] + public List RequestedPIDs { get; set; } + + public Mid0704ExtraDataRequest() + { + + } + + public Mid0704ExtraDataRequest(int revision) : base(revision) + { + + } + + public override string Pack() + { + TotalRequestedPIDs = RequestedPIDs?.Count ?? 0; //Enforce list size even if modified + GetField(revision: 1, field: 3).Size = TotalRequestedPIDs * PID_SIZE; //Enforce size of requested pids + return base.Pack(); + } + + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) + { + if (dataField.Field == 3) //RequestedPIDs + { + dataField.Size = package.Length - dataField.Index; + } + base.ProcessDataField(dataField, package); + } + } + + /// + ///Use for subscription of uploads at versioning. + /// + /// As the restriction is given as an UI the value may be casted to the type for the requested + /// parameter. For a string parameter only 000 is allowed. For time parameters the restriction is + /// given in seconds. + /// + /// + /// Example: A request for temperature with a restriction of 5 will initiate a transmission of MID 0704 whenever the temperature has changed 5 degrees or more. + /// + /// + public class Mid0704ExtraDataSubscription : ExtraData, IExtraDataSubscription + { + public override int Mid => Mid0704.MID; + + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 0, Size = 4, HasPrefix = false)] + public int ToolNumber { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 4, Size = 3, HasPrefix = false)] + public int NumberOfPIDs { get; set; } + + [PIDRestrictionCollectionDefinition(revision: 1, field: 3, Index = 7, Size = 0, HasPrefix = false)] + public List PIDRestrictions { get; set; } + + public Mid0704ExtraDataSubscription() + { - return string.Concat(BuildHeader(), base.Pack(revision)); } - public override Mid Parse(ReadOnlySpan package) + public Mid0704ExtraDataSubscription(int revision) : base(revision) { - Header = ProcessHeader(package); - var field = GetField(1, DataFields.VariableDataFields); - field.Size = Header.Length - field.Index; - base.Parse(package); - VariableDataFields = VariableDataField.ParseAll(field.Value).ToList(); - return this; } - protected override Dictionary> RegisterDatafields() + public override string Pack() + { + NumberOfPIDs = PIDRestrictions?.Count ?? 0; //Enforce list size even if modified + GetField(revision: 1, field: 3).Size = NumberOfPIDs * PIDRestriction.PackedSize; //Enforce size of variable data fields + return base.Pack(); + } + + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - return new Dictionary>() + if (dataField.Field == 3) //CalibrationParameters { - { - 1, new List() - { - DataField.Number(DataFields.NumberOfDataFields, 20, 3, false), - DataField.Volatile(DataFields.VariableDataFields, 23, false) - } - } - }; + dataField.Size = package.Length - dataField.Index; + } + base.ProcessDataField(dataField, package); } + } - protected enum DataFields + /// + /// Use to unsubscribe a . + /// + public class Mid0704ExtraDataUnsubscription : ExtraData, IExtraDataUnsubscription + { + public override int Mid => Mid0704.MID; + + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 0, Size = 4, HasPrefix = false)] + public int ToolNumber { get; set; } + + public Mid0704ExtraDataUnsubscription() { - NumberOfDataFields, - VariableDataFields + + } + + public Mid0704ExtraDataUnsubscription(int revision) : base(revision) + { + } } } diff --git a/src/OpenProtocolInterpreter/Tool/OpenEndData.cs b/src/OpenProtocolInterpreter/Tool/OpenEndData.cs index ef3fd42b..347106a0 100644 --- a/src/OpenProtocolInterpreter/Tool/OpenEndData.cs +++ b/src/OpenProtocolInterpreter/Tool/OpenEndData.cs @@ -53,13 +53,13 @@ public OpenEndDataDefinitionAttribute(int field, int revision) : base(field, rev } - internal override DataField Build(Mid mid, PropertyInfo propertyInfo, int index) + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { return new DataField(Field, index, Size, HasPrefix) { DefaultConverter = PackOpenEndData, DefaultParser = ParseOpenEndData - }.Bind(mid, propertyInfo); + }.Bind(owner, propertyInfo); } private string PackOpenEndData(char paddingChar, int size, PaddingOrientation orientation, OpenEndData openEndData) diff --git a/src/OpenProtocolInterpreter/Tool/PIDRestriction.cs b/src/OpenProtocolInterpreter/Tool/PIDRestriction.cs new file mode 100644 index 00000000..d5cf19a4 --- /dev/null +++ b/src/OpenProtocolInterpreter/Tool/PIDRestriction.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace OpenProtocolInterpreter.Tool +{ + public class PIDRestriction + { + /// + /// Size in bytes of a packed : (6) plus (3). + /// + internal const int PackedSize = 9; + + public int PID { get; set; } + public int Restriction { get; set; } + + public PIDRestriction() + { + + } + + public PIDRestriction(int pid, int restriction) + { + PID = pid; + Restriction = restriction; + } + + public string Pack() + { + return OpenProtocolConvert.ToString('0', 6, PaddingOrientation.LeftPadded, PID) + + OpenProtocolConvert.ToString('0', 3, PaddingOrientation.LeftPadded, Restriction); + } + + public static PIDRestriction Parse(string value) + => Parse(value.AsSpan()); + + public static PIDRestriction Parse(ReadOnlySpan value) + { + return new PIDRestriction() + { + PID = OpenProtocolConvert.ToInt32(value.Slice(0, 6)), + Restriction = OpenProtocolConvert.ToInt32(value.Slice(6, 3)), + }; + } + } + + public class PIDRestrictionCollectionDefinitionAttribute : DataFieldDefinitionAttribute + { + public PIDRestrictionCollectionDefinitionAttribute(int revision) : base(revision) + { + + } + public PIDRestrictionCollectionDefinitionAttribute(int field, int revision) : base(field, revision) + { + + } + + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) + { + return new DataField>(Field, index, Size, HasPrefix) + { + DefaultConverter = PackPIDRestriction, + DefaultParser = ParsePIDRestriction + }.Bind(owner, propertyInfo); + } + + private string PackPIDRestriction(char paddingChar, int size, PaddingOrientation orientation, List pidRestrictions) + => string.Join("", pidRestrictions.Select(p => p.Pack())); + + private List ParsePIDRestriction(string value) + { + var span = value.AsSpan(); + var pidRestrictions = new List(span.Length / PIDRestriction.PackedSize); + for (int i = 0; i + PIDRestriction.PackedSize <= span.Length; i += PIDRestriction.PackedSize) + { + pidRestrictions.Add(PIDRestriction.Parse(span.Slice(i, PIDRestriction.PackedSize))); + } + + return pidRestrictions; + } + } +} diff --git a/src/OpenProtocolInterpreter/Tool/ToolData.cs b/src/OpenProtocolInterpreter/Tool/ToolData.cs index 67074464..13a5002d 100644 --- a/src/OpenProtocolInterpreter/Tool/ToolData.cs +++ b/src/OpenProtocolInterpreter/Tool/ToolData.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Reflection; namespace OpenProtocolInterpreter.Tool { @@ -8,6 +10,8 @@ namespace OpenProtocolInterpreter.Tool /// public class ToolData { + internal const int SectionSize = 94; + public int Number { get; set; } public string SerialNumber { get; set; } public string ModelName { get; set; } @@ -41,13 +45,39 @@ public static IEnumerable ParseAll(string value) public static IEnumerable ParseAll(ReadOnlySpan value) { if (value.IsEmpty) - return System.Array.Empty(); + return Array.Empty(); - var result = new System.Collections.Generic.List(); - const int sectionSize = 94; - for (int i = 0; i < value.Length; i += sectionSize) - result.Add(Parse(value.Slice(i, sectionSize))); + var result = new List(); + for (int i = 0; i < value.Length; i += SectionSize) + result.Add(Parse(value.Slice(i, SectionSize))); return result; } } + + public class ToolDataCollectionDefinitionAttribute : DataFieldDefinitionAttribute + { + public ToolDataCollectionDefinitionAttribute(int revision) : base(revision) + { + + } + public ToolDataCollectionDefinitionAttribute(int field, int revision) : base(field, revision) + { + + } + + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) + { + return new DataField>(Field, index, Size, HasPrefix) + { + DefaultConverter = PackToolData, + DefaultParser = ParseToolData + }.Bind(owner, propertyInfo); + } + + private string PackToolData(char paddingChar, int size, PaddingOrientation orientation, List toolData) + => string.Join("", toolData.Select(t => t.Pack())); + + private List ParseToolData(string value) + => ToolData.ParseAll(value).ToList(); + } } diff --git a/src/OpenProtocolInterpreter/Tool/ToolMessages.cs b/src/OpenProtocolInterpreter/Tool/ToolMessages.cs index f95f2854..6d46cd00 100644 --- a/src/OpenProtocolInterpreter/Tool/ToolMessages.cs +++ b/src/OpenProtocolInterpreter/Tool/ToolMessages.cs @@ -11,21 +11,39 @@ internal class ToolMessages : MessagesTemplate { public ToolMessages() : base() { - _templates = new Dictionary() + _templates = new Dictionary>() { - { Mid0040.MID, new MidCompiledInstance(typeof(Mid0040)) }, - { Mid0041.MID, new MidCompiledInstance(typeof(Mid0041)) }, - { Mid0042.MID, new MidCompiledInstance(typeof(Mid0042)) }, - { Mid0043.MID, new MidCompiledInstance(typeof(Mid0043)) }, - { Mid0044.MID, new MidCompiledInstance(typeof(Mid0044)) }, - { Mid0045.MID, new MidCompiledInstance(typeof(Mid0045)) }, - { Mid0046.MID, new MidCompiledInstance(typeof(Mid0046)) }, - { Mid0047.MID, new MidCompiledInstance(typeof(Mid0047)) }, - { Mid0048.MID, new MidCompiledInstance(typeof(Mid0048)) }, - { Mid0701.MID, new MidCompiledInstance(typeof(Mid0701)) }, - { Mid0702.MID, new MidCompiledInstance(typeof(Mid0702)) }, - { Mid0703.MID, new MidCompiledInstance(typeof(Mid0703)) }, - { Mid0704.MID, new MidCompiledInstance(typeof(Mid0704)) } + { Mid0040.MID, new CompiledInstance(typeof(Mid0040)) }, + { Mid0041.MID, new CompiledInstance(typeof(Mid0041)) }, + { Mid0042.MID, new CompiledInstance(typeof(Mid0042)) }, + { Mid0043.MID, new CompiledInstance(typeof(Mid0043)) }, + { Mid0044.MID, new CompiledInstance(typeof(Mid0044)) }, + { Mid0045.MID, new CompiledInstance(typeof(Mid0045)) }, + { Mid0046.MID, new CompiledInstance(typeof(Mid0046)) }, + { Mid0047.MID, new CompiledInstance(typeof(Mid0047)) }, + { Mid0048.MID, new CompiledInstance(typeof(Mid0048)) }, + { Mid0701.MID, new CompiledInstance(typeof(Mid0701)) }, + { Mid0702.MID, new CompiledInstance(typeof(Mid0702)) }, + { Mid0703.MID, new CompiledInstance(typeof(Mid0703)) }, + { Mid0704.MID, new CompiledInstance(typeof(Mid0704)) } + }; + + _extraDataTemplates = new Dictionary>>() + { + { + Mid0702.MID, new List>() + { + new CompiledInstance(typeof(Mid0702ExtraData)) + } + }, + { + Mid0704.MID, new List>() + { + new CompiledInstance(typeof(Mid0704ExtraDataRequest)), + new CompiledInstance(typeof(Mid0704ExtraDataSubscription)), + new CompiledInstance(typeof(Mid0704ExtraDataUnsubscription)) + } + } }; } diff --git a/src/OpenProtocolInterpreter/UserInterface/UserInterfaceMessages.cs b/src/OpenProtocolInterpreter/UserInterface/UserInterfaceMessages.cs index a8d10623..37689707 100644 --- a/src/OpenProtocolInterpreter/UserInterface/UserInterfaceMessages.cs +++ b/src/OpenProtocolInterpreter/UserInterface/UserInterfaceMessages.cs @@ -11,11 +11,11 @@ internal class UserInterfaceMessages : MessagesTemplate { public UserInterfaceMessages() : base() { - _templates = new Dictionary() + _templates = new Dictionary>() { - { Mid0110.MID, new MidCompiledInstance(typeof(Mid0110)) }, - { Mid0111.MID, new MidCompiledInstance(typeof(Mid0111)) }, - { Mid0113.MID, new MidCompiledInstance(typeof(Mid0113)) } + { Mid0110.MID, new CompiledInstance(typeof(Mid0110)) }, + { Mid0111.MID, new CompiledInstance(typeof(Mid0111)) }, + { Mid0113.MID, new CompiledInstance(typeof(Mid0113)) } }; } diff --git a/src/OpenProtocolInterpreter/Vin/VINMessages.cs b/src/OpenProtocolInterpreter/Vin/VINMessages.cs index e6752700..92088da8 100644 --- a/src/OpenProtocolInterpreter/Vin/VINMessages.cs +++ b/src/OpenProtocolInterpreter/Vin/VINMessages.cs @@ -11,13 +11,13 @@ internal class VinMessages : MessagesTemplate { public VinMessages() : base() { - _templates = new Dictionary() + _templates = new Dictionary>() { - { Mid0050.MID, new MidCompiledInstance(typeof(Mid0050)) }, - { Mid0051.MID, new MidCompiledInstance(typeof(Mid0051)) }, - { Mid0052.MID, new MidCompiledInstance(typeof(Mid0052)) }, - { Mid0053.MID, new MidCompiledInstance(typeof(Mid0053)) }, - { Mid0054.MID, new MidCompiledInstance(typeof(Mid0054)) } + { Mid0050.MID, new CompiledInstance(typeof(Mid0050)) }, + { Mid0051.MID, new CompiledInstance(typeof(Mid0051)) }, + { Mid0052.MID, new CompiledInstance(typeof(Mid0052)) }, + { Mid0053.MID, new CompiledInstance(typeof(Mid0053)) }, + { Mid0054.MID, new CompiledInstance(typeof(Mid0054)) } }; } diff --git a/src/OpenProtocolInterpreter/_internals/DataFieldMetadata.cs b/src/OpenProtocolInterpreter/_internals/DataFieldMetadata.cs index 672517a8..e259f7ab 100644 --- a/src/OpenProtocolInterpreter/_internals/DataFieldMetadata.cs +++ b/src/OpenProtocolInterpreter/_internals/DataFieldMetadata.cs @@ -20,9 +20,12 @@ public DataFieldMetadata(int index, DataFieldDefinitionAttribute attribute, Prop Property = property; } - public DataField CreateAndBind(Mid mid) + /// + /// Creates the data field bound to , which is either a or an . + /// + public DataField CreateAndBind(object owner) { - return Attribute.CreateAndBind(mid, Property, Index); + return Attribute.CreateAndBind(owner, Property, Index); } } } diff --git a/src/OpenProtocolInterpreter/_internals/Messages/IMessagesTemplate.cs b/src/OpenProtocolInterpreter/_internals/Messages/IMessagesTemplate.cs index baac7579..d16d430e 100644 --- a/src/OpenProtocolInterpreter/_internals/Messages/IMessagesTemplate.cs +++ b/src/OpenProtocolInterpreter/_internals/Messages/IMessagesTemplate.cs @@ -12,5 +12,7 @@ internal interface IMessagesTemplate Mid ProcessPackage(int mid, string package); Mid ProcessPackage(int mid, byte[] package); bool IsAssignableTo(int mid); + CompiledInstance GetInstance(int mid); + CompiledInstance GetExtraDataInstance(int mid, Type kind); } } diff --git a/src/OpenProtocolInterpreter/_internals/Messages/MessagesTemplate.cs b/src/OpenProtocolInterpreter/_internals/Messages/MessagesTemplate.cs index 47b10d52..1744f2bf 100644 --- a/src/OpenProtocolInterpreter/_internals/Messages/MessagesTemplate.cs +++ b/src/OpenProtocolInterpreter/_internals/Messages/MessagesTemplate.cs @@ -9,14 +9,16 @@ namespace OpenProtocolInterpreter.Messages /// internal abstract class MessagesTemplate : IMessagesTemplate { - protected IDictionary _templates; + protected IDictionary> _templates; + protected IDictionary>> _extraDataTemplates; /// /// Initializes a new instance of class. /// public MessagesTemplate() { - _templates = new Dictionary(); + _templates = new Dictionary>(); + _extraDataTemplates = new Dictionary>>(); } /// @@ -34,7 +36,7 @@ public MessagesTemplate() /// instance. public Mid ProcessPackage(int mid, string package) { - var compiledInstance = GetMidType(mid); + var compiledInstance = GetInstance(mid); return compiledInstance.CompiledConstructor().Parse(package); } @@ -46,10 +48,68 @@ public Mid ProcessPackage(int mid, string package) /// instance public Mid ProcessPackage(int mid, byte[] package) { - var compiledInstance = GetMidType(mid); + var compiledInstance = GetInstance(mid); return compiledInstance.CompiledConstructor().Parse(package); } + /// + /// Get from the dictionary based on mid number + /// + /// Mid number + /// Compiled instance + public CompiledInstance GetInstance(int mid) + { + if (!_templates.TryGetValue(mid, out CompiledInstance compiledInstance)) + { + throw new NotImplementedException($"MID {mid} was not implemented, please register it!"); + } + + return compiledInstance; + } + + /// + /// Get the of the registered for a mid number. + /// + /// A mid may have one per kind, since request (), + /// subscription () and unsubscription () + /// may not share the same content. + /// + /// + /// Mid number + /// , or + /// Compiled instance + public CompiledInstance GetExtraDataInstance(int mid, Type kind) + { + if (!_extraDataTemplates.TryGetValue(mid, out List> compiledInstances)) + { + throw new NotImplementedException($"MID {mid} has no extra data implemented, please register it!"); + } + + var compiledInstance = compiledInstances.FirstOrDefault(x => kind.IsAssignableFrom(x.Type)); + if (compiledInstance == null) + { + throw new NotImplementedException($"MID {mid} has no extra data implemented for {kind.Name}, please register it!"); + } + + return compiledInstance; + } + + /// + /// Register an implementer for a mid number. + /// The same mid may register one per kind (request, subscription and unsubscription). + /// + /// Mid number + /// Compiled instance of the implementer + protected void AddExtraDataTemplate(int mid, CompiledInstance compiledInstance) + { + if (!_extraDataTemplates.TryGetValue(mid, out List> compiledInstances)) + { + _extraDataTemplates.Add(mid, compiledInstances = new List>()); + } + + compiledInstances.Add(compiledInstance); + } + /// /// Update Mid instance it should instantiate /// @@ -63,7 +123,7 @@ public void AddOrUpdateTemplate(IDictionary types) _templates.Remove(type.Key); } - _templates.Add(type.Key, new MidCompiledInstance(type.Value)); + _templates.Add(type.Key, new CompiledInstance(type.Value)); } } @@ -92,29 +152,14 @@ protected void FilterSelectedMids(IEnumerable mids) } /// - /// Remove unused/ignored from dictionary. + /// Remove unused/ignored from dictionary. /// /// Ignored mid instances - private void FilterSelectedMids(IEnumerable mids) + private void FilterSelectedMids(IEnumerable> mids) { var ignoredMids = _templates.Where(x => !mids.Contains(x.Value)).ToList(); foreach (var ignore in ignoredMids) _templates.Remove(ignore); } - - /// - /// Get from the dictionary based on mid number - /// - /// Mid number - /// Compiled instance - private MidCompiledInstance GetMidType(int mid) - { - if (!_templates.TryGetValue(mid, out MidCompiledInstance instanceCompiler)) - { - throw new NotImplementedException($"MID {mid} was not implemented, please register it!"); - } - - return instanceCompiler; - } } } diff --git a/src/OpenProtocolInterpreter/_internals/Messages/MidCompiledInstance.cs b/src/OpenProtocolInterpreter/_internals/Messages/MidCompiledInstance.cs index 9b8b0c68..8a9ed2bc 100644 --- a/src/OpenProtocolInterpreter/_internals/Messages/MidCompiledInstance.cs +++ b/src/OpenProtocolInterpreter/_internals/Messages/MidCompiledInstance.cs @@ -4,16 +4,16 @@ namespace OpenProtocolInterpreter.Messages { - internal class MidCompiledInstance + internal class CompiledInstance { public Type Type { get; set; } - public Func CompiledConstructor { get; set; } + public Func CompiledConstructor { get; set; } - public MidCompiledInstance(Type type) + public CompiledInstance(Type type) { Type = type; var ctor = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null); - CompiledConstructor = Expression.Lambda>(Expression.New(ctor)).Compile(); + CompiledConstructor = Expression.Lambda>(Expression.New(ctor)).Compile(); } } } From 5dbe334230713ac46124ba9a53b08099513cb791 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Fri, 7 Aug 2026 00:53:46 -0300 Subject: [PATCH 61/78] Fixed parsing enum as integer at in32 datafields --- src/OpenProtocolInterpreter/DataField.cs | 20 +++++++- .../DataFieldDefinitionAttribute.cs | 49 +++++++++++++++++-- 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/OpenProtocolInterpreter/DataField.cs b/src/OpenProtocolInterpreter/DataField.cs index e18979e3..fcf3496e 100644 --- a/src/OpenProtocolInterpreter/DataField.cs +++ b/src/OpenProtocolInterpreter/DataField.cs @@ -133,6 +133,17 @@ public static DataField Timestamp(int field, int index, bool hasPrefix public static DataField Timestamp(Enum field, int index, bool hasPrefix = true) => Timestamp(field.GetHashCode(), index, hasPrefix); + public static DataField UnixTimestamp(int field, int index, bool hasPrefix = true) + { + return new DataField(field, index, 10, hasPrefix) + { + DefaultConverter = OpenProtocolConvert.ToString, + DefaultParser = OpenProtocolConvert.ToUnixDateTime + }; + } + public static DataField UnixTimestamp(Enum field, int index, bool hasPrefix = true) + => UnixTimestamp(field.GetHashCode(), index, hasPrefix); + public static DataField Decimal(int field, int index, int size, bool hasPrefix = true) { return new DataField(field, index, size, '0', PaddingOrientation.LeftPadded, hasPrefix) @@ -205,6 +216,7 @@ public class DataField : DataField, IBackedPropertyDataField private PropertyInfo _backingProperty; protected internal Func DefaultConverter; protected internal Func DefaultParser; + protected internal Func BackingPropertyConverter; public DataField(int field, int index, int size, bool hasPrefix = true) : base(field, index, size, hasPrefix) { @@ -224,11 +236,17 @@ public DataField(int field, int index, int size, char paddingChar, PaddingOrient public void SyncWithBackingPropertyIfBound() { - if (_backingProperty?.GetValue(_owner) is T propValue) + var backingValue = _backingProperty?.GetValue(_owner); + if (backingValue is T propValue) { var value = DefaultConverter(PaddingChar, Size, PaddingOrientation, propValue); base.SetValue(value); } + else if (backingValue != null && BackingPropertyConverter != null) + { + var value = DefaultConverter(PaddingChar, Size, PaddingOrientation, BackingPropertyConverter(backingValue)); + base.SetValue(value); + } } public override void SetValue(string value) diff --git a/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs b/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs index 11dc9f71..85d74681 100644 --- a/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs +++ b/src/OpenProtocolInterpreter/DataFieldDefinitionAttribute.cs @@ -49,6 +49,20 @@ internal virtual DataField Build(object owner, PropertyInfo propertyInfo, int in { return new DataField(Field, index, Size, PaddingChar, PaddingOrientation, HasPrefix); } + + /// + /// Tells whether the decorated property is an enum whose underlying type is , + /// as happens when a property is decorated with an + /// . Such a property reads back as a boxed enum rather + /// than as a , so its data field needs a + /// to be packed. + /// + protected static bool IsEnumBackedBy(PropertyInfo propertyInfo) + { + return propertyInfo != null + && propertyInfo.PropertyType.IsEnum + && Enum.GetUnderlyingType(propertyInfo.PropertyType) == typeof(T); + } } public class BooleanDataFieldDefinitionAttribute : DataFieldDefinitionAttribute { @@ -95,8 +109,13 @@ public Int32DataFieldDefinitionAttribute(int field, int revision) : base(field, } internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { - return DataField.Int32(Field, index, Size, HasPrefix) - .Bind(owner, propertyInfo); + var dataField = DataField.Int32(Field, index, Size, HasPrefix); + if (IsEnumBackedBy(propertyInfo)) + { + dataField.BackingPropertyConverter = Convert.ToInt32; + } + + return dataField.Bind(owner, propertyInfo); } } public class Int64DataFieldDefinitionAttribute : DataFieldDefinitionAttribute @@ -113,8 +132,13 @@ public Int64DataFieldDefinitionAttribute(int field, int revision) : base(field, } internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) { - return DataField.Int64(Field, index, Size, HasPrefix) - .Bind(owner, propertyInfo); + var dataField = DataField.Int64(Field, index, Size, HasPrefix); + if (IsEnumBackedBy(propertyInfo)) + { + dataField.BackingPropertyConverter = Convert.ToInt64; + } + + return dataField.Bind(owner, propertyInfo); } } public class DecimalDataFieldDefinitionAttribute : DataFieldDefinitionAttribute @@ -177,6 +201,23 @@ internal override DataField Build(object owner, PropertyInfo propertyInfo, int i } } + public class UnixTimestampDataFieldDefinitionAttribute : DataFieldDefinitionAttribute + { + public UnixTimestampDataFieldDefinitionAttribute(int revision) : base(revision) + { + Size = 10; + } + public UnixTimestampDataFieldDefinitionAttribute(int field, int revision) : base(field, revision) + { + Size = 10; + } + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) + { + return DataField.Timestamp(Field, index, HasPrefix) + .Bind(owner, propertyInfo); + } + } + public class VariableDataFieldCollectionDefinitionAttribute : DataFieldDefinitionAttribute { public VariableDataFieldCollectionDefinitionAttribute(int revision) : base(revision) From e4d5f2c52f473e6e356eb1be68302e17f9214833 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Fri, 7 Aug 2026 00:54:06 -0300 Subject: [PATCH 62/78] Fixed mid 0140 packing --- src/OpenProtocolInterpreter/Job/Advanced/Mid0140.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/OpenProtocolInterpreter/Job/Advanced/Mid0140.cs b/src/OpenProtocolInterpreter/Job/Advanced/Mid0140.cs index 2f0b0d79..bf7ba25f 100644 --- a/src/OpenProtocolInterpreter/Job/Advanced/Mid0140.cs +++ b/src/OpenProtocolInterpreter/Job/Advanced/Mid0140.cs @@ -186,6 +186,8 @@ protected override string BuildHeader() public override string Pack() { var builder = new StringBuilder(); + NumberOfParameterSets = JobList?.Count ?? 0; //Enforce list size even if modified + GetField(revision: Header.StandardizedRevision, field: 4).Size = NumberOfParameterSets * AdvancedJob.GetDefaultSize(Header.StandardizedRevision); var fields = DataFieldsByRevision().OrderBy(f => f.Index).ToList(); builder.Append(BuildHeader()); builder.Append(Pack(fields)); From 8d2418ec84c00d6641cd9fb22b97e7c9d5b54d0d Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Fri, 7 Aug 2026 00:54:25 -0300 Subject: [PATCH 63/78] Added unixdatetime parsing --- .../OpenProtocolConvert.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/OpenProtocolInterpreter/OpenProtocolConvert.cs b/src/OpenProtocolInterpreter/OpenProtocolConvert.cs index bfb76a2b..30346614 100644 --- a/src/OpenProtocolInterpreter/OpenProtocolConvert.cs +++ b/src/OpenProtocolInterpreter/OpenProtocolConvert.cs @@ -59,6 +59,25 @@ public static DateTime ToDateTime(ReadOnlySpan value) return convertedValue; } + public static string ToString(DateTimeOffset value) + => ToString('0', 10, PaddingOrientation.LeftPadded, value); + + public static string ToString(char paddingChar, int size, PaddingOrientation orientation, DateTimeOffset value) + => ToString(paddingChar, size, orientation, value.ToUnixTimeSeconds()); + + public static DateTimeOffset ToUnixDateTime(string value) + => ToUnixDateTime(value.AsSpan()); + + public static DateTimeOffset ToUnixDateTime(ReadOnlySpan value) + { + var convertedValue = DateTimeOffset.UnixEpoch; + if (value.IsWhiteSpace()) + return convertedValue; + + var unixTimestamp = ToInt64(value); + return DateTimeOffset.FromUnixTimeSeconds(unixTimestamp); + } + public static string ToString(decimal value) { return value.ToString("00.0###", _formatProvider); From f040a57fb51dca568dc985c8bcfc8dab2f57acce Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Fri, 7 Aug 2026 00:54:41 -0300 Subject: [PATCH 64/78] Updated mid 1201 to use datafield attributes --- src/OpenProtocolInterpreter/Result/Mid1201.cs | 291 +++++++++++------- .../Result/ObjectData.cs | 35 ++- src/OpenProtocolInterpreter/Tool/ToolData.cs | 8 +- 3 files changed, 221 insertions(+), 113 deletions(-) diff --git a/src/OpenProtocolInterpreter/Result/Mid1201.cs b/src/OpenProtocolInterpreter/Result/Mid1201.cs index 1dca07a6..7e4d6b6b 100644 --- a/src/OpenProtocolInterpreter/Result/Mid1201.cs +++ b/src/OpenProtocolInterpreter/Result/Mid1201.cs @@ -5,6 +5,7 @@ namespace OpenProtocolInterpreter.Result { + /// /// Operation result Overall data /// @@ -24,37 +25,57 @@ public class Mid1201 : Mid, IResult, IController, IAcknowledgeable, IAc public const int MID = 1201; [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 2, field: 1, Index = 20, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 3, field: 1, Index = 20, Size = 3, HasPrefix = false)] public int TotalNumberOfMessages { get; set; } [Int32DataFieldDefinition(revision: 1, field: 2, Index = 23, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 2, field: 2, Index = 23, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 3, field: 2, Index = 23, Size = 3, HasPrefix = false)] public int MessageNumber { get; set; } - [Int32DataFieldDefinition(revision: 1, field: 3, Index = 26, Size = 10, HasPrefix = false)] - public int ResultDataIdentifier { get; set; } + [Int64DataFieldDefinition(revision: 1, field: 3, Index = 26, Size = 10, HasPrefix = false)] + [Int64DataFieldDefinition(revision: 2, field: 3, Index = 26, Size = 10, HasPrefix = false)] + [Int64DataFieldDefinition(revision: 3, field: 3, Index = 26, Size = 10, HasPrefix = false)] + public long ResultDataIdentifier { get; set; } - [TimestampDataFieldDefinition(revision: 1, field: 4, Index = 36, PaddingChar = '0', HasPrefix = false)] + [TimestampDataFieldDefinition(revision: 1, field: 4, Index = 36, HasPrefix = false)] + [TimestampDataFieldDefinition(revision: 2, field: 4, Index = 36, HasPrefix = false)] + [TimestampDataFieldDefinition(revision: 3, field: 4, Index = 36, HasPrefix = false)] public DateTime Time { get; set; } - [BooleanDataFieldDefinition(revision: 1, field: 5, Index = 55, HasPrefix = false)] - public bool ResultStatus { get; set; } + [Int32DataFieldDefinition(revision: 1, field: 5, Index = 55, Size = 1, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 2, field: 5, Index = 55, Size = 1, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 3, field: 5, Index = 55, Size = 1, HasPrefix = false)] + public int ResultStatus { get; set; } [Int32DataFieldDefinition(revision: 1, field: 6, Index = 56, Size = 2, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 2, field: 6, Index = 56, Size = 2, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 3, field: 6, Index = 56, Size = 2, HasPrefix = false)] public OperationType OperationType { get; set; } [Int32DataFieldDefinition(revision: 2, field: 7, Index = 58, Size = 4, HasPrefix = false)] - public int RequestMid - { - get => GetField(Header.StandardizedRevision, DataFields.RequestMid).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(Header.StandardizedRevision, DataFields.RequestMid).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 3, field: 7, Index = 58, Size = 4, HasPrefix = false)] + public int RequestMid { get; set; } - [Int32DataFieldDefinition(revision: 1, field: 8, Index = 58, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 7, Index = 58, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 2, field: 8, Index = 62, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 3, field: 8, Index = 62, Size = 3, HasPrefix = false)] public int NumberOfObjects { get; set; } + [ObjectDataCollectionDefinition(revision: 1, field: 8, Index = 61, Size = 0, HasPrefix = false)] + [ObjectDataCollectionDefinition(revision: 2, field: 9, Index = 65, Size = 0, HasPrefix = false)] + [ObjectDataCollectionDefinition(revision: 3, field: 9, Index = 65, Size = 0, HasPrefix = false)] public List ObjectDataList { get; set; } - [Int32DataFieldDefinition(revision: 1, field: 10, Index = 61, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 1, field: 9, Index = 0, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 2, field: 10, Index = 0, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 3, field: 10, Index = 0, Size = 3, HasPrefix = false)] public int NumberOfDataFields { get; set; } + + [VariableDataFieldCollectionDefinition(revision: 1, field: 10, Index = 0, Size = 0, HasPrefix = false)] + [VariableDataFieldCollectionDefinition(revision: 2, field: 11, Index = 0, Size = 0, HasPrefix = false)] + [VariableDataFieldCollectionDefinition(revision: 3, field: 11, Index = 0, Size = 0, HasPrefix = false)] public List VariableDataFields { get; set; } public Mid1201() : this(DEFAULT_REVISION) @@ -78,125 +99,183 @@ public Mid1201(Header header) : base(header) protected override string BuildHeader() { - Header.Length = 20 + RevisionsByFields[Header.StandardizedRevision].Sum(x => x.Size + (x.HasPrefix ? 2 : 0)); + var fields = DataFieldsByRevision(); + Header.Length = Header.DefaultSize + fields.Sum(x => x.TotalSize); return Header.ToString(); } public override string Pack() { - NumberOfObjects = ObjectDataList.Count; - NumberOfDataFields = VariableDataFields.Count; - var revision = Header.StandardizedRevision; - GetField(revision, DataFields.ObjectData).SetValue(PackObjectDataList()); - GetField(revision, DataFields.DataFieldList).SetValue(OpenProtocolConvert.ToString(VariableDataFields)); + NumberOfObjects = ObjectDataList?.Count ?? 0; + NumberOfDataFields = VariableDataFields?.Count ?? 0; + if (Header.StandardizedRevision == 1) + { + GetField(revision: Header.StandardizedRevision, field: 8).Size = NumberOfObjects * ObjectData.Size(Header.StandardizedRevision); + GetField(revision: Header.StandardizedRevision, field: 10).Size = VariableDataFields.Sum(x => x.TotalSize); + } + else + { + GetField(revision: Header.StandardizedRevision, field: 9).Size = NumberOfObjects * ObjectData.Size(Header.StandardizedRevision); + GetField(revision: Header.StandardizedRevision, field: 11).Size = VariableDataFields.Sum(x => x.TotalSize); + } - return string.Concat(BuildHeader(), base.Pack(revision)); + var builder = new StringBuilder(); + builder.Append(BuildHeader()); + builder.Append(Pack(DataFieldsByRevision())); + return builder.ToString(); } - public override Mid Parse(ReadOnlySpan package) + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - Header = ProcessHeader(package); + switch (dataField.Field) + { + case 8 when dataField is DataField>: + case 9 when dataField is DataField>: + dataField.Size = NumberOfObjects * ObjectData.Size(Header.StandardizedRevision); + break; + case 10 when dataField is DataField>: //StepResults + case 11 when dataField is DataField>: + dataField.Size = Header.Length - dataField.Index; + break; + } + + base.ProcessDataField(dataField, package); + } + + protected override void ProcessDataFields(ReadOnlySpan package) + { + foreach (var field in DataFieldsByRevision()) + ProcessDataField(field, package); + } + + private IEnumerable DataFieldsByRevision() + { + var previousField = default(DataField); + foreach (var dataField in RevisionsByFields[Header.StandardizedRevision]) + { + if (previousField != null && dataField.Index == 0) + { + dataField.Index = previousField.Index + previousField.TotalSize; + } - var revision = Header.StandardizedRevision; - var rawTotalObjectData = GetValue(GetField(revision, DataFields.NumberOfObjects), package); - int totalObjectData = OpenProtocolConvert.ToInt32(rawTotalObjectData.ToString()); + previousField = dataField; + yield return dataField; + } + } + } - var objectDataField = GetField(revision, DataFields.ObjectData); - objectDataField.Size = totalObjectData * ObjectData.Size(Header.Revision); + public class Mid1201ExtraDataRequest : ExtraData, IExtraDataRequest + { + public override int Mid => Mid1201.MID; - var totalNumberDataField = GetField(revision, DataFields.NumberOfDataFields); - totalNumberDataField.Index = objectDataField.Index + objectDataField.Size; + [Int64DataFieldDefinition(revision: 1, field: 1, Index = 0, Size = 10, HasPrefix = false)] + public long Index { get; set; } - var dataFieldListField = GetField(revision, DataFields.DataFieldList); - dataFieldListField.Index = totalNumberDataField.Index + totalNumberDataField.Size; - dataFieldListField.Size = Header.Length - dataFieldListField.Index; + public Mid1201ExtraDataRequest() : this(2) + { - ProcessDataFields(revision, package); - ObjectDataList = ObjectData.ParseAll(revision, objectDataField.Value).ToList(); - VariableDataFields = VariableDataField.ParseAll(dataFieldListField.Value).ToList(); - return this; } - protected virtual string PackObjectDataList() + public Mid1201ExtraDataRequest(int revision) : base(revision) + { + } + } + + public class Mid1201ExtraDataSubscription : ExtraData, IExtraDataSubscription + { + public override int Mid => Mid1201.MID; + + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 0, Size = 10, HasPrefix = false)] + public int SendAlternatives { get; set; } + + [TimestampDataFieldDefinition(revision: 1, field: 2, Index = 10, HasPrefix = false)] + public DateTime DataIdentifierTimestamp { get; set; } + + [BooleanDataFieldDefinition(revision: 1, field: 4, Index = 29, HasPrefix = false)] + public bool SendObjectData { get; set; } + + public Mid1201ExtraDataSubscription() : this(1) { - var builder = new StringBuilder(); - foreach (var v in ObjectDataList) - { - builder.Append(v.Pack(Header.StandardizedRevision)); - } - return builder.ToString(); } + public Mid1201ExtraDataSubscription(int revision) : base(revision) + { + } + } + + public class Mid1201SecondAlternativeExtraDataSubscription : ExtraData, IExtraDataSubscription + { + public override int Mid => Mid1201.MID; + + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 0, Size = 10, HasPrefix = false)] + public int SendAlternatives { get; set; } + + [UnixTimestampDataFieldDefinition(revision: 1, field: 2, Index = 10, HasPrefix = false)] + public DateTimeOffset DataIdentifierTimestamp { get; set; } + + [BooleanDataFieldDefinition(revision: 1, field: 4, Index = 20, HasPrefix = false)] + public bool SendObjectData { get; set; } - protected override Dictionary> RegisterDatafields() + public Mid1201SecondAlternativeExtraDataSubscription() : this(1) { - return new Dictionary>() - { - { - 1, new List() - { - DataField.Number(DataFields.TotalMessages, 20, 3, false), - DataField.Number(DataFields.MessageNumber, 23, 3, false), - DataField.Number(DataFields.ResultDataIdentifier, 26, 10, false), - DataField.Timestamp(DataFields.Time, 36, false), - DataField.Boolean(DataFields.ResultStatus, 55, false), - DataField.Number(DataFields.OperationType, 56, 2, false), - DataField.Number(DataFields.NumberOfObjects, 58, 3, false), - DataField.Volatile(DataFields.ObjectData, 61, false), - DataField.Number(DataFields.NumberOfDataFields, 0, 3, false), - DataField.Volatile(DataFields.DataFieldList, 0, false) - } - }, - { - 2, new List() - { - DataField.Number(DataFields.TotalMessages, 20, 3, false), - DataField.Number(DataFields.MessageNumber, 23, 3, false), - DataField.Number(DataFields.ResultDataIdentifier, 26, 10, false), - DataField.Timestamp(DataFields.Time, 36, false), - DataField.Boolean(DataFields.ResultStatus, 55, false), - DataField.Number(DataFields.OperationType, 56, 2, false), - DataField.Number(DataFields.RequestMid, 58, 4, false), - DataField.Number(DataFields.NumberOfObjects, 62, 3, false), - DataField.Volatile(DataFields.ObjectData, 65, false), - DataField.Number(DataFields.NumberOfDataFields, 0, 3, false), - DataField.Volatile(DataFields.DataFieldList, 0, false) - } - }, - { - 3, new List() - { - DataField.Number(DataFields.TotalMessages, 20, 3, false), - DataField.Number(DataFields.MessageNumber, 23, 3, false), - DataField.Number(DataFields.ResultDataIdentifier, 26, 10, false), - DataField.Timestamp(DataFields.Time, 36, false), - DataField.Boolean(DataFields.ResultStatus, 55, false), - DataField.Number(DataFields.OperationType, 56, 2, false), - DataField.Number(DataFields.RequestMid, 58, 4, false), - DataField.Number(DataFields.NumberOfObjects, 62, 3, false), - DataField.Volatile(DataFields.ObjectData, 65, false), - DataField.Number(DataFields.NumberOfDataFields, 0, 3, false), - DataField.Volatile(DataFields.DataFieldList, 0, false) - } - } - }; + } - protected enum DataFields + public Mid1201SecondAlternativeExtraDataSubscription(int revision) : base(revision) { - TotalMessages, - MessageNumber, - ResultDataIdentifier, - Time, - ResultStatus, - OperationType, - RequestMid, - NumberOfObjects, - ObjectData, // list of data - NumberOfDataFields, - DataFieldList } } -} + public class Mid1201ThirdAlternativeExtraDataSubscription : ExtraData, IExtraDataSubscription + { + public override int Mid => Mid1201.MID; + + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 0, Size = 1, HasPrefix = false)] + public int SendAlternatives { get; set; } = 3; + + [Int64DataFieldDefinition(revision: 1, field: 2, Index = 1, Size = 10, HasPrefix = false)] + public long DataIdentifierFirstIndex { get; set; } + + [Int64DataFieldDefinition(revision: 1, field: 3, Index = 11, Size = 10, HasPrefix = false)] + public long DataIdentifierLastIndex { get; set; } + + [BooleanDataFieldDefinition(revision: 1, field: 4, Index = 21, HasPrefix = false)] + public bool SendObjectData { get; set; } + + public Mid1201ThirdAlternativeExtraDataSubscription() : this(1) + { + + } + + public Mid1201ThirdAlternativeExtraDataSubscription(int revision) : base(revision) + { + } + } + + public class Mid1201FourthAlternativeExtraDataSubscription : ExtraData, IExtraDataSubscription + { + public override int Mid => Mid1201.MID; + + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 0, Size = 1, HasPrefix = false)] + public int SendAlternatives { get; set; } = 3; + + [UnixTimestampDataFieldDefinition(revision: 1, field: 2, Index = 1, Size = 10, HasPrefix = false)] + public DateTimeOffset DataIdentifierFirstIndex { get; set; } + + [UnixTimestampDataFieldDefinition(revision: 1, field: 3, Index = 11, Size = 10, HasPrefix = false)] + public DateTimeOffset DataIdentifierLastIndex { get; set; } + + [BooleanDataFieldDefinition(revision: 1, field: 4, Index = 21, HasPrefix = false)] + public bool SendObjectData { get; set; } + + public Mid1201FourthAlternativeExtraDataSubscription() : this(1) + { + + } + + public Mid1201FourthAlternativeExtraDataSubscription(int revision) : base(revision) + { + } + } +} diff --git a/src/OpenProtocolInterpreter/Result/ObjectData.cs b/src/OpenProtocolInterpreter/Result/ObjectData.cs index 2c4a0dc4..40034171 100644 --- a/src/OpenProtocolInterpreter/Result/ObjectData.cs +++ b/src/OpenProtocolInterpreter/Result/ObjectData.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Reflection; using System.Text; namespace OpenProtocolInterpreter.Result @@ -20,10 +22,10 @@ public string Pack() public string Pack(int revision) { var package = OpenProtocolConvert.ToString('0', 4, PaddingOrientation.LeftPadded, Id) + OpenProtocolConvert.ToString(Status); - if(revision > 2) + if (revision > 2) { - return string.Concat(package, - OpenProtocolConvert.ToString((int)ObjectType), + return string.Concat(package, + OpenProtocolConvert.ToString((int)ObjectType), OpenProtocolConvert.ToString('0', 4, PaddingOrientation.LeftPadded, ReferenceObjectId)); } @@ -70,4 +72,31 @@ public static IEnumerable ParseAll(string value) internal static int Size(int revision) => revision > 2 ? 10 : 5; } + + public class ObjectDataCollectionDefinitionAttribute : DataFieldDefinitionAttribute + { + public ObjectDataCollectionDefinitionAttribute(int revision) : base(revision) + { + + } + public ObjectDataCollectionDefinitionAttribute(int field, int revision) : base(field, revision) + { + + } + + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) + { + return new DataField>(Field, index, Size, HasPrefix) + { + DefaultConverter = Pack, + DefaultParser = Parse + }.Bind(owner, propertyInfo); + } + + private string Pack(char paddingChar, int size, PaddingOrientation orientation, List value) + => string.Join("", value.Select(t => t.Pack(Revision))); + + private List Parse(string value) + => ObjectData.ParseAll(Revision, value).ToList(); + } } diff --git a/src/OpenProtocolInterpreter/Tool/ToolData.cs b/src/OpenProtocolInterpreter/Tool/ToolData.cs index 13a5002d..6359e8f1 100644 --- a/src/OpenProtocolInterpreter/Tool/ToolData.cs +++ b/src/OpenProtocolInterpreter/Tool/ToolData.cs @@ -69,15 +69,15 @@ internal override DataField Build(object owner, PropertyInfo propertyInfo, int i { return new DataField>(Field, index, Size, HasPrefix) { - DefaultConverter = PackToolData, - DefaultParser = ParseToolData + DefaultConverter = Pack, + DefaultParser = Parse }.Bind(owner, propertyInfo); } - private string PackToolData(char paddingChar, int size, PaddingOrientation orientation, List toolData) + private string Pack(char paddingChar, int size, PaddingOrientation orientation, List toolData) => string.Join("", toolData.Select(t => t.Pack())); - private List ParseToolData(string value) + private List Parse(string value) => ToolData.ParseAll(value).ToList(); } } From 2fedc59d6ef4a0a430a2bcc5565640f081ab3dc9 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Fri, 7 Aug 2026 00:54:54 -0300 Subject: [PATCH 65/78] Added packing tests along with parsing tests --- src/MIDTesters.Core/Alarm/TestMid0070.cs | 9 + src/MIDTesters.Core/Alarm/TestMid0071.cs | 48 + src/MIDTesters.Core/Alarm/TestMid0072.cs | 9 + src/MIDTesters.Core/Alarm/TestMid0073.cs | 9 + src/MIDTesters.Core/Alarm/TestMid0074.cs | 24 + src/MIDTesters.Core/Alarm/TestMid0075.cs | 9 + src/MIDTesters.Core/Alarm/TestMid0076.cs | 49 + src/MIDTesters.Core/Alarm/TestMid0077.cs | 9 + src/MIDTesters.Core/Alarm/TestMid0078.cs | 9 + src/MIDTesters.Core/Alarm/TestMid1000.cs | 20 + src/MIDTesters.Core/Alarm/TestMid1001.cs | 9 + .../ApplicationController/TestMid0270.cs | 9 + .../ApplicationSelector/TestMid0250.cs | 9 + .../ApplicationSelector/TestMid0251.cs | 15 + .../ApplicationSelector/TestMid0252.cs | 9 + .../ApplicationSelector/TestMid0253.cs | 9 + .../ApplicationSelector/TestMid0254.cs | 17 + .../ApplicationSelector/TestMid0255.cs | 17 + .../TestMid0260.cs | 9 + .../TestMid0261.cs | 9 + .../TestMid0262.cs | 12 + .../TestMid0263.cs | 9 + .../TestMid0264.cs | 9 + .../TestMid0265.cs | 13 + .../AutomaticManualMode/TestMid0400.cs | 9 + .../AutomaticManualMode/TestMid0401.cs | 13 + .../AutomaticManualMode/TestMid0402.cs | 9 + .../AutomaticManualMode/TestMid0403.cs | 9 + .../AutomaticManualMode/TestMid0410.cs | 9 + .../AutomaticManualMode/TestMid0411.cs | 13 + .../Communication/TestMid0001.cs | 21 + .../Communication/TestMid0002.cs | 144 ++ .../Communication/TestMid0003.cs | 9 + .../Communication/TestMid0004.cs | 26 + .../Communication/TestMid0005.cs | 12 + .../Communication/TestMid0006.cs | 14 + .../Communication/TestMid0008.cs | 14 + .../Communication/TestMid0009.cs | 14 + .../IOInterface/TestMid0200.cs | 21 + .../IOInterface/TestMid0210.cs | 9 + .../IOInterface/TestMid0211.cs | 20 + .../IOInterface/TestMid0212.cs | 9 + .../IOInterface/TestMid0213.cs | 9 + .../IOInterface/TestMid0214.cs | 12 + .../IOInterface/TestMid0215.cs | 70 + .../IOInterface/TestMid0216.cs | 12 + .../IOInterface/TestMid0217.cs | 14 + .../IOInterface/TestMid0218.cs | 9 + .../IOInterface/TestMid0219.cs | 12 + .../IOInterface/TestMid0220.cs | 12 + .../IOInterface/TestMid0221.cs | 13 + .../IOInterface/TestMid0222.cs | 9 + .../IOInterface/TestMid0223.cs | 12 + .../IOInterface/TestMid0224.cs | 12 + .../IOInterface/TestMid0225.cs | 12 + .../Job/Advanced/TestMid0120.cs | 9 + .../Job/Advanced/TestMid0121.cs | 12 + .../Job/Advanced/TestMid0122.cs | 12 + .../Job/Advanced/TestMid0123.cs | 12 + .../Job/Advanced/TestMid0124.cs | 12 + .../Job/Advanced/TestMid0125.cs | 9 + .../Job/Advanced/TestMid0126.cs | 9 + .../Job/Advanced/TestMid0127.cs | 9 + .../Job/Advanced/TestMid0128.cs | 9 + .../Job/Advanced/TestMid0129.cs | 22 + .../Job/Advanced/TestMid0130.cs | 12 + .../Job/Advanced/TestMid0131.cs | 9 + .../Job/Advanced/TestMid0132.cs | 9 + .../Job/Advanced/TestMid0133.cs | 9 + .../Job/Advanced/TestMid0140.cs | 139 ++ src/MIDTesters.Core/Job/TestMid0030.cs | 9 + src/MIDTesters.Core/Job/TestMid0031.cs | 27 + src/MIDTesters.Core/Job/TestMid0032.cs | 48 + src/MIDTesters.Core/Job/TestMid0033.cs | 88 + src/MIDTesters.Core/Job/TestMid0034.cs | 9 + src/MIDTesters.Core/Job/TestMid0035.cs | 101 + src/MIDTesters.Core/Job/TestMid0036.cs | 9 + src/MIDTesters.Core/Job/TestMid0037.cs | 9 + src/MIDTesters.Core/Job/TestMid0038.cs | 24 +- src/MIDTesters.Core/Job/TestMid0039.cs | 24 + src/MIDTesters.Core/KeepAlive/TestMid9999.cs | 11 + .../LinkCommunication/TestMid9997.cs | 12 + .../LinkCommunication/TestMid9998.cs | 13 + src/MIDTesters.Core/MidTester.cs | 122 +- .../MotorTuning/TestMid0500.cs | 9 + .../MotorTuning/TestMid0501.cs | 12 + .../MotorTuning/TestMid0502.cs | 9 + .../MotorTuning/TestMid0503.cs | 9 + .../MotorTuning/TestMid0504.cs | 9 + .../MultiSpindle/TestMid0090.cs | 9 + .../MultiSpindle/TestMid0091.cs | 22 + .../MultiSpindle/TestMid0092.cs | 9 + .../MultiSpindle/TestMid0093.cs | 9 + .../MultiSpindle/TestMid0100.cs | 34 + .../MultiSpindle/TestMid0101.cs | 64 + .../MultiSpindle/TestMid0102.cs | 9 + .../MultiSpindle/TestMid0103.cs | 9 + .../MultiSpindle/TestMid0104.cs | 12 + .../MultipleIdentifiers/TestMid0150.cs | 12 + .../MultipleIdentifiers/TestMid0151.cs | 9 + .../MultipleIdentifiers/TestMid0152.cs | 15 + .../MultipleIdentifiers/TestMid0153.cs | 9 + .../MultipleIdentifiers/TestMid0154.cs | 9 + .../MultipleIdentifiers/TestMid0155.cs | 9 + .../MultipleIdentifiers/TestMid0156.cs | 9 + .../MultipleIdentifiers/TestMid0157.cs | 9 + .../TestMid0420.cs | 9 + .../TestMid0421.cs | 12 + .../TestMid0422.cs | 9 + .../TestMid0423.cs | 9 + .../PLCUserData/TestMid0240.cs | 12 + .../PLCUserData/TestMid0241.cs | 9 + .../PLCUserData/TestMid0242.cs | 13 + .../PLCUserData/TestMid0243.cs | 9 + .../PLCUserData/TestMid0244.cs | 9 + .../PLCUserData/TestMid0245.cs | 13 + .../ParameterSet/TestMid0010.cs | 9 + .../ParameterSet/TestMid0011.cs | 14 + .../ParameterSet/TestMid0012.cs | 50 + .../ParameterSet/TestMid0013.cs | 53 + .../ParameterSet/TestMid0014.cs | 9 + .../ParameterSet/TestMid0015.cs | 38 +- .../ParameterSet/TestMid0016.cs | 9 + .../ParameterSet/TestMid0017.cs | 9 + .../ParameterSet/TestMid0018.cs | 12 + .../ParameterSet/TestMid0019.cs | 27 + .../ParameterSet/TestMid0020.cs | 12 + .../ParameterSet/TestMid0021.cs | 9 + .../ParameterSet/TestMid0022.cs | 13 + .../ParameterSet/TestMid0023.cs | 9 + .../ParameterSet/TestMid0024.cs | 9 + .../ParameterSet/TestMid2504.cs | 12 + .../ParameterSet/TestMid2505.cs | 20 + src/MIDTesters.Core/PowerMACS/TestMid0105.cs | 47 + src/MIDTesters.Core/PowerMACS/TestMid0106.cs | 98 + src/MIDTesters.Core/PowerMACS/TestMid0107.cs | 69 + src/MIDTesters.Core/PowerMACS/TestMid0108.cs | 12 + src/MIDTesters.Core/PowerMACS/TestMid0109.cs | 11 + src/MIDTesters.Core/Result/TestMid1201.cs | 174 +- src/MIDTesters.Core/Statistic/TestMid0300.cs | 13 + src/MIDTesters.Core/Statistic/TestMid0301.cs | 25 + src/MIDTesters.Core/Tightening/TestMid0060.cs | 11 + src/MIDTesters.Core/Tightening/TestMid0061.cs | 1662 +++++++++++++++++ src/MIDTesters.Core/Tightening/TestMid0062.cs | 11 + src/MIDTesters.Core/Tightening/TestMid0063.cs | 11 + src/MIDTesters.Core/Tightening/TestMid0064.cs | 25 + src/MIDTesters.Core/Tightening/TestMid0065.cs | 1198 ++++++++++++ src/MIDTesters.Core/Tightening/TestMid0066.cs | 26 + src/MIDTesters.Core/Tightening/TestMid0902.cs | 40 + src/MIDTesters.Core/Time/TestMid0080.cs | 9 + src/MIDTesters.Core/Time/TestMid0081.cs | 12 + src/MIDTesters.Core/Time/TestMid0082.cs | 12 + src/MIDTesters.Core/Tool/TestMid0040.cs | 23 + src/MIDTesters.Core/Tool/TestMid0041.cs | 206 ++ src/MIDTesters.Core/Tool/TestMid0042.cs | 22 + src/MIDTesters.Core/Tool/TestMid0043.cs | 21 + src/MIDTesters.Core/Tool/TestMid0044.cs | 9 + src/MIDTesters.Core/Tool/TestMid0045.cs | 27 + src/MIDTesters.Core/Tool/TestMid0046.cs | 12 + src/MIDTesters.Core/Tool/TestMid0047.cs | 12 + src/MIDTesters.Core/Tool/TestMid0048.cs | 13 + src/MIDTesters.Core/Tool/TestMid0701.cs | 30 + src/MIDTesters.Core/Tool/TestMid0702.cs | 44 + src/MIDTesters.Core/Tool/TestMid0703.cs | 45 + src/MIDTesters.Core/Tool/TestMid0704.cs | 41 +- .../UserInterface/TestMid0110.cs | 12 + .../UserInterface/TestMid0111.cs | 17 + .../UserInterface/TestMid0113.cs | 9 + src/MIDTesters.Core/Vin/TestMid0050.cs | 12 + src/MIDTesters.Core/Vin/TestMid0051.cs | 9 + src/MIDTesters.Core/Vin/TestMid0052.cs | 27 + src/MIDTesters.Core/Vin/TestMid0053.cs | 9 + src/MIDTesters.Core/Vin/TestMid0054.cs | 9 + 173 files changed, 6504 insertions(+), 27 deletions(-) diff --git a/src/MIDTesters.Core/Alarm/TestMid0070.cs b/src/MIDTesters.Core/Alarm/TestMid0070.cs index 5a0a854c..0f73e592 100644 --- a/src/MIDTesters.Core/Alarm/TestMid0070.cs +++ b/src/MIDTesters.Core/Alarm/TestMid0070.cs @@ -29,5 +29,14 @@ public void Mid0070ByteAllRevisions() Assert.IsTrue(mid.Header.NoAckFlag); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Pack")] + public void Mid0070PackAllRevisions() + { + string pack = @"002000700021 "; + + AssertBuildAndParse(pack, new Mid0070(2, noAckFlag: true)); + } } } diff --git a/src/MIDTesters.Core/Alarm/TestMid0071.cs b/src/MIDTesters.Core/Alarm/TestMid0071.cs index 958c67f3..9d0ba5c0 100644 --- a/src/MIDTesters.Core/Alarm/TestMid0071.cs +++ b/src/MIDTesters.Core/Alarm/TestMid0071.cs @@ -2,6 +2,7 @@ using OpenProtocolInterpreter.Alarm; using System; +using OpenProtocolInterpreter; namespace MIDTesters.Alarm { [TestClass] @@ -96,5 +97,52 @@ public void Mid0071ByteRevision3() Assert.AreEqual("Alarm Text ", mid.AlarmText); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0071PackRevision1() + { + string pack = @"00530071001 01E851021031042017-12-01:20:12:45"; + + AssertBuildAndParse(pack, new Mid0071(1) + { + ErrorCode = "E851", + ControllerReadyStatus = true, + ToolReadyStatus = true, + Time = new DateTime(2017, 12, 1, 20, 12, 45) + }); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0071PackRevision2() + { + string pack = @"00540071002 01E1021021031042017-12-01:20:12:45"; + + AssertBuildAndParse(pack, new Mid0071(2) + { + ErrorCode = "E1021", + ControllerReadyStatus = true, + ToolReadyStatus = true, + Time = new DateTime(2017, 12, 1, 20, 12, 45) + }); + } + + [TestMethod] + [TestCategory("Revision 3"), TestCategory("Pack")] + public void Mid0071PackRevision3() + { + string pack = @"01090071003 01E1021021031042017-12-01:20:12:4505106Alarm Text "; + + AssertBuildAndParse(pack, new Mid0071(3) + { + ErrorCode = "E1021", + ControllerReadyStatus = true, + ToolReadyStatus = true, + Time = new DateTime(2017, 12, 1, 20, 12, 45), + ToolHealth = ToolHealth.Ok, + AlarmText = "Alarm Text" + }); + } } } diff --git a/src/MIDTesters.Core/Alarm/TestMid0072.cs b/src/MIDTesters.Core/Alarm/TestMid0072.cs index c5349f25..803e33b1 100644 --- a/src/MIDTesters.Core/Alarm/TestMid0072.cs +++ b/src/MIDTesters.Core/Alarm/TestMid0072.cs @@ -29,5 +29,14 @@ public void Mid0072ByteAllRevisions() Assert.AreEqual(typeof(Mid0072), mid.GetType()); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Pack")] + public void Mid0072PackAllRevisions() + { + string pack = @"00200072002 "; + + AssertBuildAndParse(pack, new Mid0072(2)); + } } } diff --git a/src/MIDTesters.Core/Alarm/TestMid0073.cs b/src/MIDTesters.Core/Alarm/TestMid0073.cs index 799fe970..ef5b9808 100644 --- a/src/MIDTesters.Core/Alarm/TestMid0073.cs +++ b/src/MIDTesters.Core/Alarm/TestMid0073.cs @@ -29,5 +29,14 @@ public void Mid0073ByteAllRevisions() Assert.AreEqual(typeof(Mid0073), mid.GetType()); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Pack")] + public void Mid0073PackAllRevisions() + { + string pack = @"00200073002 "; + + AssertBuildAndParse(pack, new Mid0073(2)); + } } } diff --git a/src/MIDTesters.Core/Alarm/TestMid0074.cs b/src/MIDTesters.Core/Alarm/TestMid0074.cs index ca4243df..65e3ff02 100644 --- a/src/MIDTesters.Core/Alarm/TestMid0074.cs +++ b/src/MIDTesters.Core/Alarm/TestMid0074.cs @@ -52,5 +52,29 @@ public void Mid0074ByteRevision2() Assert.AreEqual("E8514", mid.ErrorCode); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0074PackRevision1() + { + string pack = @"00240074001 E851"; + + AssertBuildAndParse(pack, new Mid0074(1) + { + ErrorCode = "E851" + }); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0074PackRevision2() + { + string pack = @"00250074002 E8514"; + + AssertBuildAndParse(pack, new Mid0074(2) + { + ErrorCode = "E8514" + }); + } } } diff --git a/src/MIDTesters.Core/Alarm/TestMid0075.cs b/src/MIDTesters.Core/Alarm/TestMid0075.cs index 633aa4b8..8d4d0e2d 100644 --- a/src/MIDTesters.Core/Alarm/TestMid0075.cs +++ b/src/MIDTesters.Core/Alarm/TestMid0075.cs @@ -29,5 +29,14 @@ public void Mid0075ByteAllRevisions() Assert.AreEqual(typeof(Mid0075), mid.GetType()); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Pack")] + public void Mid0075PackAllRevisions() + { + string pack = @"00200075001 "; + + AssertBuildAndParse(pack, new Mid0075(1)); + } } } diff --git a/src/MIDTesters.Core/Alarm/TestMid0076.cs b/src/MIDTesters.Core/Alarm/TestMid0076.cs index adf4c4df..53b6f38f 100644 --- a/src/MIDTesters.Core/Alarm/TestMid0076.cs +++ b/src/MIDTesters.Core/Alarm/TestMid0076.cs @@ -103,5 +103,54 @@ public void Mid0076ByteRevision3() Assert.AreEqual(ToolHealth.Ok, mid.ToolHealth); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0076PackRevision1() + { + string pack = @"00560076001 01102E851031041052017-01-25:10:20:20"; + + AssertBuildAndParse(pack, new Mid0076(1) + { + AlarmStatus = true, + ErrorCode = "E851", + ControllerReadyStatus = true, + ToolReadyStatus = true, + Time = new DateTime(2017, 1, 25, 10, 20, 20) + }); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0076PackRevision2() + { + string pack = @"00570076002 01102 E851031041052017-01-25:10:20:20"; + + AssertBuildAndParse(pack, new Mid0076(2) + { + AlarmStatus = true, + ErrorCode = " E851", + ControllerReadyStatus = true, + ToolReadyStatus = true, + Time = new DateTime(2017, 1, 25, 10, 20, 20) + }); + } + + [TestMethod] + [TestCategory("Revision 3"), TestCategory("Pack")] + public void Mid0076PackRevision3() + { + string pack = @"00600076003 01102 E851031041052017-01-25:10:20:20061"; + + AssertBuildAndParse(pack, new Mid0076(3) + { + AlarmStatus = true, + ErrorCode = " E851", + ControllerReadyStatus = true, + ToolReadyStatus = true, + Time = new DateTime(2017, 1, 25, 10, 20, 20), + ToolHealth = ToolHealth.Ok + }); + } } } diff --git a/src/MIDTesters.Core/Alarm/TestMid0077.cs b/src/MIDTesters.Core/Alarm/TestMid0077.cs index e29ef62b..9263da8e 100644 --- a/src/MIDTesters.Core/Alarm/TestMid0077.cs +++ b/src/MIDTesters.Core/Alarm/TestMid0077.cs @@ -29,5 +29,14 @@ public void Mid0077ByteAllRevisions() Assert.AreEqual(typeof(Mid0077), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Pack")] + public void Mid0077PackAllRevisions() + { + string pack = @"00200077 "; + + AssertBuildAndParse(pack, new Mid0077(), true); + } } } diff --git a/src/MIDTesters.Core/Alarm/TestMid0078.cs b/src/MIDTesters.Core/Alarm/TestMid0078.cs index 96db85a9..e2c5557c 100644 --- a/src/MIDTesters.Core/Alarm/TestMid0078.cs +++ b/src/MIDTesters.Core/Alarm/TestMid0078.cs @@ -29,5 +29,14 @@ public void Mid0078ByteAllRevisions() Assert.AreEqual(typeof(Mid0078), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Pack")] + public void Mid0078PackAllRevisions() + { + string pack = @"00200078 "; + + AssertBuildAndParse(pack, new Mid0078(), true); + } } } diff --git a/src/MIDTesters.Core/Alarm/TestMid1000.cs b/src/MIDTesters.Core/Alarm/TestMid1000.cs index 92131448..b012d933 100644 --- a/src/MIDTesters.Core/Alarm/TestMid1000.cs +++ b/src/MIDTesters.Core/Alarm/TestMid1000.cs @@ -3,6 +3,7 @@ using OpenProtocolInterpreter.Alarm; using System; +using System.Collections.Generic; namespace MIDTesters.Alarm { [TestClass] @@ -71,5 +72,24 @@ public void Mid1000ByteRevision1() AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid1000PackRevision1() + { + string pack = "00911000001 ABCDE2017-12-01:20:12:4500201700009040000000ALARMTEXT017010010100000001"; + + AssertBuildAndParse(pack, new Mid1000(1) + { + AlarmCode = "ABCDE", + Time = new DateTime(2017, 12, 1, 20, 12, 45), + NumberOfDataFields = 2, + AlarmDataFields = new List() + { + new VariableDataField() { ParameterId = 1700, Length = 9, DataType = DataTypeDefinition.String, Unit = DataUnitType.NoUnit, StepNumber = 0, DataValue = "ALARMTEXT" }, + new VariableDataField() { ParameterId = 1701, Length = 1, DataType = DataTypeDefinition.UnsignedInteger, Unit = DataUnitType.NoUnit, StepNumber = 0, DataValue = "1" } + } + }); + } } } diff --git a/src/MIDTesters.Core/Alarm/TestMid1001.cs b/src/MIDTesters.Core/Alarm/TestMid1001.cs index 6f11c29e..d864dcdf 100644 --- a/src/MIDTesters.Core/Alarm/TestMid1001.cs +++ b/src/MIDTesters.Core/Alarm/TestMid1001.cs @@ -29,5 +29,14 @@ public void Mid1001ByteAllRevisions() Assert.AreEqual(typeof(Mid1001), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Pack")] + public void Mid1001PackAllRevisions() + { + string pack = @"00201001 "; + + AssertBuildAndParse(pack, new Mid1001(), true); + } } } diff --git a/src/MIDTesters.Core/ApplicationController/TestMid0270.cs b/src/MIDTesters.Core/ApplicationController/TestMid0270.cs index e44a9f07..b8e2cf32 100644 --- a/src/MIDTesters.Core/ApplicationController/TestMid0270.cs +++ b/src/MIDTesters.Core/ApplicationController/TestMid0270.cs @@ -29,5 +29,14 @@ public void Mid0270ByteRevision1() Assert.AreEqual(typeof(Mid0270), mid.GetType()); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0270PackRevision1() + { + string package = "00200270001 "; + + AssertBuildAndParse(package, new Mid0270()); + } } } diff --git a/src/MIDTesters.Core/ApplicationSelector/TestMid0250.cs b/src/MIDTesters.Core/ApplicationSelector/TestMid0250.cs index e244baab..090dd90f 100644 --- a/src/MIDTesters.Core/ApplicationSelector/TestMid0250.cs +++ b/src/MIDTesters.Core/ApplicationSelector/TestMid0250.cs @@ -31,5 +31,14 @@ public void Mid0250ByteRevision1() Assert.IsTrue(mid.Header.NoAckFlag); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0250PackRevision1() + { + string package = "00200250 1 "; + + AssertBuildAndParse(package, new Mid0250(noAckFlag: true), true); + } } } diff --git a/src/MIDTesters.Core/ApplicationSelector/TestMid0251.cs b/src/MIDTesters.Core/ApplicationSelector/TestMid0251.cs index ecd81c92..569cc6f0 100644 --- a/src/MIDTesters.Core/ApplicationSelector/TestMid0251.cs +++ b/src/MIDTesters.Core/ApplicationSelector/TestMid0251.cs @@ -59,5 +59,20 @@ public void Mid0251ByteRevision1() }, mid.SocketStatus); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0251PackRevision1() + { + string package = "00400251 1 01500210030101101110"; + + AssertBuildAndParse(package, new Mid0251() + { + Header = { NoAckFlag = true }, + DeviceId = 50, + NumberOfSockets = 10, + SocketStatus = new List() { false, true, false, true, true, false, true, true, true, false } + }, true); + } } } diff --git a/src/MIDTesters.Core/ApplicationSelector/TestMid0252.cs b/src/MIDTesters.Core/ApplicationSelector/TestMid0252.cs index 5d49289c..2ce1cd4e 100644 --- a/src/MIDTesters.Core/ApplicationSelector/TestMid0252.cs +++ b/src/MIDTesters.Core/ApplicationSelector/TestMid0252.cs @@ -29,5 +29,14 @@ public void Mid0252ByteRevision1() Assert.AreEqual(typeof(Mid0252), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0252PackRevision1() + { + string package = "00200252 "; + + AssertBuildAndParse(package, new Mid0252(), true); + } } } diff --git a/src/MIDTesters.Core/ApplicationSelector/TestMid0253.cs b/src/MIDTesters.Core/ApplicationSelector/TestMid0253.cs index 882f5e67..5c6403a7 100644 --- a/src/MIDTesters.Core/ApplicationSelector/TestMid0253.cs +++ b/src/MIDTesters.Core/ApplicationSelector/TestMid0253.cs @@ -29,5 +29,14 @@ public void Mid0253ByteRevision1() Assert.AreEqual(typeof(Mid0253), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0253PackRevision1() + { + string package = "00200253 "; + + AssertBuildAndParse(package, new Mid0253(), true); + } } } diff --git a/src/MIDTesters.Core/ApplicationSelector/TestMid0254.cs b/src/MIDTesters.Core/ApplicationSelector/TestMid0254.cs index 298b2e56..dd814606 100644 --- a/src/MIDTesters.Core/ApplicationSelector/TestMid0254.cs +++ b/src/MIDTesters.Core/ApplicationSelector/TestMid0254.cs @@ -45,5 +45,22 @@ public void Mid0254ByteRevision1() }, mid.GreenLights); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0254PackRevision1() + { + string package = "00340254 01110201221022"; + + AssertBuildAndParse(package, new Mid0254() + { + DeviceId = 11, + GreenLights = new List() + { + LightCommand.Off, LightCommand.Steady, LightCommand.Flashing, LightCommand.Flashing, + LightCommand.Steady, LightCommand.Off, LightCommand.Flashing, LightCommand.Flashing + } + }, true); + } } } diff --git a/src/MIDTesters.Core/ApplicationSelector/TestMid0255.cs b/src/MIDTesters.Core/ApplicationSelector/TestMid0255.cs index af3c831d..5cef2769 100644 --- a/src/MIDTesters.Core/ApplicationSelector/TestMid0255.cs +++ b/src/MIDTesters.Core/ApplicationSelector/TestMid0255.cs @@ -53,5 +53,22 @@ public void Mid0255ByteRevision1() }, mid.RedLights); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0255PackRevision1() + { + string package = "00340255 01510221112022"; + + AssertBuildAndParse(package, new Mid0255() + { + DeviceId = 51, + RedLights = new List() + { + LightCommand.Flashing, LightCommand.Steady, LightCommand.Steady, LightCommand.Steady, + LightCommand.Flashing, LightCommand.Off, LightCommand.Flashing, LightCommand.Flashing + } + }, true); + } } } diff --git a/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0260.cs b/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0260.cs index 5d9406f9..2f468829 100644 --- a/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0260.cs +++ b/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0260.cs @@ -29,5 +29,14 @@ public void Mid0260ByteRevision1() Assert.AreEqual(typeof(Mid0260), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0260PackRevision1() + { + string package = "00200260 "; + + AssertBuildAndParse(package, new Mid0260(), true); + } } } diff --git a/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0261.cs b/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0261.cs index ff2e734d..dd23d33b 100644 --- a/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0261.cs +++ b/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0261.cs @@ -31,5 +31,14 @@ public void Mid0261ByteRevision1() Assert.IsTrue(mid.Header.NoAckFlag); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0261PackRevision1() + { + string package = "00200261 1 "; + + AssertBuildAndParse(package, new Mid0261(noAckFlag: true), true); + } } } diff --git a/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0262.cs b/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0262.cs index 4d8339d0..0b6b3a24 100644 --- a/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0262.cs +++ b/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0262.cs @@ -29,5 +29,17 @@ public void Mid0262ByteRevision1() Assert.AreEqual("3200078D", mid.ToolTagId); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0262PackRevision1() + { + string package = "00300262001 013200078D"; + + AssertBuildAndParse(package, new Mid0262() + { + ToolTagId = "3200078D" + }); + } } } diff --git a/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0263.cs b/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0263.cs index 64687345..fd5e1717 100644 --- a/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0263.cs +++ b/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0263.cs @@ -29,5 +29,14 @@ public void Mid0263ByteRevision1() Assert.AreEqual(typeof(Mid0263), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0263PackRevision1() + { + string package = "00200263 "; + + AssertBuildAndParse(package, new Mid0263(), true); + } } } diff --git a/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0264.cs b/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0264.cs index c9e6321e..d326f9d8 100644 --- a/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0264.cs +++ b/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0264.cs @@ -29,5 +29,14 @@ public void Mid0264ByteRevision1() Assert.AreEqual(typeof(Mid0264), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0264PackRevision1() + { + string package = "00200264 "; + + AssertBuildAndParse(package, new Mid0264(), true); + } } } diff --git a/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0265.cs b/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0265.cs index 854b2817..7158a224 100644 --- a/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0265.cs +++ b/src/MIDTesters.Core/ApplicationToolLocationSystem/TestMid0265.cs @@ -32,5 +32,18 @@ public void Mid0265ByteRevision1() Assert.AreEqual(ToolStatus.Inoperable, mid.ToolStatus); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0265PackRevision1() + { + string package = "00340265001 013200078D0202"; + + AssertBuildAndParse(package, new Mid0265() + { + ToolTagId = "3200078D", + ToolStatus = ToolStatus.Inoperable + }); + } } } diff --git a/src/MIDTesters.Core/AutomaticManualMode/TestMid0400.cs b/src/MIDTesters.Core/AutomaticManualMode/TestMid0400.cs index 5c61e97a..e14c2f97 100644 --- a/src/MIDTesters.Core/AutomaticManualMode/TestMid0400.cs +++ b/src/MIDTesters.Core/AutomaticManualMode/TestMid0400.cs @@ -31,5 +31,14 @@ public void Mid0400ByteRevision1() Assert.IsTrue(mid.Header.NoAckFlag); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0400PackRevision1() + { + string package = "00200400 1 "; + + AssertBuildAndParse(package, new Mid0400(noAckFlag: true), true); + } } } diff --git a/src/MIDTesters.Core/AutomaticManualMode/TestMid0401.cs b/src/MIDTesters.Core/AutomaticManualMode/TestMid0401.cs index 307965ac..8f66b601 100644 --- a/src/MIDTesters.Core/AutomaticManualMode/TestMid0401.cs +++ b/src/MIDTesters.Core/AutomaticManualMode/TestMid0401.cs @@ -29,5 +29,18 @@ public void Mid0401ByteRevision1() Assert.IsTrue(mid.ManualAutomaticMode); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0401PackRevision1() + { + string package = "00210401 1 1"; + + AssertBuildAndParse(package, new Mid0401() + { + Header = { NoAckFlag = true }, + ManualAutomaticMode = true + }, true); + } } } diff --git a/src/MIDTesters.Core/AutomaticManualMode/TestMid0402.cs b/src/MIDTesters.Core/AutomaticManualMode/TestMid0402.cs index 2305c9e3..40799294 100644 --- a/src/MIDTesters.Core/AutomaticManualMode/TestMid0402.cs +++ b/src/MIDTesters.Core/AutomaticManualMode/TestMid0402.cs @@ -29,5 +29,14 @@ public void Mid0402ByteRevision1() Assert.AreEqual(typeof(Mid0402), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0402PackRevision1() + { + string package = "00200402 "; + + AssertBuildAndParse(package, new Mid0402(), true); + } } } diff --git a/src/MIDTesters.Core/AutomaticManualMode/TestMid0403.cs b/src/MIDTesters.Core/AutomaticManualMode/TestMid0403.cs index 0adb4741..1eafd083 100644 --- a/src/MIDTesters.Core/AutomaticManualMode/TestMid0403.cs +++ b/src/MIDTesters.Core/AutomaticManualMode/TestMid0403.cs @@ -29,5 +29,14 @@ public void Mid0403ByteRevision1() Assert.AreEqual(typeof(Mid0403), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0403PackRevision1() + { + string package = "00200403 "; + + AssertBuildAndParse(package, new Mid0403(), true); + } } } diff --git a/src/MIDTesters.Core/AutomaticManualMode/TestMid0410.cs b/src/MIDTesters.Core/AutomaticManualMode/TestMid0410.cs index 7b8e1a6f..5ac40d81 100644 --- a/src/MIDTesters.Core/AutomaticManualMode/TestMid0410.cs +++ b/src/MIDTesters.Core/AutomaticManualMode/TestMid0410.cs @@ -29,5 +29,14 @@ public void Mid0410ByteRevision1() Assert.AreEqual(typeof(Mid0410), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0410PackRevision1() + { + string package = "00200410 "; + + AssertBuildAndParse(package, new Mid0410(), true); + } } } diff --git a/src/MIDTesters.Core/AutomaticManualMode/TestMid0411.cs b/src/MIDTesters.Core/AutomaticManualMode/TestMid0411.cs index 48abc4ac..de8b10c3 100644 --- a/src/MIDTesters.Core/AutomaticManualMode/TestMid0411.cs +++ b/src/MIDTesters.Core/AutomaticManualMode/TestMid0411.cs @@ -31,5 +31,18 @@ public void Mid0411ByteRevision1() Assert.AreEqual(5, mid.CurrentBatch); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0411PackRevision1() + { + string package = "00240411 0105"; + + AssertBuildAndParse(package, new Mid0411() + { + AutoDisableSetting = 1, + CurrentBatch = 5 + }, true); + } } } diff --git a/src/MIDTesters.Core/Communication/TestMid0001.cs b/src/MIDTesters.Core/Communication/TestMid0001.cs index fce6c921..e292ab4b 100644 --- a/src/MIDTesters.Core/Communication/TestMid0001.cs +++ b/src/MIDTesters.Core/Communication/TestMid0001.cs @@ -52,5 +52,26 @@ public void Mid0001ByteRevision7() Assert.IsTrue(mid.OptionalKeepAlive); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Pack")] + public void Mid0001PackAllRevisions() + { + var package = "00200001003 "; + + AssertBuildAndParse(package, new Mid0001(3)); + } + + [TestMethod] + [TestCategory("Revision 7"), TestCategory("Pack")] + public void Mid0001PackRevision7() + { + var package = "00230001007 011"; + + AssertBuildAndParse(package, new Mid0001(7) + { + OptionalKeepAlive = true + }); + } } } diff --git a/src/MIDTesters.Core/Communication/TestMid0002.cs b/src/MIDTesters.Core/Communication/TestMid0002.cs index f4423b05..14a6f40f 100644 --- a/src/MIDTesters.Core/Communication/TestMid0002.cs +++ b/src/MIDTesters.Core/Communication/TestMid0002.cs @@ -288,5 +288,149 @@ public void Mid0002ByteRevision7() Assert.IsTrue(mid.OptionalKeepAlive); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0002PackRevision1() + { + string pack = @"00570002001 010001020103Airbag1 "; + + AssertBuildAndParse(pack, new Mid0002(1) + { + CellId = 1, + ChannelId = 1, + ControllerName = "Airbag1" + }); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0002PackRevision2() + { + string pack = @"00620002002 010001020103Airbag1 04ACT"; + + AssertBuildAndParse(pack, new Mid0002(2) + { + CellId = 1, + ChannelId = 1, + ControllerName = "Airbag1", + SupplierCode = "ACT" + }); + } + + [TestMethod] + [TestCategory("Revision 3"), TestCategory("Pack")] + public void Mid0002PackRevision3() + { + string pack = @"01250002003 010001020103Airbag1 04ACT05OpenProtocolVersion06Version 19.0.0.0 07Version 01.0.0.0 "; + + AssertBuildAndParse(pack, new Mid0002(3) + { + CellId = 1, + ChannelId = 1, + ControllerName = "Airbag1", + SupplierCode = "ACT", + OpenProtocolVersion = "OpenProtocolVersion", + ControllerSoftwareVersion = "Version 19.0.0.0", + ToolSoftwareVersion = "Version 01.0.0.0" + }); + } + + [TestMethod] + [TestCategory("Revision 4"), TestCategory("Pack")] + public void Mid0002PackRevision4() + { + string pack = @"01630002004 010001020103Airbag1 04ACT05OpenProtocolVersion06Version 19.0.0.0 07Version 01.0.0.0 08RBUType 09Serial "; + + AssertBuildAndParse(pack, new Mid0002(4) + { + CellId = 1, + ChannelId = 1, + ControllerName = "Airbag1", + SupplierCode = "ACT", + OpenProtocolVersion = "OpenProtocolVersion", + ControllerSoftwareVersion = "Version 19.0.0.0", + ToolSoftwareVersion = "Version 01.0.0.0", + RBUType = "RBUType", + ControllerSerialNumber = "Serial" + }); + } + + [TestMethod] + [TestCategory("Revision 5"), TestCategory("Pack")] + public void Mid0002PackRevision5() + { + string pack = @"01730002005 010001020103Airbag1 04ACT05OpenProtocolVersion06Version 19.0.0.0 07Version 01.0.0.0 08RBUType 09Serial 1000211002"; + + AssertBuildAndParse(pack, new Mid0002(5) + { + CellId = 1, + ChannelId = 1, + ControllerName = "Airbag1", + SupplierCode = "ACT", + OpenProtocolVersion = "OpenProtocolVersion", + ControllerSoftwareVersion = "Version 19.0.0.0", + ToolSoftwareVersion = "Version 01.0.0.0", + RBUType = "RBUType", + ControllerSerialNumber = "Serial", + SystemType = SystemType.PowerMacs4000, + SystemSubType = SystemSubType.SystemRunningPresses + }); + } + + [TestMethod] + [TestCategory("Revision 6"), TestCategory("Pack")] + public void Mid0002PackRevision6() + { + string pack = @"02210002006 010001020103Airbag1 04ACT05OpenProtocolVersion06Version 19.0.0.0 07Version 01.0.0.0 08RBUType 09Serial 100021100212013114429496729515Station Or Cell Name 16A"; + + AssertBuildAndParse(pack, new Mid0002(6) + { + CellId = 1, + ChannelId = 1, + ControllerName = "Airbag1", + SupplierCode = "ACT", + OpenProtocolVersion = "OpenProtocolVersion", + ControllerSoftwareVersion = "Version 19.0.0.0", + ToolSoftwareVersion = "Version 01.0.0.0", + RBUType = "RBUType", + ControllerSerialNumber = "Serial", + SystemType = SystemType.PowerMacs4000, + SystemSubType = SystemSubType.SystemRunningPresses, + SequenceNumberSupport = false, + LinkingHandlingSupport = true, + StationCellId = 4294967295L, + StationCellName = "Station Or Cell Name", + ClientId = "A" + }); + } + + [TestMethod] + [TestCategory("Revision 7"), TestCategory("Pack")] + public void Mid0002PackRevision7() + { + string pack = @"02240002007 010001020103Airbag1 04ACT05OpenProtocolVersion06Version 19.0.0.0 07Version 01.0.0.0 08RBUType 09Serial 100021100212013114429496729515Station Or Cell Name 16A171"; + + AssertBuildAndParse(pack, new Mid0002(7) + { + CellId = 1, + ChannelId = 1, + ControllerName = "Airbag1", + SupplierCode = "ACT", + OpenProtocolVersion = "OpenProtocolVersion", + ControllerSoftwareVersion = "Version 19.0.0.0", + ToolSoftwareVersion = "Version 01.0.0.0", + RBUType = "RBUType", + ControllerSerialNumber = "Serial", + SystemType = SystemType.PowerMacs4000, + SystemSubType = SystemSubType.SystemRunningPresses, + SequenceNumberSupport = false, + LinkingHandlingSupport = true, + StationCellId = 4294967295L, + StationCellName = "Station Or Cell Name", + ClientId = "A", + OptionalKeepAlive = true + }); + } } } diff --git a/src/MIDTesters.Core/Communication/TestMid0003.cs b/src/MIDTesters.Core/Communication/TestMid0003.cs index 26e9239d..c9b7f9ce 100644 --- a/src/MIDTesters.Core/Communication/TestMid0003.cs +++ b/src/MIDTesters.Core/Communication/TestMid0003.cs @@ -27,5 +27,14 @@ public void Mid0003ByteRevision1() AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0003PackRevision1() + { + string pack = @"00200003001 "; + + AssertBuildAndParse(pack, new Mid0003()); + } } } diff --git a/src/MIDTesters.Core/Communication/TestMid0004.cs b/src/MIDTesters.Core/Communication/TestMid0004.cs index 9cf6d9f7..051ca64b 100644 --- a/src/MIDTesters.Core/Communication/TestMid0004.cs +++ b/src/MIDTesters.Core/Communication/TestMid0004.cs @@ -57,5 +57,31 @@ public void Mid0004ByteRevision2() Assert.AreEqual(Error.JobNotRunning, mid.ErrorCode); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0004PackRevision1() + { + string pack = @"00260004 001802"; + + AssertBuildAndParse(pack, new Mid0004(1) + { + FailedMid = 18, + ErrorCode = Error.ParameterSetIdNotPresent + }, true); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0004PackRevision2() + { + string pack = @"00270004002 0018021"; + + AssertBuildAndParse(pack, new Mid0004(2) + { + FailedMid = 18, + ErrorCode = Error.JobNotRunning + }); + } } } diff --git a/src/MIDTesters.Core/Communication/TestMid0005.cs b/src/MIDTesters.Core/Communication/TestMid0005.cs index a011ec80..c046c59a 100644 --- a/src/MIDTesters.Core/Communication/TestMid0005.cs +++ b/src/MIDTesters.Core/Communication/TestMid0005.cs @@ -29,5 +29,17 @@ public void Mid0005ByteRevision1() Assert.AreEqual(18, mid.MidAccepted); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0005PackRevision1() + { + string pack = @"00240005 0018"; + + AssertBuildAndParse(pack, new Mid0005(1) + { + MidAccepted = 18 + }, true); + } } } diff --git a/src/MIDTesters.Core/Communication/TestMid0006.cs b/src/MIDTesters.Core/Communication/TestMid0006.cs index 3b01e472..27a92304 100644 --- a/src/MIDTesters.Core/Communication/TestMid0006.cs +++ b/src/MIDTesters.Core/Communication/TestMid0006.cs @@ -38,5 +38,19 @@ public void Mid0006ByteRevision1() Assert.AreEqual("lengthequals14", mid.ExtraData); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0006PackRevision1() + { + string pack = @"00430006 001800214lengthequals14"; + + AssertBuildAndParse(pack, new Mid0006() + { + RequestedMid = 18, + WantedRevision = 2, + ExtraData = "lengthequals14" + }, true); + } } } diff --git a/src/MIDTesters.Core/Communication/TestMid0008.cs b/src/MIDTesters.Core/Communication/TestMid0008.cs index c5377821..a05c9fbe 100644 --- a/src/MIDTesters.Core/Communication/TestMid0008.cs +++ b/src/MIDTesters.Core/Communication/TestMid0008.cs @@ -35,5 +35,19 @@ public void Mid0008ByteRevision1() Assert.AreEqual("lengthequals14", mid.ExtraData); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0008PackRevision1() + { + string pack = @"00430008 002200214lengthequals14"; + + AssertBuildAndParse(pack, new Mid0008() + { + SubscriptionMid = 22, + WantedRevision = 2, + ExtraData = "lengthequals14" + }, true); + } } } diff --git a/src/MIDTesters.Core/Communication/TestMid0009.cs b/src/MIDTesters.Core/Communication/TestMid0009.cs index c9fb760b..131e9808 100644 --- a/src/MIDTesters.Core/Communication/TestMid0009.cs +++ b/src/MIDTesters.Core/Communication/TestMid0009.cs @@ -35,5 +35,19 @@ public void Mid0009ByteRevision1() Assert.AreEqual("lengthequals14", mid.ExtraData); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0009PackRevision1() + { + string pack = @"00430009 002200214lengthequals14"; + + AssertBuildAndParse(pack, new Mid0009() + { + UnsubscriptionMid = 22, + ExtraDataRevision = 2, + ExtraData = "lengthequals14" + }, true); + } } } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0200.cs b/src/MIDTesters.Core/IOInterface/TestMid0200.cs index c99f3af7..71369d90 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0200.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0200.cs @@ -48,5 +48,26 @@ public void Mid0200ByteRevision1() Assert.AreEqual(RelayStatus.Off, mid.StatusRelayTen); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0200PackRevision1() + { + string package = "00300200 1231231230"; + + AssertBuildAndParse(package, new Mid0200() + { + StatusRelayOne = RelayStatus.On, + StatusRelayTwo = RelayStatus.Flashing, + StatusRelayThree = RelayStatus.KeepCurrentStatus, + StatusRelayFour = RelayStatus.On, + StatusRelayFive = RelayStatus.Flashing, + StatusRelaySix = RelayStatus.KeepCurrentStatus, + StatusRelaySeven = RelayStatus.On, + StatusRelayEight = RelayStatus.Flashing, + StatusRelayNine = RelayStatus.KeepCurrentStatus, + StatusRelayTen = RelayStatus.Off + }, true); + } } } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0210.cs b/src/MIDTesters.Core/IOInterface/TestMid0210.cs index fe034936..87481642 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0210.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0210.cs @@ -31,5 +31,14 @@ public void Mid0210ByteRevision1() Assert.IsTrue(mid.Header.NoAckFlag); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0210PackRevision1() + { + string package = "00200210 1 "; + + AssertBuildAndParse(package, new Mid0210(noAckFlag: true), true); + } } } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0211.cs b/src/MIDTesters.Core/IOInterface/TestMid0211.cs index e9b705de..03dd47f9 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0211.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0211.cs @@ -45,5 +45,25 @@ public void Mid0211ByteRevision1() Assert.IsTrue(mid.StatusDigInEight); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0211PackRevision1() + { + string package = "00280211 1 10101011"; + + AssertBuildAndParse(package, new Mid0211() + { + Header = { NoAckFlag = true }, + StatusDigInOne = true, + StatusDigInTwo = false, + StatusDigInThree = true, + StatusDigInFour = false, + StatusDigInFive = true, + StatusDigInSix = false, + StatusDigInSeven = true, + StatusDigInEight = true + }, true); + } } } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0212.cs b/src/MIDTesters.Core/IOInterface/TestMid0212.cs index ba0c6231..91a18bf0 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0212.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0212.cs @@ -29,5 +29,14 @@ public void Mid0212ByteRevision1() Assert.AreEqual(typeof(Mid0212), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0212PackRevision1() + { + string package = "00200212 "; + + AssertBuildAndParse(package, new Mid0212(), true); + } } } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0213.cs b/src/MIDTesters.Core/IOInterface/TestMid0213.cs index 7256d8f6..9e9c5b20 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0213.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0213.cs @@ -29,5 +29,14 @@ public void Mid0213ByteRevision1() Assert.AreEqual(typeof(Mid0213), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0213PackRevision1() + { + string package = "00200213 "; + + AssertBuildAndParse(package, new Mid0213(), true); + } } } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0214.cs b/src/MIDTesters.Core/IOInterface/TestMid0214.cs index 6e2632c5..8ace127b 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0214.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0214.cs @@ -29,5 +29,17 @@ public void Mid0214ByteAllRevisions() Assert.AreEqual(10, mid.DeviceNumber); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Pack")] + public void Mid0214PackAllRevisions() + { + string package = "00220214002 10"; + + AssertBuildAndParse(package, new Mid0214(2) + { + DeviceNumber = 10 + }); + } } } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0215.cs b/src/MIDTesters.Core/IOInterface/TestMid0215.cs index bce76244..ad58419b 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0215.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0215.cs @@ -1,6 +1,8 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter.IOInterface; +using System.Collections.Generic; +using OpenProtocolInterpreter; namespace MIDTesters.IOInterface { [TestClass] @@ -68,5 +70,73 @@ public void Mid0215ByteRevision2() AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0215PackRevision1() + { + string package = "00920215001 010302001000210031004010012000300140010300110020003100410051006100700080"; + + AssertBuildAndParse(package, new Mid0215(1) + { + IODeviceId = 3, + Relays = new List() + { + new Relay(RelayNumber.Ok, false), + new Relay(RelayNumber.Nok, true), + new Relay(RelayNumber.Low, true), + new Relay(RelayNumber.High, false), + new Relay(RelayNumber.ExternalControlled8, true), + new Relay((RelayNumber)200, false), + new Relay(RelayNumber.TrackingDisabled, true), + new Relay((RelayNumber)400, true) + }, + DigitalInputs = new List() + { + new DigitalInput(DigitalInputNumber.ResetBatch, true), + new DigitalInput(DigitalInputNumber.UnlockTool, false), + new DigitalInput(DigitalInputNumber.ToolDisableNo, true), + new DigitalInput(DigitalInputNumber.ToolDisableNc, true), + new DigitalInput(DigitalInputNumber.ToolTighteningDisable, true), + new DigitalInput(DigitalInputNumber.ToolLooseningDisable, true), + new DigitalInput(DigitalInputNumber.RemoteStartPulse, false), + new DigitalInput(DigitalInputNumber.RemoteStartCont, false) + } + }); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0215PackRevision2() + { + string package = @"00920215002 010302070300100021003100400051006000710407050011002000310041005100610070"; + + AssertBuildAndParse(package, new Mid0215(2) + { + IODeviceId = 3, + NumberOfRelays = 7, + Relays = new List() + { + new Relay(RelayNumber.Ok, false), + new Relay(RelayNumber.Nok, true), + new Relay(RelayNumber.Low, true), + new Relay(RelayNumber.High, false), + new Relay(RelayNumber.LowTorque, true), + new Relay(RelayNumber.HighTorque, false), + new Relay(RelayNumber.LowAngle, true) + }, + NumberOfDigitalInputs = 7, + DigitalInputs = new List() + { + new DigitalInput(DigitalInputNumber.ResetBatch, true), + new DigitalInput(DigitalInputNumber.UnlockTool, false), + new DigitalInput(DigitalInputNumber.ToolDisableNo, true), + new DigitalInput(DigitalInputNumber.ToolDisableNc, true), + new DigitalInput(DigitalInputNumber.ToolTighteningDisable, true), + new DigitalInput(DigitalInputNumber.ToolLooseningDisable, true), + new DigitalInput(DigitalInputNumber.RemoteStartPulse, false) + } + }); + } } } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0216.cs b/src/MIDTesters.Core/IOInterface/TestMid0216.cs index 6dfc475b..1f03819e 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0216.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0216.cs @@ -30,5 +30,17 @@ public void Mid0216ByteRevision1() Assert.AreEqual(RelayNumber.ToolRunning, mid.RelayNumber); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0216PackRevision1() + { + string package = "00230216 1 026"; + + AssertBuildAndParse(package, new Mid0216(noAckFlag: true) + { + RelayNumber = RelayNumber.ToolRunning + }, true); + } } } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0217.cs b/src/MIDTesters.Core/IOInterface/TestMid0217.cs index 9deb8db6..c98ebf0b 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0217.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0217.cs @@ -32,5 +32,19 @@ public void Mid0217ByteRevision1() Assert.IsTrue(mid.RelayStatus); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0217PackRevision1() + { + string package = "00280217 1 01026021"; + + AssertBuildAndParse(package, new Mid0217() + { + Header = { NoAckFlag = true }, + RelayNumber = RelayNumber.ToolRunning, + RelayStatus = true + }, true); + } } } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0218.cs b/src/MIDTesters.Core/IOInterface/TestMid0218.cs index d3f8cb3c..5bbd5483 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0218.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0218.cs @@ -29,5 +29,14 @@ public void Mid0218ByteRevision1() Assert.AreEqual(typeof(Mid0218), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0218PackRevision1() + { + string package = "00200218 "; + + AssertBuildAndParse(package, new Mid0218(), true); + } } } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0219.cs b/src/MIDTesters.Core/IOInterface/TestMid0219.cs index 8b2d7dae..ce8ca4d9 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0219.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0219.cs @@ -30,5 +30,17 @@ public void Mid0219ByteRevision1() Assert.AreEqual(RelayNumber.ExternalControlled10, mid.RelayNumber); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0219PackRevision1() + { + string package = "00230219 102"; + + AssertBuildAndParse(package, new Mid0219() + { + RelayNumber = RelayNumber.ExternalControlled10 + }, true); + } } } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0220.cs b/src/MIDTesters.Core/IOInterface/TestMid0220.cs index 2844bcdd..b3e70df5 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0220.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0220.cs @@ -30,5 +30,17 @@ public void Mid0220ByteRevision1() Assert.AreEqual(DigitalInputNumber.ForcedCcwOnce, mid.DigitalInputNumber); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0220PackRevision1() + { + string package = "00230220 120"; + + AssertBuildAndParse(package, new Mid0220() + { + DigitalInputNumber = DigitalInputNumber.ForcedCcwOnce + }, true); + } } } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0221.cs b/src/MIDTesters.Core/IOInterface/TestMid0221.cs index 610c5bbb..0d15ec48 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0221.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0221.cs @@ -32,5 +32,18 @@ public void Mid0221ByteRevision1() Assert.IsTrue(mid.DigitalInputStatus); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0221PackRevision1() + { + string package = "00280221 01120021"; + + AssertBuildAndParse(package, new Mid0221() + { + DigitalInputNumber = DigitalInputNumber.ForcedCcwOnce, + DigitalInputStatus = true + }, true); + } } } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0222.cs b/src/MIDTesters.Core/IOInterface/TestMid0222.cs index c9d94aad..7ea1f623 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0222.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0222.cs @@ -29,5 +29,14 @@ public void Mid0222ByteRevision1() Assert.AreEqual(typeof(Mid0222), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0222PackRevision1() + { + string package = "00200222 "; + + AssertBuildAndParse(package, new Mid0222(), true); + } } } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0223.cs b/src/MIDTesters.Core/IOInterface/TestMid0223.cs index b8c3e6fd..2f1da974 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0223.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0223.cs @@ -30,5 +30,17 @@ public void Mid0223ByteRevision1() Assert.AreEqual(DigitalInputNumber.IdCard, mid.DigitalInputNumber); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0223PackRevision1() + { + string package = "00230223 066"; + + AssertBuildAndParse(package, new Mid0223() + { + DigitalInputNumber = DigitalInputNumber.IdCard + }, true); + } } } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0224.cs b/src/MIDTesters.Core/IOInterface/TestMid0224.cs index cc50ee7e..17d62ed3 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0224.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0224.cs @@ -30,5 +30,17 @@ public void Mid0224ByteRevision1() Assert.AreEqual(DigitalInputNumber.ClickWrench4, mid.DigitalInputNumber); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0224PackRevision1() + { + string package = "00230224 065"; + + AssertBuildAndParse(package, new Mid0224() + { + DigitalInputNumber = DigitalInputNumber.ClickWrench4 + }, true); + } } } diff --git a/src/MIDTesters.Core/IOInterface/TestMid0225.cs b/src/MIDTesters.Core/IOInterface/TestMid0225.cs index bb4e4fee..b2a98909 100644 --- a/src/MIDTesters.Core/IOInterface/TestMid0225.cs +++ b/src/MIDTesters.Core/IOInterface/TestMid0225.cs @@ -30,5 +30,17 @@ public void Mid0225ByteRevision1() Assert.AreEqual(DigitalInputNumber.EndOfCycle, mid.DigitalInputNumber); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0225PackRevision1() + { + string package = "00230225 055"; + + AssertBuildAndParse(package, new Mid0225() + { + DigitalInputNumber = DigitalInputNumber.EndOfCycle + }, true); + } } } diff --git a/src/MIDTesters.Core/Job/Advanced/TestMid0120.cs b/src/MIDTesters.Core/Job/Advanced/TestMid0120.cs index 18f00627..37f0b187 100644 --- a/src/MIDTesters.Core/Job/Advanced/TestMid0120.cs +++ b/src/MIDTesters.Core/Job/Advanced/TestMid0120.cs @@ -31,5 +31,14 @@ public void Mid0120ByteRevision1() Assert.IsTrue(mid.Header.NoAckFlag); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0120PackRevision1() + { + string package = "00200120 1 "; + + AssertBuildAndParse(package, new Mid0120(noAckFlag: true), true); + } } } diff --git a/src/MIDTesters.Core/Job/Advanced/TestMid0121.cs b/src/MIDTesters.Core/Job/Advanced/TestMid0121.cs index 557e6bed..d3075c33 100644 --- a/src/MIDTesters.Core/Job/Advanced/TestMid0121.cs +++ b/src/MIDTesters.Core/Job/Advanced/TestMid0121.cs @@ -31,5 +31,17 @@ public void Mid0121ByteRevision1() Assert.IsTrue(mid.Header.NoAckFlag); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0121PackRevision1() + { + string package = "00200121 1 "; + + AssertBuildAndParse(package, new Mid0121() + { + Header = { NoAckFlag = true } + }, true); + } } } diff --git a/src/MIDTesters.Core/Job/Advanced/TestMid0122.cs b/src/MIDTesters.Core/Job/Advanced/TestMid0122.cs index 6acc0c4b..c45f2973 100644 --- a/src/MIDTesters.Core/Job/Advanced/TestMid0122.cs +++ b/src/MIDTesters.Core/Job/Advanced/TestMid0122.cs @@ -31,5 +31,17 @@ public void Mid0122ByteRevision1() Assert.IsTrue(mid.Header.NoAckFlag); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0122PackRevision1() + { + string package = "00200122 1 "; + + AssertBuildAndParse(package, new Mid0122() + { + Header = { NoAckFlag = true } + }, true); + } } } diff --git a/src/MIDTesters.Core/Job/Advanced/TestMid0123.cs b/src/MIDTesters.Core/Job/Advanced/TestMid0123.cs index d2e8d8e6..fbbb06ee 100644 --- a/src/MIDTesters.Core/Job/Advanced/TestMid0123.cs +++ b/src/MIDTesters.Core/Job/Advanced/TestMid0123.cs @@ -31,5 +31,17 @@ public void Mid0123ByteRevision1() Assert.IsTrue(mid.Header.NoAckFlag); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0123PackRevision1() + { + string package = "00200123 1 "; + + AssertBuildAndParse(package, new Mid0123() + { + Header = { NoAckFlag = true } + }, true); + } } } diff --git a/src/MIDTesters.Core/Job/Advanced/TestMid0124.cs b/src/MIDTesters.Core/Job/Advanced/TestMid0124.cs index fdc9e38e..5463beaa 100644 --- a/src/MIDTesters.Core/Job/Advanced/TestMid0124.cs +++ b/src/MIDTesters.Core/Job/Advanced/TestMid0124.cs @@ -31,5 +31,17 @@ public void Mid0124ByteRevision1() Assert.IsTrue(mid.Header.NoAckFlag); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0124PackRevision1() + { + string package = "00200124 1 "; + + AssertBuildAndParse(package, new Mid0124() + { + Header = { NoAckFlag = true } + }, true); + } } } diff --git a/src/MIDTesters.Core/Job/Advanced/TestMid0125.cs b/src/MIDTesters.Core/Job/Advanced/TestMid0125.cs index d4cdfe84..4ba4508e 100644 --- a/src/MIDTesters.Core/Job/Advanced/TestMid0125.cs +++ b/src/MIDTesters.Core/Job/Advanced/TestMid0125.cs @@ -29,5 +29,14 @@ public void Mid0125ByteRevision1() Assert.AreEqual(typeof(Mid0125), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0125PackRevision1() + { + string package = "00200125 "; + + AssertBuildAndParse(package, new Mid0125(), true); + } } } diff --git a/src/MIDTesters.Core/Job/Advanced/TestMid0126.cs b/src/MIDTesters.Core/Job/Advanced/TestMid0126.cs index fbbc665a..f2417805 100644 --- a/src/MIDTesters.Core/Job/Advanced/TestMid0126.cs +++ b/src/MIDTesters.Core/Job/Advanced/TestMid0126.cs @@ -29,5 +29,14 @@ public void Mid0126ByteRevision1() Assert.AreEqual(typeof(Mid0126), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0126PackRevision1() + { + string package = "00200126 "; + + AssertBuildAndParse(package, new Mid0126(), true); + } } } diff --git a/src/MIDTesters.Core/Job/Advanced/TestMid0127.cs b/src/MIDTesters.Core/Job/Advanced/TestMid0127.cs index 0a61ed2b..1aa125b6 100644 --- a/src/MIDTesters.Core/Job/Advanced/TestMid0127.cs +++ b/src/MIDTesters.Core/Job/Advanced/TestMid0127.cs @@ -29,5 +29,14 @@ public void Mid0127ByteRevision1() Assert.AreEqual(typeof(Mid0127), mid.GetType()); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0127PackRevision1() + { + string package = "00200127001 "; + + AssertBuildAndParse(package, new Mid0127()); + } } } diff --git a/src/MIDTesters.Core/Job/Advanced/TestMid0128.cs b/src/MIDTesters.Core/Job/Advanced/TestMid0128.cs index 22426340..2dcce0ce 100644 --- a/src/MIDTesters.Core/Job/Advanced/TestMid0128.cs +++ b/src/MIDTesters.Core/Job/Advanced/TestMid0128.cs @@ -29,5 +29,14 @@ public void Mid0128ByteRevision1() Assert.AreEqual(typeof(Mid0128), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0128PackRevision1() + { + string package = "00200128 "; + + AssertBuildAndParse(package, new Mid0128(), true); + } } } diff --git a/src/MIDTesters.Core/Job/Advanced/TestMid0129.cs b/src/MIDTesters.Core/Job/Advanced/TestMid0129.cs index a18a466b..26aaed32 100644 --- a/src/MIDTesters.Core/Job/Advanced/TestMid0129.cs +++ b/src/MIDTesters.Core/Job/Advanced/TestMid0129.cs @@ -54,5 +54,27 @@ public void Mid0129ByteRevision2() Assert.AreEqual(123, mid.ParameterSetId); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0129PackRevision1() + { + string package = "00200129 "; + + AssertBuildAndParse(package, new Mid0129(1), true); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0129PackRevision2() + { + string package = "00290129002 010302123"; + + AssertBuildAndParse(package, new Mid0129(2) + { + ChannelId = 3, + ParameterSetId = 123 + }); + } } } diff --git a/src/MIDTesters.Core/Job/Advanced/TestMid0130.cs b/src/MIDTesters.Core/Job/Advanced/TestMid0130.cs index 13c0cd81..630696ae 100644 --- a/src/MIDTesters.Core/Job/Advanced/TestMid0130.cs +++ b/src/MIDTesters.Core/Job/Advanced/TestMid0130.cs @@ -29,5 +29,17 @@ public void Mid0130ByteRevision1() Assert.IsTrue(mid.JobOffStatus); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0130PackRevision1() + { + string package = "00210130 1"; + + AssertBuildAndParse(package, new Mid0130() + { + JobOffStatus = true + }, true); + } } } diff --git a/src/MIDTesters.Core/Job/Advanced/TestMid0131.cs b/src/MIDTesters.Core/Job/Advanced/TestMid0131.cs index c345aa71..066f1584 100644 --- a/src/MIDTesters.Core/Job/Advanced/TestMid0131.cs +++ b/src/MIDTesters.Core/Job/Advanced/TestMid0131.cs @@ -29,5 +29,14 @@ public void Mid0131ByteRevision1() Assert.AreEqual(typeof(Mid0131), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0131PackRevision1() + { + string package = "00200131 "; + + AssertBuildAndParse(package, new Mid0131(), true); + } } } diff --git a/src/MIDTesters.Core/Job/Advanced/TestMid0132.cs b/src/MIDTesters.Core/Job/Advanced/TestMid0132.cs index c7d0798f..1196999d 100644 --- a/src/MIDTesters.Core/Job/Advanced/TestMid0132.cs +++ b/src/MIDTesters.Core/Job/Advanced/TestMid0132.cs @@ -29,5 +29,14 @@ public void Mid0132ByteRevision1() Assert.AreEqual(typeof(Mid0132), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0132PackRevision1() + { + string package = "00200132 "; + + AssertBuildAndParse(package, new Mid0132(), true); + } } } diff --git a/src/MIDTesters.Core/Job/Advanced/TestMid0133.cs b/src/MIDTesters.Core/Job/Advanced/TestMid0133.cs index bd48c2d6..5853cb38 100644 --- a/src/MIDTesters.Core/Job/Advanced/TestMid0133.cs +++ b/src/MIDTesters.Core/Job/Advanced/TestMid0133.cs @@ -29,5 +29,14 @@ public void Mid0133ByteRevision1() Assert.AreEqual(typeof(Mid0133), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0133PackRevision1() + { + string package = "00200133 "; + + AssertBuildAndParse(package, new Mid0133(), true); + } } } diff --git a/src/MIDTesters.Core/Job/Advanced/TestMid0140.cs b/src/MIDTesters.Core/Job/Advanced/TestMid0140.cs index 2f7531cf..f57022bc 100644 --- a/src/MIDTesters.Core/Job/Advanced/TestMid0140.cs +++ b/src/MIDTesters.Core/Job/Advanced/TestMid0140.cs @@ -3,6 +3,7 @@ using OpenProtocolInterpreter.Job.Advanced; using System.Threading; +using System.Collections.Generic; namespace MIDTesters.Job.Advanced { [TestClass] @@ -281,5 +282,143 @@ public void Mid0140ByteRevision999() Assert.AreEqual(Reserved.G, mid.Reserved); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0140PackRevision1() + { + string package = "01440140 01000102Job 1 03020414:045:0:22:02;01:013:1:10:01;0510610720810911011111201001310000140090151161171181191"; + + AssertBuildAndParse(package, BuildMid0140(1, new List() + { + new AdvancedJob() { ChannelId = 14, ProgramId = 45, AutoSelect = AutoSelect.None, BatchSize = 22, MaxCoherentNok = 2 }, + new AdvancedJob() { ChannelId = 1, ProgramId = 13, AutoSelect = AutoSelect.AutoNextChange, BatchSize = 10, MaxCoherentNok = 1 } + }), true); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0140PackRevision2() + { + string package = "02250140002 01000102Job 1 03020415:045:0:22:02:10:0107:Job Action 1 :01;15:045:0:12:12:13:0407:Job Action 2 :02;05106107208109110111112010013100001400901511611711811912000120"; + + var mid = BuildMid0140(2, new List() + { + new AdvancedJob() { ChannelId = 15, ProgramId = 45, AutoSelect = AutoSelect.None, BatchSize = 22, MaxCoherentNok = 2, BatchCounter = 10, IdentifierNumber = 107, JobStepName = "Job Action 1", JobStepType = 1 }, + new AdvancedJob() { ChannelId = 15, ProgramId = 45, AutoSelect = AutoSelect.None, BatchSize = 12, MaxCoherentNok = 12, BatchCounter = 13, IdentifierNumber = 407, JobStepName = "Job Action 2", JobStepType = 2 } + }); + mid.JobSequenceNumber = 120; + + AssertBuildAndParse(package, mid); + } + + [TestMethod] + [TestCategory("Revision 3"), TestCategory("Pack")] + public void Mid0140PackRevision3() + { + string package = "02350140003 01000102Job 1 03020415:045:10:22:02:10:0107:Job Action 1 :01:2:1:1:2:2;15:041:10:12:12:13:0407:Job Action 2 :02:2:1:1:2:2;05106107108010009009001000901111211311411511605000"; + + AssertBuildAndParse(package, BuildMid0140Revision3Onwards(3, new List() + { + BuildAdvancedJobRevision3(programId: 45, batchSize: 22, maxCoherentNok: 2, batchCounter: 10, identifierNumber: 107, jobStepName: "Job Action 1", jobStepType: 1), + BuildAdvancedJobRevision3(programId: 41, batchSize: 12, maxCoherentNok: 12, batchCounter: 13, identifierNumber: 407, jobStepName: "Job Action 2", jobStepType: 2) + })); + } + + [TestMethod] + [TestCategory("Revision 4"), TestCategory("Pack")] + public void Mid0140PackRevision4() + { + string package = "02430140004 01000102Job 1 03020415:045:10:0022:02:0010:0107:Job Action 1 :01:2:1:1:2:2;15:041:10:0016:12:0013:0407:Job Action 2 :02:2:1:1:2:2;05106107108010009009001000901111211311411511605000"; + + AssertBuildAndParse(package, BuildMid0140Revision3Onwards(4, new List() + { + BuildAdvancedJobRevision3(programId: 45, batchSize: 22, maxCoherentNok: 2, batchCounter: 10, identifierNumber: 107, jobStepName: "Job Action 1", jobStepType: 1), + BuildAdvancedJobRevision3(programId: 41, batchSize: 16, maxCoherentNok: 12, batchCounter: 13, identifierNumber: 407, jobStepName: "Job Action 2", jobStepType: 2) + })); + } + + [TestMethod] + [TestCategory("Revision 999"), TestCategory("Pack")] + public void Mid0140PackRevision999() + { + string package = "01500140999 01000102Job 1 03020414:045:0:22:02:10;01:013:1:10:01:05;0510610720810911011111201001310000140090151161171181191"; + + AssertBuildAndParse(package, BuildMid0140(999, new List() + { + new AdvancedJob() { ChannelId = 14, ProgramId = 45, AutoSelect = AutoSelect.None, BatchSize = 22, MaxCoherentNok = 2, BatchCounter = 10 }, + new AdvancedJob() { ChannelId = 1, ProgramId = 13, AutoSelect = AutoSelect.AutoNextChange, BatchSize = 10, MaxCoherentNok = 1, BatchCounter = 5 } + })); + } + + private static Mid0140 BuildMid0140(int revision, List jobList) + { + return new Mid0140(revision) + { + JobId = 1, + JobName = "Job 1", + NumberOfParameterSets = 2, + JobList = jobList, + ForcedOrder = ForcedOrder.ForcedOrder, + LockAtJobDone = true, + ToolLoosening = ToolLoosening.EnableOnlyOnNokTightenings, + RepeatJob = true, + BatchMode = BatchMode.BothOkAndNok, + BatchStatusAtIncrement = true, + DecrementBatchAtOkLoosening = true, + MaxTimeForFirstTightening = 100, + MaxTimeToCompleteJob = 10000, + DisplayResultAtAutoSelect = 90, + UsingLineControl = true, + IdentifierResultPart = IdentifierPart.Other, + ResultOfNonTightenings = true, + ResetAllIdentifiersAtJobDone = true, + Reserved = Reserved.G + }; + } + + private static Mid0140 BuildMid0140Revision3Onwards(int revision, List jobList) + { + return new Mid0140(revision) + { + JobId = 1, + JobName = "Job 1", + NumberOfParameterSets = 2, + JobList = jobList, + ForcedOrder = ForcedOrder.ForcedOrder, + LockAtJobDone = true, + RepeatJob = true, + MaxTimeForFirstTightening = 100, + MaxTimeToCompleteJob = 900, + DisplayResultAtAutoSelect = 90, + UsingLineControl = true, + IdentifierResultPart = IdentifierPart.Other, + ResultOfNonTightenings = true, + ResetAllIdentifiersAtJobDone = true, + Reserved = Reserved.G, + JobSequenceNumber = 5000 + }; + } + + private static AdvancedJob BuildAdvancedJobRevision3(int programId, int batchSize, int maxCoherentNok, int batchCounter, int identifierNumber, string jobStepName, int jobStepType) + { + return new AdvancedJob() + { + ChannelId = 15, + ProgramId = programId, + AutoSelect = AutoSelect.ToolDisplay, + BatchSize = batchSize, + MaxCoherentNok = maxCoherentNok, + BatchCounter = batchCounter, + IdentifierNumber = identifierNumber, + JobStepName = jobStepName, + JobStepType = jobStepType, + ToolLoosening = ToolLoosening.EnableOnlyOnNokTightenings, + JobBatchMode = BatchMode.BothOkAndNok, + BatchStatusAtIncrement = BatchStatusAtIncrement.Nok, + DecrementBatchAfterLoosening = DecrementBatchAfterLoosening.AfterOk, + CurrentBatchStatus = CurrentBatchStatus.Nok + }; + } } } diff --git a/src/MIDTesters.Core/Job/TestMid0030.cs b/src/MIDTesters.Core/Job/TestMid0030.cs index 651c298c..d9067c73 100644 --- a/src/MIDTesters.Core/Job/TestMid0030.cs +++ b/src/MIDTesters.Core/Job/TestMid0030.cs @@ -29,5 +29,14 @@ public void Mid0030ByteAllRevisions() Assert.AreEqual(typeof(Mid0030), mid.GetType()); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Pack")] + public void Mid0030PackAllRevisions() + { + string package = "00200030002 "; + + AssertBuildAndParse(package, new Mid0030(2)); + } } } diff --git a/src/MIDTesters.Core/Job/TestMid0031.cs b/src/MIDTesters.Core/Job/TestMid0031.cs index 509c6356..5eb81de9 100644 --- a/src/MIDTesters.Core/Job/TestMid0031.cs +++ b/src/MIDTesters.Core/Job/TestMid0031.cs @@ -1,5 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter.Job; +using System.Collections.Generic; namespace MIDTesters.Job { @@ -56,5 +57,31 @@ public void Mid0031ByteRevision2() Assert.AreEqual(10, mid.JobIds.Count); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0031PackRevision1() + { + string package = "00300031001 0401020304"; + + AssertBuildAndParse(package, new Mid0031(1) + { + TotalJobs = 4, + JobIds = new List() { 1, 2, 3, 4 } + }); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0031PackRevision2() + { + string package = "00640031002 00100001000200030004000500100015001100120019"; + + AssertBuildAndParse(package, new Mid0031(2) + { + TotalJobs = 10, + JobIds = new List() { 1, 2, 3, 4, 5, 10, 15, 11, 12, 19 } + }); + } } } diff --git a/src/MIDTesters.Core/Job/TestMid0032.cs b/src/MIDTesters.Core/Job/TestMid0032.cs index 84783363..9e45e5a8 100644 --- a/src/MIDTesters.Core/Job/TestMid0032.cs +++ b/src/MIDTesters.Core/Job/TestMid0032.cs @@ -98,5 +98,53 @@ public void Mid0032ByteRevision4() Assert.AreEqual(3, mid.JobId); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0032PackRevision1() + { + string package = "00220032001 04"; + + AssertBuildAndParse(package, new Mid0032(1) + { + JobId = 4 + }); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0032PackRevision2() + { + string package = "00240032002 0002"; + + AssertBuildAndParse(package, new Mid0032(2) + { + JobId = 2 + }); + } + + [TestMethod] + [TestCategory("Revision 3"), TestCategory("Pack")] + public void Mid0032PackRevision3() + { + string package = "00240032003 0003"; + + AssertBuildAndParse(package, new Mid0032(3) + { + JobId = 3 + }); + } + + [TestMethod] + [TestCategory("Revision 4"), TestCategory("Pack")] + public void Mid0032PackRevision4() + { + string package = "00240032004 0003"; + + AssertBuildAndParse(package, new Mid0032(4) + { + JobId = 3 + }); + } } } diff --git a/src/MIDTesters.Core/Job/TestMid0033.cs b/src/MIDTesters.Core/Job/TestMid0033.cs index 3e005b5e..d6551d4e 100644 --- a/src/MIDTesters.Core/Job/TestMid0033.cs +++ b/src/MIDTesters.Core/Job/TestMid0033.cs @@ -1,6 +1,8 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter; using OpenProtocolInterpreter.Job; +using System.Collections.Generic; +using JobParameterSet = OpenProtocolInterpreter.Job.ParameterSet; namespace MIDTesters.Job { @@ -242,5 +244,91 @@ public void Mid0033ByteRevision5() Assert.AreEqual(2, mid.ParameterSetList.Count); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0033PackRevision1() + { + string package = "01150033001 010402My Job 4 031045000057000406107108109110211112021315:011:1:02;11:015:1:02;"; + + AssertBuildAndParse(package, BuildMid0033(1, new List() + { + new JobParameterSet() { ChannelId = 15, TypeId = 11, AutoValue = true, BatchSize = 2 }, + new JobParameterSet() { ChannelId = 11, TypeId = 15, AutoValue = true, BatchSize = 2 } + })); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0033PackRevision2() + { + string package = "01170033002 01000402My Job 4 031045000057000406107108109110211112021315:011:1:02;11:015:1:02;"; + + AssertBuildAndParse(package, BuildMid0033(2, new List() + { + new JobParameterSet() { ChannelId = 15, TypeId = 11, AutoValue = true, BatchSize = 2 }, + new JobParameterSet() { ChannelId = 11, TypeId = 15, AutoValue = true, BatchSize = 2 } + })); + } + + [TestMethod] + [TestCategory("Revision 3"), TestCategory("Pack")] + public void Mid0033PackRevision3() + { + string package = "01810033003 01000402My Job 4 031045000057000406107108109110211112021315:011:1:02:02:Job Step 1 Name :03;11:015:1:02:01:Job Step 2 Name :05;"; + +#pragma warning disable CS0618 // Socket is the revision 3 layout of the field + AssertBuildAndParse(package, BuildMid0033(3, new List() + { + new JobParameterSet() { ChannelId = 15, TypeId = 11, AutoValue = true, BatchSize = 2, Socket = 2, JobStepName = "Job Step 1 Name", JobStepType = 3 }, + new JobParameterSet() { ChannelId = 11, TypeId = 15, AutoValue = true, BatchSize = 2, Socket = 1, JobStepName = "Job Step 2 Name", JobStepType = 5 } + })); +#pragma warning restore CS0618 + } + + [TestMethod] + [TestCategory("Revision 4"), TestCategory("Pack")] + public void Mid0033PackRevision4() + { + string package = "01910033004 01000402My Job 4 031045000057000406107108109110211112021315:011:1:02:0002:Job Step 1 Name :03:05;11:015:1:02:0001:Job Step 2 Name :05:02;"; + + AssertBuildAndParse(package, BuildMid0033(4, new List() + { + new JobParameterSet() { ChannelId = 15, TypeId = 11, AutoValue = true, BatchSize = 2, IdentifierNumber = 2, JobStepName = "Job Step 1 Name", JobStepType = 3, MaxCoherentNok = 5 }, + new JobParameterSet() { ChannelId = 11, TypeId = 15, AutoValue = true, BatchSize = 2, IdentifierNumber = 1, JobStepName = "Job Step 2 Name", JobStepType = 5, MaxCoherentNok = 2 } + })); + } + + [TestMethod] + [TestCategory("Revision 5"), TestCategory("Pack")] + public void Mid0033PackRevision5() + { + string package = "01950033005 01000402My Job 4 031045000057000406107108109110211112021315:011:1:0002:0002:Job Step 1 Name :03:05;11:015:1:0002:0001:Job Step 2 Name :05:02;"; + + AssertBuildAndParse(package, BuildMid0033(5, new List() + { + new JobParameterSet() { ChannelId = 15, TypeId = 11, AutoValue = true, BatchSize = 2, IdentifierNumber = 2, JobStepName = "Job Step 1 Name", JobStepType = 3, MaxCoherentNok = 5 }, + new JobParameterSet() { ChannelId = 11, TypeId = 15, AutoValue = true, BatchSize = 2, IdentifierNumber = 1, JobStepName = "Job Step 2 Name", JobStepType = 5, MaxCoherentNok = 2 } + })); + } + + private static Mid0033 BuildMid0033(int revision, List parameterSets) + { + return new Mid0033(revision) + { + JobId = 4, + JobName = "My Job 4", + ForcedOrder = ForcedOrder.ForcedOrder, + MaxTimeForFirstTightening = 5000, + MaxTimeToCompleteJob = 70004, + JobBatchMode = JobBatchMode.OkAndNokTightenings, + LockAtJobDone = true, + UseLineControl = true, + RepeatJob = true, + ToolLoosening = ToolLoosening.EnableOnlyOnNokTightenings, + Reserved = Reserved.G, + ParameterSetList = parameterSets + }; + } } } diff --git a/src/MIDTesters.Core/Job/TestMid0034.cs b/src/MIDTesters.Core/Job/TestMid0034.cs index e7d5283e..a4f68e0d 100644 --- a/src/MIDTesters.Core/Job/TestMid0034.cs +++ b/src/MIDTesters.Core/Job/TestMid0034.cs @@ -29,5 +29,14 @@ public void Mid0034ByteAllRevisions() Assert.AreEqual(typeof(Mid0034), mid.GetType()); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Pack")] + public void Mid0034PackAllRevisions() + { + string package = "00200034001 "; + + AssertBuildAndParse(package, new Mid0034()); + } } } diff --git a/src/MIDTesters.Core/Job/TestMid0035.cs b/src/MIDTesters.Core/Job/TestMid0035.cs index 7cec372a..3dc89e9e 100644 --- a/src/MIDTesters.Core/Job/TestMid0035.cs +++ b/src/MIDTesters.Core/Job/TestMid0035.cs @@ -205,5 +205,106 @@ public void Mid0035ByteRevision5() Assert.AreEqual("IdentifierResultPart4xxxx", mid.IdentifierResultPart4); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0035PackRevision1() + { + string package = "00630035001 0101020030040008050003062001-12-01:20:12:45"; + + AssertBuildAndParse(package, new Mid0035(1) + { + JobId = 1, + JobStatus = JobStatus.NotCompleted, + JobBatchMode = JobBatchMode.OnlyOkTightenings, + JobBatchSize = 8, + JobBatchCounter = 3, + TimeStamp = new DateTime(2001, 12, 1, 20, 12, 45) + }); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0035PackRevision2() + { + string package = "00650035002 010001020030040008050003062001-12-01:20:12:45"; + + AssertBuildAndParse(package, new Mid0035(2) + { + JobId = 1, + JobStatus = JobStatus.NotCompleted, + JobBatchMode = JobBatchMode.OnlyOkTightenings, + JobBatchSize = 8, + JobBatchCounter = 3, + TimeStamp = new DateTime(2001, 12, 1, 20, 12, 45) + }); + } + + [TestMethod] + [TestCategory("Revision 3"), TestCategory("Pack")] + public void Mid0035PackRevision3() + { + string package = "00790035003 010001020030040008050003062001-12-01:20:12:4507120080100912"; + + AssertBuildAndParse(package, new Mid0035(3) + { + JobId = 1, + JobStatus = JobStatus.NotCompleted, + JobBatchMode = JobBatchMode.OnlyOkTightenings, + JobBatchSize = 8, + JobBatchCounter = 3, + TimeStamp = new DateTime(2001, 12, 1, 20, 12, 45), + JobCurrentStep = 120, + JobTotalNumberOfSteps = 10, + JobStepType = 12 + }); + } + + [TestMethod] + [TestCategory("Revision 4"), TestCategory("Pack")] + public void Mid0035PackRevision4() + { + string package = "00830035004 010001020030040008050003062001-12-01:20:12:45071200801009121001"; + + AssertBuildAndParse(package, new Mid0035(4) + { + JobId = 1, + JobStatus = JobStatus.NotCompleted, + JobBatchMode = JobBatchMode.OnlyOkTightenings, + JobBatchSize = 8, + JobBatchCounter = 3, + TimeStamp = new DateTime(2001, 12, 1, 20, 12, 45), + JobCurrentStep = 120, + JobTotalNumberOfSteps = 10, + JobStepType = 12, + JobTighteningStatus = JobTighteningStatus.Nok + }); + } + + [TestMethod] + [TestCategory("Revision 5"), TestCategory("Pack")] + public void Mid0035PackRevision5() + { + string package = "01980035005 010001020030040008050003062001-12-01:20:12:45071200801009121001111234512VINVINN12345678912345678913IdentifierResultPart2xxxx14IdentifierResultPart3xxxx15IdentifierResultPart4xxxx"; + + AssertBuildAndParse(package, new Mid0035(5) + { + JobId = 1, + JobStatus = JobStatus.NotCompleted, + JobBatchMode = JobBatchMode.OnlyOkTightenings, + JobBatchSize = 8, + JobBatchCounter = 3, + TimeStamp = new DateTime(2001, 12, 1, 20, 12, 45), + JobCurrentStep = 120, + JobTotalNumberOfSteps = 10, + JobStepType = 12, + JobTighteningStatus = JobTighteningStatus.Nok, + JobSequenceNumber = 12345, + VinNumber = "VINVINN123456789123456789", + IdentifierResultPart2 = "IdentifierResultPart2xxxx", + IdentifierResultPart3 = "IdentifierResultPart3xxxx", + IdentifierResultPart4 = "IdentifierResultPart4xxxx" + }); + } } } diff --git a/src/MIDTesters.Core/Job/TestMid0036.cs b/src/MIDTesters.Core/Job/TestMid0036.cs index 91546d09..ca60da8e 100644 --- a/src/MIDTesters.Core/Job/TestMid0036.cs +++ b/src/MIDTesters.Core/Job/TestMid0036.cs @@ -29,5 +29,14 @@ public void Mid0036ByteAllRevisions() Assert.AreEqual(typeof(Mid0036), mid.GetType()); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Pack")] + public void Mid0036PackAllRevisions() + { + string package = "00200036001 "; + + AssertBuildAndParse(package, new Mid0036()); + } } } diff --git a/src/MIDTesters.Core/Job/TestMid0037.cs b/src/MIDTesters.Core/Job/TestMid0037.cs index e399f14a..5bea56d4 100644 --- a/src/MIDTesters.Core/Job/TestMid0037.cs +++ b/src/MIDTesters.Core/Job/TestMid0037.cs @@ -29,5 +29,14 @@ public void Mid0037ByteAllRevisions() Assert.AreEqual(typeof(Mid0037), mid.GetType()); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Pack")] + public void Mid0037PackAllRevisions() + { + string package = "00200037001 "; + + AssertBuildAndParse(package, new Mid0037()); + } } } diff --git a/src/MIDTesters.Core/Job/TestMid0038.cs b/src/MIDTesters.Core/Job/TestMid0038.cs index bc412f1b..827f33ec 100644 --- a/src/MIDTesters.Core/Job/TestMid0038.cs +++ b/src/MIDTesters.Core/Job/TestMid0038.cs @@ -54,15 +54,27 @@ public void Mid0038ByteRevision2() } [TestMethod] - [TestCategory("Revision 2"), TestCategory("ByteArray")] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0038PackRevision1() + { + string package = "00220038001 01"; + + AssertBuildAndParse(package, new Mid0038(1) + { + JobId = 1 + }); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] public void Mid0038PackRevision2() { - var mid = new Mid0038(2); - mid.JobId = 1; - var actual = mid.PackBytes(); - var package = "00240038002 0001"; + string package = "00240038002 0001"; - CollectionAssert.AreEqual(GetAsciiBytes(package), actual); + AssertBuildAndParse(package, new Mid0038(2) + { + JobId = 1 + }); } } } diff --git a/src/MIDTesters.Core/Job/TestMid0039.cs b/src/MIDTesters.Core/Job/TestMid0039.cs index 8b3b3af8..8113ec38 100644 --- a/src/MIDTesters.Core/Job/TestMid0039.cs +++ b/src/MIDTesters.Core/Job/TestMid0039.cs @@ -52,5 +52,29 @@ public void Mid0039ByteRevision2() Assert.AreEqual(3, mid.JobId); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0039PackRevision1() + { + string package = "00220039001 01"; + + AssertBuildAndParse(package, new Mid0039(1) + { + JobId = 1 + }); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0039PackRevision2() + { + string package = "00240039002 0003"; + + AssertBuildAndParse(package, new Mid0039(2) + { + JobId = 3 + }); + } } } diff --git a/src/MIDTesters.Core/KeepAlive/TestMid9999.cs b/src/MIDTesters.Core/KeepAlive/TestMid9999.cs index f3084416..bcdf0c29 100644 --- a/src/MIDTesters.Core/KeepAlive/TestMid9999.cs +++ b/src/MIDTesters.Core/KeepAlive/TestMid9999.cs @@ -29,5 +29,16 @@ public void Mid9999ByteRevision1() Assert.AreEqual(typeof(Mid9999), mid.GetType()); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid9999PackRevision1() + { + string package = "00209999 "; + + AssertBuildAndParse(package, new Mid9999(1) + { + }, true); + } } } diff --git a/src/MIDTesters.Core/LinkCommunication/TestMid9997.cs b/src/MIDTesters.Core/LinkCommunication/TestMid9997.cs index 5d81f772..04c0ddd4 100644 --- a/src/MIDTesters.Core/LinkCommunication/TestMid9997.cs +++ b/src/MIDTesters.Core/LinkCommunication/TestMid9997.cs @@ -29,5 +29,17 @@ public void Mid9997ByteRevision1() Assert.AreEqual(65, mid.MidNumber); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid9997PackRevision1() + { + string package = "00249997001 0061"; + + AssertBuildAndParse(package, new Mid9997() + { + MidNumber = 61 + }); + } } } diff --git a/src/MIDTesters.Core/LinkCommunication/TestMid9998.cs b/src/MIDTesters.Core/LinkCommunication/TestMid9998.cs index 3861e38c..567eb355 100644 --- a/src/MIDTesters.Core/LinkCommunication/TestMid9998.cs +++ b/src/MIDTesters.Core/LinkCommunication/TestMid9998.cs @@ -32,5 +32,18 @@ public void Mid9998ByteRevision1() Assert.AreEqual(LinkCommunicationError.InvalidSequenceNumber, mid.ErrorCode); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid9998PackRevision1() + { + string package = "00289998 00610003"; + + AssertBuildAndParse(package, new Mid9998() + { + MidNumber = 61, + ErrorCode = LinkCommunicationError.InvalidSequenceNumber + }, true); + } } } diff --git a/src/MIDTesters.Core/MidTester.cs b/src/MIDTesters.Core/MidTester.cs index 2737c9e2..3acfc628 100644 --- a/src/MIDTesters.Core/MidTester.cs +++ b/src/MIDTesters.Core/MidTester.cs @@ -1,8 +1,10 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter; using System; +using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Text; [assembly: Parallelize(Workers = 4, Scope = ExecutionScope.MethodLevel)] @@ -10,6 +12,8 @@ namespace MIDTesters { public abstract class MidTester { + private const int MaxComparisonDepth = 4; + protected readonly MidInterpreter _midInterpreter; public MidTester() @@ -45,5 +49,121 @@ protected void AssertEqualPackages(IReadOnlyCollection expected, Mid mid, mid.Header.StationId = mid.Header.SpindleId = null; CollectionAssert.AreEqual(expected.ToArray(), mid.PackBytes()); } + + /// + /// Packs a mid built from its properties (instead of parsed from a package) and asserts both + /// the string and the byte array representations match . + /// + protected void AssertPackedFromProperties(string expected, Mid built, bool useEmptyRevision = false) + { + if (useEmptyRevision) + { + built.Header.Revision = 0; + } + + built.Header.StationId = built.Header.SpindleId = null; + Assert.AreEqual(expected, built.Pack()); + CollectionAssert.AreEqual(GetAsciiBytes(expected), built.PackBytes()); + } + + /// + /// Reverse of the usual parse/re-pack test: packs a mid built from its properties, asserts the + /// package matches, then parses that package back and asserts every data field bound property + /// of the parsed mid holds the same value as the built one. + /// + protected TMid AssertBuildAndParse(string expected, TMid built, bool useEmptyRevision = false) where TMid : Mid + { + AssertPackedFromProperties(expected, built, useEmptyRevision); + + var parsed = _midInterpreter.Parse(expected); + AssertEqualDataFieldProperties(built, parsed, built.Header.StandardizedRevision); + return parsed; + } + + /// + /// Compares every public property decorated with a up to + /// between two instances, recursing into the collections a mid may hold. + /// + protected static void AssertEqualDataFieldProperties(object expected, object actual, int revision) + { + Assert.IsNotNull(actual, $"Expected an instance of {expected.GetType().Name} but got null"); + + foreach (var property in GetDataFieldProperties(expected.GetType(), revision)) + { + AssertEqualValues(property.GetValue(expected), + property.GetValue(actual), + $"{expected.GetType().Name}.{property.Name}", + 0); + } + } + + private static IEnumerable GetDataFieldProperties(Type type, int revision) + { + return type.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(x => x.GetCustomAttributes() + .Any(a => a.Revision <= revision)); + } + + private static void AssertEqualValues(object expected, object actual, string path, int depth) + { + if (expected is string || actual is string) + { + // Setting a property keeps the value as given while parsing hands back the padded field, + // so only the content is compared here. The padding itself is already covered by the + // exact package assertion made before the parse leg runs. + Assert.AreEqual((expected as string)?.Trim() ?? string.Empty, + (actual as string)?.Trim() ?? string.Empty, + $"{path} does not match"); + return; + } + + if (expected == null && actual == null) + return; + + Assert.IsNotNull(expected, $"{path}: built value is null but parsed value is '{actual}'"); + Assert.IsNotNull(actual, $"{path}: parsed value is null but built value is '{expected}'"); + + if (IsComparableValue(expected.GetType())) + { + Assert.AreEqual(expected, actual, $"{path} does not match"); + return; + } + + if (expected is IEnumerable expectedItems && actual is IEnumerable actualItems) + { + AssertEqualCollections(expectedItems, actualItems, path, depth); + return; + } + + if (depth >= MaxComparisonDepth) + return; + + foreach (var property in expected.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + if (property.GetIndexParameters().Length > 0) + continue; + + AssertEqualValues(property.GetValue(expected), + property.GetValue(actual), + $"{path}.{property.Name}", + depth + 1); + } + } + + private static void AssertEqualCollections(IEnumerable expected, IEnumerable actual, string path, int depth) + { + var expectedList = expected.Cast().ToList(); + var actualList = actual.Cast().ToList(); + + Assert.AreEqual(expectedList.Count, actualList.Count, $"{path} has a different number of entries"); + + for (int i = 0; i < expectedList.Count; i++) + { + AssertEqualValues(expectedList[i], actualList[i], $"{path}[{i}]", depth + 1); + } + } + + private static bool IsComparableValue(Type type) + => type.IsPrimitive || type.IsEnum || type == typeof(string) || type == typeof(decimal) || type == typeof(DateTime); } } diff --git a/src/MIDTesters.Core/MotorTuning/TestMid0500.cs b/src/MIDTesters.Core/MotorTuning/TestMid0500.cs index 382a4e8f..8cc88b7a 100644 --- a/src/MIDTesters.Core/MotorTuning/TestMid0500.cs +++ b/src/MIDTesters.Core/MotorTuning/TestMid0500.cs @@ -29,5 +29,14 @@ public void Mid0500ByteRevision1() Assert.AreEqual(typeof(Mid0500), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0500PackRevision1() + { + string package = "00200500 "; + + AssertBuildAndParse(package, new Mid0500(), true); + } } } diff --git a/src/MIDTesters.Core/MotorTuning/TestMid0501.cs b/src/MIDTesters.Core/MotorTuning/TestMid0501.cs index 94f165bd..52e43de3 100644 --- a/src/MIDTesters.Core/MotorTuning/TestMid0501.cs +++ b/src/MIDTesters.Core/MotorTuning/TestMid0501.cs @@ -29,5 +29,17 @@ public void Mid0501ByteRevision1() Assert.IsTrue(mid.MotorTuneResult); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0501PackRevision1() + { + string package = "00230501 011"; + + AssertBuildAndParse(package, new Mid0501() + { + MotorTuneResult = true + }, true); + } } } diff --git a/src/MIDTesters.Core/MotorTuning/TestMid0502.cs b/src/MIDTesters.Core/MotorTuning/TestMid0502.cs index 429a8cf1..e943f3c8 100644 --- a/src/MIDTesters.Core/MotorTuning/TestMid0502.cs +++ b/src/MIDTesters.Core/MotorTuning/TestMid0502.cs @@ -29,5 +29,14 @@ public void Mid0502ByteRevision1() Assert.AreEqual(typeof(Mid0502), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0502PackRevision1() + { + string package = "00200502 "; + + AssertBuildAndParse(package, new Mid0502(), true); + } } } diff --git a/src/MIDTesters.Core/MotorTuning/TestMid0503.cs b/src/MIDTesters.Core/MotorTuning/TestMid0503.cs index 48445d53..561cadc4 100644 --- a/src/MIDTesters.Core/MotorTuning/TestMid0503.cs +++ b/src/MIDTesters.Core/MotorTuning/TestMid0503.cs @@ -29,5 +29,14 @@ public void Mid0503ByteRevision1() Assert.AreEqual(typeof(Mid0503), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0503PackRevision1() + { + string package = "00200503 "; + + AssertBuildAndParse(package, new Mid0503(), true); + } } } diff --git a/src/MIDTesters.Core/MotorTuning/TestMid0504.cs b/src/MIDTesters.Core/MotorTuning/TestMid0504.cs index 632d51bd..5ac775d2 100644 --- a/src/MIDTesters.Core/MotorTuning/TestMid0504.cs +++ b/src/MIDTesters.Core/MotorTuning/TestMid0504.cs @@ -29,5 +29,14 @@ public void Mid0504ByteRevision1() Assert.AreEqual(typeof(Mid0504), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0504PackRevision1() + { + string package = "00200504 "; + + AssertBuildAndParse(package, new Mid0504(), true); + } } } diff --git a/src/MIDTesters.Core/MultiSpindle/TestMid0090.cs b/src/MIDTesters.Core/MultiSpindle/TestMid0090.cs index 51e39642..02bb97ee 100644 --- a/src/MIDTesters.Core/MultiSpindle/TestMid0090.cs +++ b/src/MIDTesters.Core/MultiSpindle/TestMid0090.cs @@ -31,5 +31,14 @@ public void Mid0090ByteRevision1() Assert.IsTrue(mid.Header.NoAckFlag); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Pack")] + public void Mid0090PackAllRevisions() + { + string pack = @"00200090 1 "; + + AssertBuildAndParse(pack, new Mid0090(noAckFlag: true), true); + } } } diff --git a/src/MIDTesters.Core/MultiSpindle/TestMid0091.cs b/src/MIDTesters.Core/MultiSpindle/TestMid0091.cs index 5b01308f..d22b9385 100644 --- a/src/MIDTesters.Core/MultiSpindle/TestMid0091.cs +++ b/src/MIDTesters.Core/MultiSpindle/TestMid0091.cs @@ -2,6 +2,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter.MultiSpindle; +using System.Collections.Generic; namespace MIDTesters.MultiSpindle { [TestClass] @@ -38,5 +39,26 @@ public void Mid0091ByteAllRevisions() Assert.AreEqual(2, mid.SpindlesStatus.Count); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Pack")] + public void Mid0091PackAllRevisions() + { + string pack = @"00670091 1 01020265535032017-01-25:10:20:20041050101102031"; + + AssertBuildAndParse(pack, new Mid0091() + { + Header = { NoAckFlag = true }, + NumberOfSpindles = 2, + SyncTighteningId = 65535, + Time = new DateTime(2017, 1, 25, 10, 20, 20), + SyncOverallStatus = true, + SpindlesStatus = new List() + { + new SpindleStatus() { SpindleNumber = 1, ChannelId = 1, SyncOverallStatus = true }, + new SpindleStatus() { SpindleNumber = 2, ChannelId = 3, SyncOverallStatus = true } + } + }, true); + } } } diff --git a/src/MIDTesters.Core/MultiSpindle/TestMid0092.cs b/src/MIDTesters.Core/MultiSpindle/TestMid0092.cs index 0dcb5aba..1a5b2229 100644 --- a/src/MIDTesters.Core/MultiSpindle/TestMid0092.cs +++ b/src/MIDTesters.Core/MultiSpindle/TestMid0092.cs @@ -29,5 +29,14 @@ public void Mid0092ByteAllRevisions() Assert.AreEqual(typeof(Mid0092), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Pack")] + public void Mid0092PackAllRevisions() + { + string pack = @"00200092 "; + + AssertBuildAndParse(pack, new Mid0092(), true); + } } } diff --git a/src/MIDTesters.Core/MultiSpindle/TestMid0093.cs b/src/MIDTesters.Core/MultiSpindle/TestMid0093.cs index ff89a480..21b3d574 100644 --- a/src/MIDTesters.Core/MultiSpindle/TestMid0093.cs +++ b/src/MIDTesters.Core/MultiSpindle/TestMid0093.cs @@ -29,5 +29,14 @@ public void Mid0093ByteAllRevisions() Assert.AreEqual(typeof(Mid0093), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Pack")] + public void Mid0093PackAllRevisions() + { + string pack = @"00200093 "; + + AssertBuildAndParse(pack, new Mid0093(), true); + } } } diff --git a/src/MIDTesters.Core/MultiSpindle/TestMid0100.cs b/src/MIDTesters.Core/MultiSpindle/TestMid0100.cs index 33bca12a..992f83c0 100644 --- a/src/MIDTesters.Core/MultiSpindle/TestMid0100.cs +++ b/src/MIDTesters.Core/MultiSpindle/TestMid0100.cs @@ -81,5 +81,39 @@ public void Mid0100ByteRevision3() Assert.IsTrue(mid.SendOnlyNewData); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0100PackRevision1() + { + string pack = @"00200100001 "; + + AssertBuildAndParse(pack, new Mid0100(1)); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0100PackRevision2() + { + string pack = @"00300100002 0123456789"; + + AssertBuildAndParse(pack, new Mid0100(2) + { + DataNumberSystem = 123456789L + }); + } + + [TestMethod] + [TestCategory("Revision 3"), TestCategory("Pack")] + public void Mid0100PackRevision3() + { + string pack = @"00310100003 01234567891"; + + AssertBuildAndParse(pack, new Mid0100(3) + { + DataNumberSystem = 123456789L, + SendOnlyNewData = true + }); + } } } diff --git a/src/MIDTesters.Core/MultiSpindle/TestMid0101.cs b/src/MIDTesters.Core/MultiSpindle/TestMid0101.cs index 4454df2e..ecf75b31 100644 --- a/src/MIDTesters.Core/MultiSpindle/TestMid0101.cs +++ b/src/MIDTesters.Core/MultiSpindle/TestMid0101.cs @@ -3,6 +3,7 @@ using OpenProtocolInterpreter; using OpenProtocolInterpreter.MultiSpindle; +using System.Collections.Generic; namespace MIDTesters.MultiSpindle { [TestClass] @@ -254,5 +255,68 @@ public void Mid0101ByteRevision6() Assert.AreEqual(21, mid.JobSequenceNumber); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Pack")] + public void Mid0101PackRevisions1To3() + { + var pack = "02100101001 010202BM3GA02111900601 030304003050001060001071080006800900092010000800110000012000151300000142019-11-14:14:08:05152019-11-25:11:22:41160091317118010111000809100000020111000809100000"; + + AssertBuildAndParse(pack, BuildMid0101(1)); + } + + [TestMethod] + [TestCategory("Revision 4"), TestCategory("Pack")] + public void Mid0101PackRevision4() + { + var pack = "02150101004 010202BM3GA02111900601 030304003050001060001071080006800900092010000800110000012000151300000142019-11-14:14:08:05152019-11-25:11:22:4116009131711801011100080910000002011100080910000019002"; + + var mid = BuildMid0101(4); + mid.SystemSubType = SystemSubType.SystemRunningPresses; + + AssertBuildAndParse(pack, mid); + } + + [TestMethod] + [TestCategory("Revision 5"), TestCategory("Pack")] + public void Mid0101PackRevision5() + { + var pack = "02220101005 010202BM3GA02111900601 030304003050001060001071080006800900092010000800110000012000151300000142019-11-14:14:08:05152019-11-25:11:22:41160091317118010111000809100000020111000809100000190022000021"; + + var mid = BuildMid0101(5); + mid.SystemSubType = SystemSubType.SystemRunningPresses; + mid.JobSequenceNumber = 21; + + AssertBuildAndParse(pack, mid); + } + + private static Mid0101 BuildMid0101(int revision) + { + return new Mid0101(revision) + { + NumberOfSpindlesOrPresses = 2, + VinNumber = "BM3GA02111900601", + JobId = 3, + ParameterSetId = 3, + BatchSize = 1, + BatchCounter = 1, + BatchStatus = BatchStatus.Ok, + TorqueOrForceMinLimit = 6.80m, + TorqueOrForceMaxLimit = 9.20m, + TorqueOrForceFinalTarget = 8.00m, + AngleOrStrokeMinLimit = 0.00m, + AngleOrStrokeMaxLimit = 0.15m, + FinalAngleOrStrokeTarget = 0.00m, + LastChangeInParameterSet = new DateTime(2019, 11, 14, 14, 8, 5), + TimeStamp = new DateTime(2019, 11, 25, 11, 22, 41), + SyncTighteningId = 913, + SyncOverallStatus = true, + SpindlesOrPressesStatus = new List() + { + new SpindleOrPressStatus() { SpindleOrPressNumber = 1, ChannelId = 1, OverallStatus = true, TorqueOrForceStatus = TighteningValueStatus.Ok, TorqueOrForce = 8.09m, AngleOrStrokeStatus = true, AngleOrStroke = 0 }, + new SpindleOrPressStatus() { SpindleOrPressNumber = 2, ChannelId = 1, OverallStatus = true, TorqueOrForceStatus = TighteningValueStatus.Ok, TorqueOrForce = 8.09m, AngleOrStrokeStatus = true, AngleOrStroke = 0 } + } + }; + } } } diff --git a/src/MIDTesters.Core/MultiSpindle/TestMid0102.cs b/src/MIDTesters.Core/MultiSpindle/TestMid0102.cs index 8f2cc775..3b1634fb 100644 --- a/src/MIDTesters.Core/MultiSpindle/TestMid0102.cs +++ b/src/MIDTesters.Core/MultiSpindle/TestMid0102.cs @@ -29,5 +29,14 @@ public void Mid0102ByteRevision1() Assert.AreEqual(typeof(Mid0102), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0102PackRevision1() + { + string pack = @"00200102 "; + + AssertBuildAndParse(pack, new Mid0102(), true); + } } } diff --git a/src/MIDTesters.Core/MultiSpindle/TestMid0103.cs b/src/MIDTesters.Core/MultiSpindle/TestMid0103.cs index 2f326ffe..f4223722 100644 --- a/src/MIDTesters.Core/MultiSpindle/TestMid0103.cs +++ b/src/MIDTesters.Core/MultiSpindle/TestMid0103.cs @@ -29,5 +29,14 @@ public void Mid0102ByteRevision1() Assert.AreEqual(typeof(Mid0103), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0103PackRevision1() + { + string pack = @"00200103 "; + + AssertBuildAndParse(pack, new Mid0103(), true); + } } } diff --git a/src/MIDTesters.Core/MultiSpindle/TestMid0104.cs b/src/MIDTesters.Core/MultiSpindle/TestMid0104.cs index 06e2a21e..9ab64f79 100644 --- a/src/MIDTesters.Core/MultiSpindle/TestMid0104.cs +++ b/src/MIDTesters.Core/MultiSpindle/TestMid0104.cs @@ -29,5 +29,17 @@ public void Mid0104ByteRevision1() Assert.AreEqual(3009021015L, mid.RequestedResultIndex); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0104PackRevision1() + { + string pack = @"00320104 013009021015"; + + AssertBuildAndParse(pack, new Mid0104() + { + RequestedResultIndex = 3009021015L + }, true); + } } } diff --git a/src/MIDTesters.Core/MultipleIdentifiers/TestMid0150.cs b/src/MIDTesters.Core/MultipleIdentifiers/TestMid0150.cs index d28954a9..fbeb26f3 100644 --- a/src/MIDTesters.Core/MultipleIdentifiers/TestMid0150.cs +++ b/src/MIDTesters.Core/MultipleIdentifiers/TestMid0150.cs @@ -42,5 +42,17 @@ public void Mid0150ShouldTruncateIdentifier() var mid0150 = new Mid0150() { IdentifierData = identifier }; Assert.IsTrue(mid0150.Pack().Length == 120); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0150PackRevision1() + { + string package = "00470150001 My identifier less than 100"; + + AssertBuildAndParse(package, new Mid0150() + { + IdentifierData = "My identifier less than 100" + }); + } } } diff --git a/src/MIDTesters.Core/MultipleIdentifiers/TestMid0151.cs b/src/MIDTesters.Core/MultipleIdentifiers/TestMid0151.cs index 6153e7aa..5131e96d 100644 --- a/src/MIDTesters.Core/MultipleIdentifiers/TestMid0151.cs +++ b/src/MIDTesters.Core/MultipleIdentifiers/TestMid0151.cs @@ -29,5 +29,14 @@ public void Mid0151ByteRevision1() Assert.AreEqual(typeof(Mid0151), mid.GetType()); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0151PackRevision1() + { + string package = "00200151001 "; + + AssertBuildAndParse(package, new Mid0151()); + } } } diff --git a/src/MIDTesters.Core/MultipleIdentifiers/TestMid0152.cs b/src/MIDTesters.Core/MultipleIdentifiers/TestMid0152.cs index f402f103..8f6a0073 100644 --- a/src/MIDTesters.Core/MultipleIdentifiers/TestMid0152.cs +++ b/src/MIDTesters.Core/MultipleIdentifiers/TestMid0152.cs @@ -60,5 +60,20 @@ public void Mid0152ByteRevision1() Assert.AreEqual("Result part 4", mid.FourthIdentifierStatus.ResultPart.TrimEnd()); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0152PackRevision1() + { + string package = "01480152001 0110101Result part 1 0220003Result part 2 0330104Result part 3 0440105Result part 4 "; + + AssertBuildAndParse(package, new Mid0152() + { + FirstIdentifierStatus = new IdentifierStatus() { IdentifierTypeNumber = 1, IncludedInWorkOrder = true, StatusInWorkOrder = StatusInWorkOrder.Accepted, ResultPart = "Result part 1" }, + SecondIdentifierStatus = new IdentifierStatus() { IdentifierTypeNumber = 2, IncludedInWorkOrder = false, StatusInWorkOrder = StatusInWorkOrder.Reset, ResultPart = "Result part 2" }, + ThirdIdentifierStatus = new IdentifierStatus() { IdentifierTypeNumber = 3, IncludedInWorkOrder = true, StatusInWorkOrder = StatusInWorkOrder.Next, ResultPart = "Result part 3" }, + FourthIdentifierStatus = new IdentifierStatus() { IdentifierTypeNumber = 4, IncludedInWorkOrder = true, StatusInWorkOrder = StatusInWorkOrder.Initial, ResultPart = "Result part 4" } + }); + } } } diff --git a/src/MIDTesters.Core/MultipleIdentifiers/TestMid0153.cs b/src/MIDTesters.Core/MultipleIdentifiers/TestMid0153.cs index 2084dc1b..3a3c2bb2 100644 --- a/src/MIDTesters.Core/MultipleIdentifiers/TestMid0153.cs +++ b/src/MIDTesters.Core/MultipleIdentifiers/TestMid0153.cs @@ -29,5 +29,14 @@ public void Mid0153ByteRevision1() Assert.AreEqual(typeof(Mid0153), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0153PackRevision1() + { + string package = "00200153 "; + + AssertBuildAndParse(package, new Mid0153(), true); + } } } diff --git a/src/MIDTesters.Core/MultipleIdentifiers/TestMid0154.cs b/src/MIDTesters.Core/MultipleIdentifiers/TestMid0154.cs index 59e3fbc4..dcdfd4af 100644 --- a/src/MIDTesters.Core/MultipleIdentifiers/TestMid0154.cs +++ b/src/MIDTesters.Core/MultipleIdentifiers/TestMid0154.cs @@ -29,5 +29,14 @@ public void Mid0154ByteRevision1() Assert.AreEqual(typeof(Mid0154), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0154PackRevision1() + { + string package = "00200154 "; + + AssertBuildAndParse(package, new Mid0154(), true); + } } } diff --git a/src/MIDTesters.Core/MultipleIdentifiers/TestMid0155.cs b/src/MIDTesters.Core/MultipleIdentifiers/TestMid0155.cs index 7a99ce07..92fe9c9d 100644 --- a/src/MIDTesters.Core/MultipleIdentifiers/TestMid0155.cs +++ b/src/MIDTesters.Core/MultipleIdentifiers/TestMid0155.cs @@ -29,5 +29,14 @@ public void Mid0155ByteRevision1() Assert.AreEqual(typeof(Mid0155), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0155PackRevision1() + { + string package = "00200155 "; + + AssertBuildAndParse(package, new Mid0155(), true); + } } } diff --git a/src/MIDTesters.Core/MultipleIdentifiers/TestMid0156.cs b/src/MIDTesters.Core/MultipleIdentifiers/TestMid0156.cs index 697c7439..6362b120 100644 --- a/src/MIDTesters.Core/MultipleIdentifiers/TestMid0156.cs +++ b/src/MIDTesters.Core/MultipleIdentifiers/TestMid0156.cs @@ -29,5 +29,14 @@ public void Mid0156ByteRevision1() Assert.AreEqual(typeof(Mid0156), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0156PackRevision1() + { + string package = "00200156 "; + + AssertBuildAndParse(package, new Mid0156(), true); + } } } diff --git a/src/MIDTesters.Core/MultipleIdentifiers/TestMid0157.cs b/src/MIDTesters.Core/MultipleIdentifiers/TestMid0157.cs index a2d609df..80804c6a 100644 --- a/src/MIDTesters.Core/MultipleIdentifiers/TestMid0157.cs +++ b/src/MIDTesters.Core/MultipleIdentifiers/TestMid0157.cs @@ -29,5 +29,14 @@ public void Mid0157ByteRevision1() Assert.AreEqual(typeof(Mid0157), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0157PackRevision1() + { + string package = "00200157 "; + + AssertBuildAndParse(package, new Mid0157(), true); + } } } diff --git a/src/MIDTesters.Core/OpenProtocolCommandsDisabled/TestMid0420.cs b/src/MIDTesters.Core/OpenProtocolCommandsDisabled/TestMid0420.cs index 3b4275e0..daac9412 100644 --- a/src/MIDTesters.Core/OpenProtocolCommandsDisabled/TestMid0420.cs +++ b/src/MIDTesters.Core/OpenProtocolCommandsDisabled/TestMid0420.cs @@ -31,5 +31,14 @@ public void Mid0420ByteRevision1() Assert.IsTrue(mid.Header.NoAckFlag); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0420PackRevision1() + { + string package = "00200420 1 "; + + AssertBuildAndParse(package, new Mid0420(noAckFlag: true), true); + } } } diff --git a/src/MIDTesters.Core/OpenProtocolCommandsDisabled/TestMid0421.cs b/src/MIDTesters.Core/OpenProtocolCommandsDisabled/TestMid0421.cs index ee85e74b..40bb41ac 100644 --- a/src/MIDTesters.Core/OpenProtocolCommandsDisabled/TestMid0421.cs +++ b/src/MIDTesters.Core/OpenProtocolCommandsDisabled/TestMid0421.cs @@ -29,5 +29,17 @@ public void Mid0421ByteRevision1() Assert.IsTrue(mid.DigitalInputStatus); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0421PackRevision1() + { + string package = "00210421 1"; + + AssertBuildAndParse(package, new Mid0421() + { + DigitalInputStatus = true + }, true); + } } } diff --git a/src/MIDTesters.Core/OpenProtocolCommandsDisabled/TestMid0422.cs b/src/MIDTesters.Core/OpenProtocolCommandsDisabled/TestMid0422.cs index e81a6664..0d23ff08 100644 --- a/src/MIDTesters.Core/OpenProtocolCommandsDisabled/TestMid0422.cs +++ b/src/MIDTesters.Core/OpenProtocolCommandsDisabled/TestMid0422.cs @@ -29,5 +29,14 @@ public void Mid0422ByteRevision1() Assert.AreEqual(typeof(Mid0422), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0422PackRevision1() + { + string package = "00200422 "; + + AssertBuildAndParse(package, new Mid0422(), true); + } } } diff --git a/src/MIDTesters.Core/OpenProtocolCommandsDisabled/TestMid0423.cs b/src/MIDTesters.Core/OpenProtocolCommandsDisabled/TestMid0423.cs index 8afcbd68..79e12a78 100644 --- a/src/MIDTesters.Core/OpenProtocolCommandsDisabled/TestMid0423.cs +++ b/src/MIDTesters.Core/OpenProtocolCommandsDisabled/TestMid0423.cs @@ -29,5 +29,14 @@ public void Mid0423ByteRevision1() Assert.AreEqual(typeof(Mid0423), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0423PackRevision1() + { + string package = "00200423 "; + + AssertBuildAndParse(package, new Mid0423(), true); + } } } diff --git a/src/MIDTesters.Core/PLCUserData/TestMid0240.cs b/src/MIDTesters.Core/PLCUserData/TestMid0240.cs index 16d3e368..085183a6 100644 --- a/src/MIDTesters.Core/PLCUserData/TestMid0240.cs +++ b/src/MIDTesters.Core/PLCUserData/TestMid0240.cs @@ -40,5 +40,17 @@ public void Mid0240ShouldTrucanteUserData() Assert.AreEqual(208, mid0240.UserData.Length); Assert.IsTrue(mid0240.Pack().Length == 220); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0240PackRevision1() + { + string package = "00470240 My identifier less than 200"; + + AssertBuildAndParse(package, new Mid0240() + { + UserData = "My identifier less than 200" + }, true); + } } } diff --git a/src/MIDTesters.Core/PLCUserData/TestMid0241.cs b/src/MIDTesters.Core/PLCUserData/TestMid0241.cs index 502824b4..d61f1ec6 100644 --- a/src/MIDTesters.Core/PLCUserData/TestMid0241.cs +++ b/src/MIDTesters.Core/PLCUserData/TestMid0241.cs @@ -29,5 +29,14 @@ public void Mid0241ByteRevision1() Assert.IsTrue(mid.Header.NoAckFlag); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0241PackRevision1() + { + string package = "00200241 1 "; + + AssertBuildAndParse(package, new Mid0241(noAckFlag: true), true); + } } } diff --git a/src/MIDTesters.Core/PLCUserData/TestMid0242.cs b/src/MIDTesters.Core/PLCUserData/TestMid0242.cs index fe142ae3..88d8939b 100644 --- a/src/MIDTesters.Core/PLCUserData/TestMid0242.cs +++ b/src/MIDTesters.Core/PLCUserData/TestMid0242.cs @@ -31,5 +31,18 @@ public void Mid0242ByteRevision1() Assert.AreEqual("My identifier less than", mid.UserData); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0242PackRevision1() + { + string package = "00430242 1 My identifier less than"; + + AssertBuildAndParse(package, new Mid0242() + { + Header = { NoAckFlag = true }, + UserData = "My identifier less than" + }, true); + } } } diff --git a/src/MIDTesters.Core/PLCUserData/TestMid0243.cs b/src/MIDTesters.Core/PLCUserData/TestMid0243.cs index dcc2bbdb..dcc86f16 100644 --- a/src/MIDTesters.Core/PLCUserData/TestMid0243.cs +++ b/src/MIDTesters.Core/PLCUserData/TestMid0243.cs @@ -29,5 +29,14 @@ public void Mid0243ByteRevision1() Assert.AreEqual(typeof(Mid0243), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0243PackRevision1() + { + string package = "00200243 "; + + AssertBuildAndParse(package, new Mid0243(), true); + } } } diff --git a/src/MIDTesters.Core/PLCUserData/TestMid0244.cs b/src/MIDTesters.Core/PLCUserData/TestMid0244.cs index 6c7e586b..58abdbb0 100644 --- a/src/MIDTesters.Core/PLCUserData/TestMid0244.cs +++ b/src/MIDTesters.Core/PLCUserData/TestMid0244.cs @@ -29,5 +29,14 @@ public void Mid0244ByteRevision1() Assert.AreEqual(typeof(Mid0244), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0244PackRevision1() + { + string package = "00200244 "; + + AssertBuildAndParse(package, new Mid0244(), true); + } } } diff --git a/src/MIDTesters.Core/PLCUserData/TestMid0245.cs b/src/MIDTesters.Core/PLCUserData/TestMid0245.cs index 6beea3d2..305bcbea 100644 --- a/src/MIDTesters.Core/PLCUserData/TestMid0245.cs +++ b/src/MIDTesters.Core/PLCUserData/TestMid0245.cs @@ -43,5 +43,18 @@ public void Mid0245ShouldTruncateUserData() Assert.AreEqual(208, mid0245.UserData.Length); Assert.IsTrue(mid0245.Pack().Length == 223); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0245PackRevision1() + { + string package = "00460245 022My identifier less than"; + + AssertBuildAndParse(package, new Mid0245(1) + { + Offset = 22, + UserData = "My identifier less than" + }, true); + } } } diff --git a/src/MIDTesters.Core/ParameterSet/TestMid0010.cs b/src/MIDTesters.Core/ParameterSet/TestMid0010.cs index 4902d7c5..e296be0b 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid0010.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid0010.cs @@ -29,5 +29,14 @@ public void Mid0010ByteAllRevisions() Assert.AreEqual(typeof(Mid0010), mid.GetType()); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Pack")] + public void Mid0010PackAllRevisions() + { + string package = "00200010002 "; + + AssertBuildAndParse(package, new Mid0010(2)); + } } } diff --git a/src/MIDTesters.Core/ParameterSet/TestMid0011.cs b/src/MIDTesters.Core/ParameterSet/TestMid0011.cs index 4c58d124..a406674a 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid0011.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid0011.cs @@ -1,6 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter.ParameterSet; +using System.Collections.Generic; namespace MIDTesters.ParameterSet { [TestClass] @@ -42,5 +43,18 @@ public void Mid0011Revision1MaxParameters() Assert.AreEqual(999, mid.ParameterSets.Count); AssertEqualPackages(pack, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0011PackRevision1() + { + string package = "00290011 002001002"; + + AssertBuildAndParse(package, new Mid0011() + { + TotalParameterSets = 2, + ParameterSets = new List() { 1, 2 } + }, true); + } } } diff --git a/src/MIDTesters.Core/ParameterSet/TestMid0012.cs b/src/MIDTesters.Core/ParameterSet/TestMid0012.cs index e7def0e8..396f0bd8 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid0012.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid0012.cs @@ -102,5 +102,55 @@ public void Mid0012ByteRevision4() Assert.AreEqual(12345678, mid.ParameterSetFileVersion); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0012PackRevision1() + { + string package = "00230012 002"; + + AssertBuildAndParse(package, new Mid0012(1) + { + ParameterSetId = 2 + }, true); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0012PackRevision2() + { + string package = @"00230012002 002"; + + AssertBuildAndParse(package, new Mid0012(2) + { + ParameterSetId = 2 + }); + } + + [TestMethod] + [TestCategory("Revision 3"), TestCategory("Pack")] + public void Mid0012PackRevision3() + { + string package = "00310012003 00212345678"; + + AssertBuildAndParse(package, new Mid0012(3) + { + ParameterSetId = 2, + ParameterSetFileVersion = 12345678 + }); + } + + [TestMethod] + [TestCategory("Revision 4"), TestCategory("Pack")] + public void Mid0012PackRevision4() + { + string package = @"00310012004 00212345678"; + + AssertBuildAndParse(package, new Mid0012(4) + { + ParameterSetId = 2, + ParameterSetFileVersion = 12345678 + }); + } } } diff --git a/src/MIDTesters.Core/ParameterSet/TestMid0013.cs b/src/MIDTesters.Core/ParameterSet/TestMid0013.cs index 79ad1890..ac39823d 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid0013.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid0013.cs @@ -141,5 +141,58 @@ public void Mid0013ByteRevision5() Assert.AreEqual(new DateTime(2001, 5, 29, 12, 34, 33), mid.LastChangeInParameterSet); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0013PackRevision1() + { + string package = "01040013 0100102Airbag1 0310403050012000600150007001400080036009007201000480"; + + AssertBuildAndParse(package, BuildMid0013(1), true); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0013PackRevision2() + { + string package = @"01200013002 0100102Airbag1 03104030500120006001500070014000800360090072010004801102021112017854"; + + var mid = BuildMid0013(2); + mid.FirstTarget = 202.11m; + mid.StartFinalAngle = 178.54m; + + AssertBuildAndParse(package, mid); + } + + [TestMethod] + [TestCategory("Revision 5"), TestCategory("Pack")] + public void Mid0013PackRevision5() + { + string package = @"01410013005 0100102Airbag1 03104030500120006001500070014000800360090072010004801102021112017854132001-05-29:12:34:33"; + + var mid = BuildMid0013(5); + mid.FirstTarget = 202.11m; + mid.StartFinalAngle = 178.54m; + mid.LastChangeInParameterSet = new DateTime(2001, 5, 29, 12, 34, 33); + + AssertBuildAndParse(package, mid); + } + + private static Mid0013 BuildMid0013(int revision) + { + return new Mid0013(revision) + { + ParameterSetId = 1, + ParameterSetName = "Airbag1", + RotationDirection = RotationDirection.Clockwise, + BatchSize = 3, + MinTorque = 12, + MaxTorque = 15, + TorqueFinalTarget = 14, + MinAngle = 360, + MaxAngle = 720, + AngleFinalTarget = 480 + }; + } } } diff --git a/src/MIDTesters.Core/ParameterSet/TestMid0014.cs b/src/MIDTesters.Core/ParameterSet/TestMid0014.cs index 362fc751..5d93a2a9 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid0014.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid0014.cs @@ -29,5 +29,14 @@ public void Mid0014ByteRevision1() Assert.AreEqual(typeof(Mid0014), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0014PackRevision1() + { + string package = "00200014 "; + + AssertBuildAndParse(package, new Mid0014(), true); + } } } diff --git a/src/MIDTesters.Core/ParameterSet/TestMid0015.cs b/src/MIDTesters.Core/ParameterSet/TestMid0015.cs index f8a96766..8ce3fc4a 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid0015.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid0015.cs @@ -80,6 +80,42 @@ public void Mid0015ByteRevision2() Assert.AreEqual(10.06m, mid.StartFinalAngle); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0015PackRevision1() + { + string package = "00420015001 0022017-06-02:09:54:09"; + + AssertBuildAndParse(package, new Mid0015(1) + { + ParameterSetId = 2, + LastChangeInParameterSet = new DateTime(2017, 6, 2, 9, 54, 9) + }); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0015PackRevision2() + { + string package = "01410015002 0100202Airbag parameter 032017-06-02:09:54:0904205040600510107010009080050050900001109999911003601200123413001006"; + + AssertBuildAndParse(package, new Mid0015(2) + { + ParameterSetId = 2, + LastChangeInParameterSet = new DateTime(2017, 6, 2, 9, 54, 9), + ParameterSetName = "Airbag parameter", + RotationDirection = RotationDirection.Counterclockwise, + BatchSize = 4, + MinTorque = 51.01m, + MaxTorque = 100.09m, + TorqueFinalTarget = 50.05m, + MinAngle = 1, + MaxAngle = 99999, + AngleFinalTarget = 360, + FirstTarget = 12.34m, + StartFinalAngle = 10.06m + }); + } } } - diff --git a/src/MIDTesters.Core/ParameterSet/TestMid0016.cs b/src/MIDTesters.Core/ParameterSet/TestMid0016.cs index 7c3fd72c..fabcbcbc 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid0016.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid0016.cs @@ -29,5 +29,14 @@ public void Mid0016ByteRevision1() Assert.AreEqual(typeof(Mid0016), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0016PackRevision1() + { + string package = "00200016 "; + + AssertBuildAndParse(package, new Mid0016(1), true); + } } } diff --git a/src/MIDTesters.Core/ParameterSet/TestMid0017.cs b/src/MIDTesters.Core/ParameterSet/TestMid0017.cs index 89f64d80..e633ff80 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid0017.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid0017.cs @@ -29,5 +29,14 @@ public void Mid0017ByteRevision1() Assert.AreEqual(typeof(Mid0017), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0017PackRevision1() + { + string package = "00200017 "; + + AssertBuildAndParse(package, new Mid0017(), true); + } } } diff --git a/src/MIDTesters.Core/ParameterSet/TestMid0018.cs b/src/MIDTesters.Core/ParameterSet/TestMid0018.cs index d5c2e3a6..7098fbcf 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid0018.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid0018.cs @@ -29,5 +29,17 @@ public void Mid0018ByteRevision1() Assert.AreEqual(22, mid.ParameterSetId); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0018PackRevision1() + { + string package = "00230018001 022"; + + AssertBuildAndParse(package, new Mid0018() + { + ParameterSetId = 22 + }); + } } } diff --git a/src/MIDTesters.Core/ParameterSet/TestMid0019.cs b/src/MIDTesters.Core/ParameterSet/TestMid0019.cs index e36aab3a..9a94ae8b 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid0019.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid0019.cs @@ -1,6 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter.ParameterSet; +using OpenProtocolInterpreter; namespace MIDTesters.ParameterSet { [TestClass] @@ -56,5 +57,31 @@ public void Mid0019ByteRevision2() Assert.AreEqual(50, mid.BatchSize); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0019PackRevision1() + { + string package = "00250019 77750"; + + AssertBuildAndParse(package, new Mid0019() + { + ParameterSetId = 777, + BatchSize = 50 + }, true); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0019PackRevision2() + { + string package = "00270019002 7770050"; + + AssertBuildAndParse(package, new Mid0019(new Header() { Mid = Mid0019.MID, Revision = 2 }) + { + ParameterSetId = 777, + BatchSize = 50 + }); + } } } diff --git a/src/MIDTesters.Core/ParameterSet/TestMid0020.cs b/src/MIDTesters.Core/ParameterSet/TestMid0020.cs index b1d2c495..f11c3b40 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid0020.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid0020.cs @@ -29,5 +29,17 @@ public void Mid0020ByteRevision1() Assert.AreEqual(54, mid.ParameterSetId); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0020PackRevision1() + { + string package = "00230020 054"; + + AssertBuildAndParse(package, new Mid0020() + { + ParameterSetId = 54 + }, true); + } } } diff --git a/src/MIDTesters.Core/ParameterSet/TestMid0021.cs b/src/MIDTesters.Core/ParameterSet/TestMid0021.cs index c06fbbac..ad1ad1eb 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid0021.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid0021.cs @@ -31,5 +31,14 @@ public void Mid0021ByteRevision1() Assert.IsTrue(mid.Header.NoAckFlag); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0021PackRevision1() + { + string package = "00200021 1 "; + + AssertBuildAndParse(package, new Mid0021(noAckFlag: true), true); + } } } diff --git a/src/MIDTesters.Core/ParameterSet/TestMid0022.cs b/src/MIDTesters.Core/ParameterSet/TestMid0022.cs index 756be6c3..7fa5cf80 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid0022.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid0022.cs @@ -31,5 +31,18 @@ public void Mid0022ByteRevision1() Assert.IsTrue(mid.RelayStatus); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0022PackRevision1() + { + string package = "00210022 1 1"; + + AssertBuildAndParse(package, new Mid0022() + { + Header = { NoAckFlag = true }, + RelayStatus = true + }, true); + } } } diff --git a/src/MIDTesters.Core/ParameterSet/TestMid0023.cs b/src/MIDTesters.Core/ParameterSet/TestMid0023.cs index 68d6a64e..8b26315d 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid0023.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid0023.cs @@ -29,5 +29,14 @@ public void Mid0023ByteRevision1() Assert.AreEqual(typeof(Mid0023), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0023PackRevision1() + { + string package = "00200023 "; + + AssertBuildAndParse(package, new Mid0023(), true); + } } } diff --git a/src/MIDTesters.Core/ParameterSet/TestMid0024.cs b/src/MIDTesters.Core/ParameterSet/TestMid0024.cs index ae509f8d..f08ed054 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid0024.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid0024.cs @@ -29,5 +29,14 @@ public void Mid0024ByteRevision1() Assert.AreEqual(typeof(Mid0024), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0024PackRevision1() + { + string package = "00200024 "; + + AssertBuildAndParse(package, new Mid0024(), true); + } } } diff --git a/src/MIDTesters.Core/ParameterSet/TestMid2504.cs b/src/MIDTesters.Core/ParameterSet/TestMid2504.cs index 33c657a6..458a868a 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid2504.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid2504.cs @@ -30,5 +30,17 @@ public void Mid2504ByteRevision1() Assert.AreEqual(10, mid.ParameterSetId); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid2504PackRevision1() + { + string package = "00232504001 010"; + + AssertBuildAndParse(package, new Mid2504() + { + ParameterSetId = 10 + }); + } } } diff --git a/src/MIDTesters.Core/ParameterSet/TestMid2505.cs b/src/MIDTesters.Core/ParameterSet/TestMid2505.cs index 73ae0b7b..af916e89 100644 --- a/src/MIDTesters.Core/ParameterSet/TestMid2505.cs +++ b/src/MIDTesters.Core/ParameterSet/TestMid2505.cs @@ -2,6 +2,8 @@ using OpenProtocolInterpreter.Communication; using OpenProtocolInterpreter.ParameterSet; +using OpenProtocolInterpreter; +using System.Collections.Generic; namespace MIDTesters.ParameterSet { [TestClass] @@ -59,5 +61,23 @@ public void Mid2505ByteRevision1() // Assert.AreEqual(10, extraDataMid.ParameterSetId); // AssertEqualPackages(bytes, mid); // } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid2505PackRevision1() + { + string package = "00822505001 01000201000003010000000005010030190500000002022-08-12:13:33:22"; + + AssertBuildAndParse(package, new Mid2505(1) + { + ParameterSetId = 10, + NumberOfParameterDataFields = 2, + VariableDataFields = new List() + { + new VariableDataField() { ParameterId = 1000, Length = 3, DataType = DataTypeDefinition.UnsignedInteger, Unit = DataUnitType.NoUnit, StepNumber = 0, DataValue = "005" }, + new VariableDataField() { ParameterId = 1003, Length = 19, DataType = DataTypeDefinition.Timestamp, Unit = DataUnitType.NoUnit, StepNumber = 0, DataValue = "2022-08-12:13:33:22" } + } + }); + } } } diff --git a/src/MIDTesters.Core/PowerMACS/TestMid0105.cs b/src/MIDTesters.Core/PowerMACS/TestMid0105.cs index 7f349ca2..251b6898 100644 --- a/src/MIDTesters.Core/PowerMACS/TestMid0105.cs +++ b/src/MIDTesters.Core/PowerMACS/TestMid0105.cs @@ -102,5 +102,52 @@ public void Mid0105ByteRevision4() Assert.IsTrue(mid.SendOnlyNewData); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0105PackRevision1() + { + string pack = @"002001050011 "; + + AssertBuildAndParse(pack, new Mid0105(1, noAckFlag: true)); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0105PackRevision2() + { + string pack = @"003001050021 4294967295"; + + AssertBuildAndParse(pack, new Mid0105(2, noAckFlag: true) + { + DataNumberSystem = 4294967295L + }); + } + + [TestMethod] + [TestCategory("Revision 3"), TestCategory("Pack")] + public void Mid0105PackRevision3() + { + string pack = @"003101050031 42949672951"; + + AssertBuildAndParse(pack, new Mid0105(3, noAckFlag: true) + { + DataNumberSystem = 4294967295L, + SendOnlyNewData = true + }); + } + + [TestMethod] + [TestCategory("Revision 4"), TestCategory("Pack")] + public void Mid0105PackRevision4() + { + string pack = @"003101050041 32949672951"; + + AssertBuildAndParse(pack, new Mid0105(4, noAckFlag: true) + { + DataNumberSystem = 3294967295L, + SendOnlyNewData = true + }); + } } } diff --git a/src/MIDTesters.Core/PowerMACS/TestMid0106.cs b/src/MIDTesters.Core/PowerMACS/TestMid0106.cs index 21d04698..0af4d571 100644 --- a/src/MIDTesters.Core/PowerMACS/TestMid0106.cs +++ b/src/MIDTesters.Core/PowerMACS/TestMid0106.cs @@ -3,6 +3,7 @@ using OpenProtocolInterpreter.PowerMACS; using System; +using System.Collections.Generic; namespace MIDTesters.PowerMACS { [TestClass] @@ -59,5 +60,102 @@ public void Mid0106ByteRevision1() Assert.AreEqual(2, mid.SpecialValues.Count); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0106PackRevision1() + { + string package = "05050106 010502010300000381270401050231 062017-05-25:09:51:38071108Ap.320Nm Diant.P11 091101119BM384069HB066171 1204130114115116117329.9091835.854019360.00020310.000219999.002200.0000130214115116117328.73618-06.10219360.00020310.000219999.002200.0000130314115116117356.04518763.97619370.00020304.000219999.002200.0000130414115116117355.40718380.87219370.00020304.000219999.002200.00002302Data No Station I 100000027897Free No 1 I 100000000002"; + + AssertBuildAndParse(package, new Mid0106(1) + { + TotalNumberOfMessages = 5, + MessageNumber = 1, + DataNumberSystem = 38127L, + StationNumber = 1, + StationName = "0231", + Time = new DateTime(2017, 5, 25, 9, 51, 38), + ModeNumber = 11, + ModeName = "Ap.320Nm Diant.P11", + SimpleStatus = true, + PMStatus = PowerMacsStatus.Okr, + WpId = "9BM384069HB066171", + NumberOfBolts = 4, + BoltsData = new List() + { + new BoltData() + { + OrdinalBoltNumber = 1, + SimpleBoltStatus = true, + TorqueStatus = TorqueStatus.BoltTorqueOk, + AngleStatus = AngleStatus.BoltAngleOk, + BoltTorque = 329.909m, + BoltAngle = 35.8540m, + BoltTorqueHighLimit = 360.000m, + BoltTorqueLowLimit = 310.000m, + BoltAngleHighLimit = 9999.00m, + BoltAngleLowLimit = 0.0000m + }, + new BoltData() + { + OrdinalBoltNumber = 2, + SimpleBoltStatus = true, + TorqueStatus = TorqueStatus.BoltTorqueOk, + AngleStatus = AngleStatus.BoltAngleOk, + BoltTorque = 328.736m, + BoltAngle = -6.102m, + BoltTorqueHighLimit = 360.000m, + BoltTorqueLowLimit = 310.000m, + BoltAngleHighLimit = 9999.00m, + BoltAngleLowLimit = 0.0000m + }, + new BoltData() + { + OrdinalBoltNumber = 3, + SimpleBoltStatus = true, + TorqueStatus = TorqueStatus.BoltTorqueOk, + AngleStatus = AngleStatus.BoltAngleOk, + BoltTorque = 356.045m, + BoltAngle = 763.976m, + BoltTorqueHighLimit = 370.000m, + BoltTorqueLowLimit = 304.000m, + BoltAngleHighLimit = 9999.00m, + BoltAngleLowLimit = 0.0000m + }, + new BoltData() + { + OrdinalBoltNumber = 4, + SimpleBoltStatus = true, + TorqueStatus = TorqueStatus.BoltTorqueOk, + AngleStatus = AngleStatus.BoltAngleOk, + BoltTorque = 355.407m, + BoltAngle = 380.872m, + BoltTorqueHighLimit = 370.000m, + BoltTorqueLowLimit = 304.000m, + BoltAngleHighLimit = 9999.00m, + BoltAngleLowLimit = 0.0000m + } + }, + SpecialValues = new List() + { + new SpecialValue() + { + VariableName = "Data No Station", + Type = "I", + Length = 10, + Value = "0000027897", + StepNumber = 0 + }, + new SpecialValue() + { + VariableName = "Free No 1", + Type = "I", + Length = 10, + Value = "0000000002", + StepNumber = 0 + } + } + }, true); + } } } diff --git a/src/MIDTesters.Core/PowerMACS/TestMid0107.cs b/src/MIDTesters.Core/PowerMACS/TestMid0107.cs index c256b655..70f64492 100644 --- a/src/MIDTesters.Core/PowerMACS/TestMid0107.cs +++ b/src/MIDTesters.Core/PowerMACS/TestMid0107.cs @@ -3,6 +3,7 @@ using OpenProtocolInterpreter.PowerMACS; using System; +using System.Collections.Generic; namespace MIDTesters.PowerMACS { [TestClass] @@ -59,5 +60,73 @@ public void Mid0107ByteRevision1() Assert.AreEqual(1, mid.SpecialValues.Count); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0107PackRevision1() + { + string package = "03510107001 010202020300000381270401052017-05-25:09:51:3806000107My first bolt 08Ap.320Nm Diant.P11 09310 11E3211202Variable 1 I 1234567Variable 2 F 9999.9913002141Step Variable name 1I 765432101Step Variable name 2F 11.1234021501Special Value 1 S 13Got 13 digits01"; + + AssertBuildAndParse(package, new Mid0107() + { + TotalNumberOfMessages = 2, + MessageNumber = 2, + DataNumberSystem = 38127L, + StationNumber = 1, + Time = new DateTime(2017, 5, 25, 9, 51, 38), + BoltNumber = 1, + BoltName = "My first bolt", + ProgramName = "Ap.320Nm Diant.P11", + PowerMacsStatus = PowerMacsStatus.TermNok, + Errors = "", + CustomerErrorCode = "E321", + NumberOfBoltResults = 2, + BoltResults = new List() + { + new BoltResult() + { + VariableName = "Variable 1", + Type = "I", + Value = 1234567 + }, + new BoltResult() + { + VariableName = "Variable 2", + Type = "F", + Value = 9999.99m + } + }, + NumberOfStepResults = 2, + AllStepDataSent = true, + StepResults = new List() + { + new StepResult() + { + VariableName = "Step Variable name 1", + Type = "I", + Value = 7654321, + StepNumber = 1 + }, + new StepResult() + { + VariableName = "Step Variable name 2", + Type = "F", + Value = 11.1234m, + StepNumber = 2 + } + }, + SpecialValues = new List() + { + new SpecialValue() + { + VariableName = "Special Value 1", + Type = "S", + Length = 13, + Value = "Got 13 digits", + StepNumber = 1 + } + } + }); + } } } diff --git a/src/MIDTesters.Core/PowerMACS/TestMid0108.cs b/src/MIDTesters.Core/PowerMACS/TestMid0108.cs index edfde372..f7c1a465 100644 --- a/src/MIDTesters.Core/PowerMACS/TestMid0108.cs +++ b/src/MIDTesters.Core/PowerMACS/TestMid0108.cs @@ -29,5 +29,17 @@ public void Mid0108ByteAllRevisions() Assert.IsTrue(mid.BoltData); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0108PackAllRevisions() + { + string package = "00210108002 1"; + + AssertBuildAndParse(package, new Mid0108(2) + { + BoltData = true + }); + } } } diff --git a/src/MIDTesters.Core/PowerMACS/TestMid0109.cs b/src/MIDTesters.Core/PowerMACS/TestMid0109.cs index 9491f18e..6ce4f939 100644 --- a/src/MIDTesters.Core/PowerMACS/TestMid0109.cs +++ b/src/MIDTesters.Core/PowerMACS/TestMid0109.cs @@ -29,5 +29,16 @@ public void Mid0109ByteAllRevisions() Assert.AreEqual(typeof(Mid0109), mid.GetType()); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0109PackAllRevisions() + { + string package = "00200109002 "; + + AssertBuildAndParse(package, new Mid0109(2) + { + }); + } } } diff --git a/src/MIDTesters.Core/Result/TestMid1201.cs b/src/MIDTesters.Core/Result/TestMid1201.cs index 78fdac65..b85942c9 100644 --- a/src/MIDTesters.Core/Result/TestMid1201.cs +++ b/src/MIDTesters.Core/Result/TestMid1201.cs @@ -1,30 +1,37 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter.Result; +using OpenProtocolInterpreter; +using System; +using System.Collections.Generic; namespace MIDTesters.Result { [TestClass] [TestCategory("Result")] public class TestMid1201 : DefaultMidTests { + private const string Revision1Package = "01581201 00200100000001532022-11-14:09:35:1000000300100001110012000301000003010000000005010030190500000002022-08-12:13:33:2201001006040000001TORQUE"; + private const string Revision2Package = "01621201002 00200100000001532022-11-14:09:35:10000000800300100001110012000301000003010000000005010030190500000002022-08-12:13:33:2201001006040000001TORQUE"; + private const string Revision3Package = "01771201003 00200100000001532022-11-14:09:35:10000000800300100300920011120093001204009400301000003010000000005010030190500000002022-08-12:13:33:2201001006040000001TORQUE"; + [TestMethod] [TestCategory("Revision 1"), TestCategory("ASCII")] public void Mid1201Revision1() { - string package = "00691201 00200100000001532022-11-14:09:35:1000000100100000"; - var mid = _midInterpreter.Parse(package); + var mid = _midInterpreter.Parse(Revision1Package); - AssertEqualPackages(package, mid, true); + AssertParsedMid1201(mid, revision: 1); + AssertEqualPackages(Revision1Package, mid, true); } [TestMethod] [TestCategory("Revision 1"), TestCategory("ByteArray")] public void Mid1201ByteRevision1() { - string package = "00691201 00200100000001532022-11-14:09:35:1000000100100000"; - byte[] bytes = GetAsciiBytes(package); + byte[] bytes = GetAsciiBytes(Revision1Package); var mid = _midInterpreter.Parse(bytes); + AssertParsedMid1201(mid, revision: 1); AssertEqualPackages(bytes, mid, true); } @@ -32,20 +39,20 @@ public void Mid1201ByteRevision1() [TestCategory("Revision 2"), TestCategory("ASCII")] public void Mid1201Revision2() { - string package = "00731201002 00200100000001532022-11-14:09:35:10000000800100100000"; - var mid = _midInterpreter.Parse(package); + var mid = _midInterpreter.Parse(Revision2Package); - AssertEqualPackages(package, mid); + AssertParsedMid1201(mid, revision: 2); + AssertEqualPackages(Revision2Package, mid); } [TestMethod] [TestCategory("Revision 2"), TestCategory("ByteArray")] public void Mid1201ByteRevision2() { - string package = "00731201002 00200100000001532022-11-14:09:35:10000000800100100000"; - byte[] bytes = GetAsciiBytes(package); + byte[] bytes = GetAsciiBytes(Revision2Package); var mid = _midInterpreter.Parse(bytes); + AssertParsedMid1201(mid, revision: 2); AssertEqualPackages(bytes, mid); } @@ -53,21 +60,156 @@ public void Mid1201ByteRevision2() [TestCategory("Revision 3"), TestCategory("ASCII")] public void Mid1201Revision3() { - string package = "00781201003 00200100000001532022-11-14:09:35:1000000080010010030092000"; - var mid = _midInterpreter.Parse(package); + var mid = _midInterpreter.Parse(Revision3Package); - AssertEqualPackages(package, mid); + AssertParsedMid1201(mid, revision: 3); + AssertEqualPackages(Revision3Package, mid); } [TestMethod] [TestCategory("Revision 3"), TestCategory("ByteArray")] public void Mid1201ByteRevision3() { - string package = "00781201003 00200100000001532022-11-14:09:35:1000000080010010030092000"; - byte[] bytes = GetAsciiBytes(package); + byte[] bytes = GetAsciiBytes(Revision3Package); var mid = _midInterpreter.Parse(bytes); + AssertParsedMid1201(mid, revision: 3); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid1201PackRevision1() + { + AssertBuildAndParse(Revision1Package, BuildMid1201(1), true); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid1201PackRevision2() + { + var mid = BuildMid1201(2); + mid.RequestMid = 8; + + AssertBuildAndParse(Revision2Package, mid); + } + + [TestMethod] + [TestCategory("Revision 3"), TestCategory("Pack")] + public void Mid1201PackRevision3() + { + var mid = BuildMid1201(3); + mid.RequestMid = 8; + + AssertBuildAndParse(Revision3Package, mid); + } + + private static Mid1201 BuildMid1201(int revision) + { + return new Mid1201(revision) + { + TotalNumberOfMessages = 2, + MessageNumber = 1, + ResultDataIdentifier = 153, + Time = new DateTime(2022, 11, 14, 9, 35, 10), + ResultStatus = 0, + OperationType = OperationType.NonSynchronizedTightening, + ObjectDataList = BuildObjectDataList(revision), + VariableDataFields = BuildVariableDataFields() + }; + } + + /// + /// The object type and the reference object id only exist from revision 3 onwards, so they are left + /// at their default on the earlier revisions where they are not part of the message. + /// + private static List BuildObjectDataList(int revision) + { + if (revision < 3) + { + return new List() + { + new ObjectData() { Id = 10, Status = false }, + new ObjectData() { Id = 11, Status = true }, + new ObjectData() { Id = 12, Status = false } + }; + } + + return new List() + { + new ObjectData() { Id = 10, Status = false, ObjectType = ObjectType.TighteningSimulation, ReferenceObjectId = 92 }, + new ObjectData() { Id = 11, Status = true, ObjectType = ObjectType.TighteningProduction, ReferenceObjectId = 93 }, + new ObjectData() { Id = 12, Status = false, ObjectType = ObjectType.JointCheck, ReferenceObjectId = 94 } + }; + } + + private static List BuildVariableDataFields() + { + return new List() + { + new VariableDataField() { ParameterId = 1000, Length = 3, DataType = DataTypeDefinition.UnsignedInteger, Unit = DataUnitType.NoUnit, StepNumber = 0, DataValue = "005" }, + new VariableDataField() { ParameterId = 1003, Length = 19, DataType = DataTypeDefinition.Timestamp, Unit = DataUnitType.NoUnit, StepNumber = 0, DataValue = "2022-08-12:13:33:22" }, + new VariableDataField() { ParameterId = 1001, Length = 6, DataType = DataTypeDefinition.String, Unit = DataUnitType.NoUnit, StepNumber = 1, DataValue = "TORQUE" } + }; + } + + private static void AssertParsedMid1201(Mid1201 mid, int revision) + { + Assert.AreEqual(2, mid.TotalNumberOfMessages); + Assert.AreEqual(1, mid.MessageNumber); + Assert.AreEqual(153, mid.ResultDataIdentifier); + Assert.AreEqual(new DateTime(2022, 11, 14, 9, 35, 10), mid.Time); + Assert.AreEqual(0, mid.ResultStatus); + Assert.AreEqual(OperationType.NonSynchronizedTightening, mid.OperationType); + + if (revision > 1) + { + Assert.AreEqual(8, mid.RequestMid); + } + + Assert.AreEqual(3, mid.NumberOfObjects); + Assert.AreEqual(3, mid.ObjectDataList.Count); + Assert.AreEqual(10, mid.ObjectDataList[0].Id); + Assert.IsFalse(mid.ObjectDataList[0].Status); + Assert.AreEqual(11, mid.ObjectDataList[1].Id); + Assert.IsTrue(mid.ObjectDataList[1].Status); + Assert.AreEqual(12, mid.ObjectDataList[2].Id); + Assert.IsFalse(mid.ObjectDataList[2].Status); + + if (revision > 2) + { + Assert.AreEqual(ObjectType.TighteningSimulation, mid.ObjectDataList[0].ObjectType); + Assert.AreEqual(92, mid.ObjectDataList[0].ReferenceObjectId); + Assert.AreEqual(ObjectType.TighteningProduction, mid.ObjectDataList[1].ObjectType); + Assert.AreEqual(93, mid.ObjectDataList[1].ReferenceObjectId); + Assert.AreEqual(ObjectType.JointCheck, mid.ObjectDataList[2].ObjectType); + Assert.AreEqual(94, mid.ObjectDataList[2].ReferenceObjectId); + } + + Assert.AreEqual(3, mid.NumberOfDataFields); + Assert.AreEqual(3, mid.VariableDataFields.Count); + + var unsignedInteger = mid.VariableDataFields[0]; + Assert.AreEqual(1000, unsignedInteger.ParameterId); + Assert.AreEqual(3, unsignedInteger.Length); + Assert.AreEqual(DataTypeDefinition.UnsignedInteger, unsignedInteger.DataType); + Assert.AreEqual(DataUnitType.NoUnit, unsignedInteger.Unit); + Assert.AreEqual(0, unsignedInteger.StepNumber); + Assert.AreEqual("005", unsignedInteger.DataValue); + + var timestamp = mid.VariableDataFields[1]; + Assert.AreEqual(1003, timestamp.ParameterId); + Assert.AreEqual(19, timestamp.Length); + Assert.AreEqual(DataTypeDefinition.Timestamp, timestamp.DataType); + Assert.AreEqual(0, timestamp.StepNumber); + Assert.AreEqual("2022-08-12:13:33:22", timestamp.DataValue); + + var text = mid.VariableDataFields[2]; + Assert.AreEqual(1001, text.ParameterId); + Assert.AreEqual(6, text.Length); + Assert.AreEqual(DataTypeDefinition.String, text.DataType); + Assert.AreEqual(1, text.StepNumber); + Assert.AreEqual("TORQUE", text.DataValue); + } } } diff --git a/src/MIDTesters.Core/Statistic/TestMid0300.cs b/src/MIDTesters.Core/Statistic/TestMid0300.cs index 0505474d..a1613352 100644 --- a/src/MIDTesters.Core/Statistic/TestMid0300.cs +++ b/src/MIDTesters.Core/Statistic/TestMid0300.cs @@ -32,5 +32,18 @@ public void Mid0300ByteRevision1() Assert.AreEqual(HistogramType.Current, mid.HistogramType); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0300PackRevision1() + { + string package = "00290300 010020202"; + + AssertBuildAndParse(package, new Mid0300() + { + ParameterSetId = 2, + HistogramType = HistogramType.Current + }, true); + } } } diff --git a/src/MIDTesters.Core/Statistic/TestMid0301.cs b/src/MIDTesters.Core/Statistic/TestMid0301.cs index 3de04596..22ee01ea 100644 --- a/src/MIDTesters.Core/Statistic/TestMid0301.cs +++ b/src/MIDTesters.Core/Statistic/TestMid0301.cs @@ -56,5 +56,30 @@ public void Mid0301ByteRevision1() Assert.AreEqual(9999, mid.NinethBar); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0301PackRevision1() + { + string package = "01070301 010020205031234560465432105999999061111072222083333094444105555116666127777138888149999"; + + AssertBuildAndParse(package, new Mid0301() + { + ParameterSetId = 2, + HistogramType = HistogramType.RundownAngle, + SigmaHistogram = 1234.56m, + MeanValueHistogram = 6543.21m, + ClassRange = 9999.99m, + FirstBar = 1111, + SecondBar = 2222, + ThirdBar = 3333, + FourthBar = 4444, + FifthBar = 5555, + SixthBar = 6666, + SeventhBar = 7777, + EighthBar = 8888, + NinethBar = 9999 + }, true); + } } } diff --git a/src/MIDTesters.Core/Tightening/TestMid0060.cs b/src/MIDTesters.Core/Tightening/TestMid0060.cs index f84b15db..f663f033 100644 --- a/src/MIDTesters.Core/Tightening/TestMid0060.cs +++ b/src/MIDTesters.Core/Tightening/TestMid0060.cs @@ -29,5 +29,16 @@ public void Mid0060ByteAllRevisions() Assert.AreEqual(typeof(Mid0060), mid.GetType()); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 998"), TestCategory("Pack")] + public void Mid0060PackAllRevisions() + { + string package = "00200060998 "; + + AssertBuildAndParse(package, new Mid0060(998) + { + }); + } } } diff --git a/src/MIDTesters.Core/Tightening/TestMid0061.cs b/src/MIDTesters.Core/Tightening/TestMid0061.cs index 32c8ae7e..819eeeff 100644 --- a/src/MIDTesters.Core/Tightening/TestMid0061.cs +++ b/src/MIDTesters.Core/Tightening/TestMid0061.cs @@ -2153,5 +2153,1667 @@ public void Mid0061ByteRevision999() Assert.AreEqual(4294967295L, mid.TighteningId); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0061PackRevision1() + { + string package = "02310061001 010001020103airbag7 04KPOL3456JKLO897 050006003070000080000090100111120008401300140014001200150007391600000170999918000001900000202001-06-02:09:54:09212001-05-29:12:34:33221230000345675"; + + AssertBuildAndParse(package, new Mid0061(1) + { + CellId = 1, + ChannelId = 1, + TorqueControllerName = "airbag7", + VinNumber = "KPOL3456JKLO897", + JobId = 0, + ParameterSetId = 3, + BatchSize = 0, + BatchCounter = 0, + TighteningStatus = false, + TorqueStatus = TighteningValueStatus.Low, + AngleStatus = TighteningValueStatus.Ok, + TorqueMinLimit = 8.4m, + TorqueMaxLimit = 14m, + TorqueFinalTarget = 12m, + Torque = 7.39m, + AngleMinLimit = 0, + AngleMaxLimit = 9999, + AngleFinalTarget = 0, + Angle = 0, + Timestamp = new DateTime(2001, 6, 2, 9, 54, 9), + LastChangeInParameterSet = new DateTime(2001, 5, 29, 12, 34, 33), + BatchStatus = BatchStatus.Ok, + TighteningId = 345675L + }); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0061PackRevision2() + { + string package = "03850061002 010000020003RA ST6.2 ETV100 04 05000006001070208000670900001000001101221301401501611711811912000000081942100340022004600230040002400050525000202600420270000028000002900100300085031000043200033150340003500000036999900370000003800000039000000400000004100001848874200000430000044 C0761275452020-06-25:01:04:39462020-06-24:10:48:53"; + + AssertBuildAndParse(package, new Mid0061(2) + { + CellId = 0, + ChannelId = 0, + TorqueControllerName = "RA ST6.2 ETV100", + VinNumber = "", + JobId = 0, + ParameterSetId = 1, + BatchSize = 0, + BatchCounter = 0, + TighteningStatus = false, + TorqueStatus = TighteningValueStatus.Low, + AngleStatus = TighteningValueStatus.Low, + TorqueMinLimit = 34m, + TorqueMaxLimit = 46m, + TorqueFinalTarget = 40m, + Torque = 5.05m, + AngleMinLimit = 20, + AngleMaxLimit = 420, + AngleFinalTarget = 0, + Angle = 0, + Timestamp = new DateTime(2020, 6, 25, 1, 4, 39), + LastChangeInParameterSet = new DateTime(2020, 6, 24, 10, 48, 53), + BatchStatus = BatchStatus.NotUsed, + TighteningId = 184887L, + Strategy = Strategy.TorqueControlAndAngleMonitoring, + StrategyOptions = new StrategyOptions() + { + Torque = true, + Angle = true, + Batch = false, + PvtMonitoring = false, + PvtCompensate = false, + Selftap = false, + Rundown = true, + CM = false, + DsControl = false, + ClickWrench = false, + RbwMonitoring = false + }, + RundownAngleStatus = TighteningValueStatus.Low, + CurrentMonitoringStatus = TighteningValueStatus.Ok, + SelftapStatus = TighteningValueStatus.Ok, + PrevailTorqueMonitoringStatus = TighteningValueStatus.Ok, + PrevailTorqueCompensateStatus = TighteningValueStatus.Ok, + TighteningErrorStatus = new TighteningErrorStatus() + { + RundownAngleMaxShutOff = false, + RundownAngleMinShutOff = true, + TorqueMaxShutOff = false, + AngleMaxShutOff = false, + SelftapTorqueMaxShutOff = false, + SelftapTorqueMinShutOff = false, + PrevailTorqueMaxShutOff = false, + PrevailTorqueMinShutOff = false, + PrevailTorqueCompensateOverflow = false, + CurrentMonitoringMaxShutOff = false, + PostViewTorqueMinTorqueShutOff = false, + PostViewTorqueMaxTorqueShutOff = false, + PostViewTorqueAngleTooSmall = false, + TriggerLost = true, + TorqueLessThanTarget = false, + ToolHot = false, + MultistageAbort = false, + Rehit = false, + DsMeasureFailed = false, + CurrentLimitReached = false, + EndTimeOutShutOff = false, + RemoveFastenerLimitExceeded = false, + DisableDrive = false, + TransducerLost = false, + TransducerShorted = false, + TransducerCorrupt = false, + SyncTimeout = false, + DynamicCurrentMonitoringMin = false, + DynamicCurrentMonitoringMax = false, + AngleMaxMonitor = false, + YieldNutOff = false, + YieldTooFewSamples = false + }, + RundownAngleMin = 100, + RundownAngleMax = 850, + RundownAngle = 4, + CurrentMonitoringMin = 0, + CurrentMonitoringMax = 150, + CurrentMonitoringValue = 0, + SelftapMin = 0m, + SelftapMax = 9999m, + SelftapTorque = 0m, + PrevailTorqueMonitoringMin = 0m, + PrevailTorqueMonitoringMax = 0m, + PrevailTorque = 0m, + JobSequenceNumber = 0, + SyncTighteningId = 0, + ToolSerialNumber = " C0761275" + }); + } + + [TestMethod] + [TestCategory("Revision 3"), TestCategory("Pack")] + public void Mid0061PackRevision3() + { + string package = "04190061003 010000020003RA ST6.2 ETV100 04 05000006001070208000670900001000001101221301401501611711811912000000081942100340022004600230040002400050525000202600420270000028000002900100300085031000043200033150340003500000036999900370000003800000039000000400000004100001848874200000430000044 C0761275452020-06-25:01:04:39462020-06-24:10:48:5347Test Parameter Set 4824905"; + + AssertBuildAndParse(package, new Mid0061(3) + { + CellId = 0, + ChannelId = 0, + TorqueControllerName = "RA ST6.2 ETV100", + VinNumber = "", + JobId = 0, + ParameterSetId = 1, + BatchSize = 0, + BatchCounter = 0, + TighteningStatus = false, + TorqueStatus = TighteningValueStatus.Low, + AngleStatus = TighteningValueStatus.Low, + TorqueMinLimit = 34m, + TorqueMaxLimit = 46m, + TorqueFinalTarget = 40m, + Torque = 5.05m, + AngleMinLimit = 20, + AngleMaxLimit = 420, + AngleFinalTarget = 0, + Angle = 0, + Timestamp = new DateTime(2020, 6, 25, 1, 4, 39), + LastChangeInParameterSet = new DateTime(2020, 6, 24, 10, 48, 53), + BatchStatus = BatchStatus.NotUsed, + TighteningId = 184887L, + Strategy = Strategy.TorqueControlAndAngleMonitoring, + StrategyOptions = new StrategyOptions() + { + Torque = true, + Angle = true, + Batch = false, + PvtMonitoring = false, + PvtCompensate = false, + Selftap = false, + Rundown = true, + CM = false, + DsControl = false, + ClickWrench = false, + RbwMonitoring = false + }, + RundownAngleStatus = TighteningValueStatus.Low, + CurrentMonitoringStatus = TighteningValueStatus.Ok, + SelftapStatus = TighteningValueStatus.Ok, + PrevailTorqueMonitoringStatus = TighteningValueStatus.Ok, + PrevailTorqueCompensateStatus = TighteningValueStatus.Ok, + TighteningErrorStatus = new TighteningErrorStatus() + { + RundownAngleMaxShutOff = false, + RundownAngleMinShutOff = true, + TorqueMaxShutOff = false, + AngleMaxShutOff = false, + SelftapTorqueMaxShutOff = false, + SelftapTorqueMinShutOff = false, + PrevailTorqueMaxShutOff = false, + PrevailTorqueMinShutOff = false, + PrevailTorqueCompensateOverflow = false, + CurrentMonitoringMaxShutOff = false, + PostViewTorqueMinTorqueShutOff = false, + PostViewTorqueMaxTorqueShutOff = false, + PostViewTorqueAngleTooSmall = false, + TriggerLost = true, + TorqueLessThanTarget = false, + ToolHot = false, + MultistageAbort = false, + Rehit = false, + DsMeasureFailed = false, + CurrentLimitReached = false, + EndTimeOutShutOff = false, + RemoveFastenerLimitExceeded = false, + DisableDrive = false, + TransducerLost = false, + TransducerShorted = false, + TransducerCorrupt = false, + SyncTimeout = false, + DynamicCurrentMonitoringMin = false, + DynamicCurrentMonitoringMax = false, + AngleMaxMonitor = false, + YieldNutOff = false, + YieldTooFewSamples = false + }, + RundownAngleMin = 100, + RundownAngleMax = 850, + RundownAngle = 4, + CurrentMonitoringMin = 0, + CurrentMonitoringMax = 150, + CurrentMonitoringValue = 0, + SelftapMin = 0m, + SelftapMax = 9999m, + SelftapTorque = 0m, + PrevailTorqueMonitoringMin = 0m, + PrevailTorqueMonitoringMax = 0m, + PrevailTorque = 0m, + JobSequenceNumber = 0, + SyncTighteningId = 0, + ToolSerialNumber = " C0761275", + ParameterSetName = "Test Parameter Set", + TorqueValuesUnit = TorqueValuesUnit.LbfFt, + ResultType = ResultType.BypassParameterSetResult + }); + } + + [TestMethod] + [TestCategory("Revision 4"), TestCategory("Pack")] + public void Mid0061PackRevision4() + { + string package = "05000061004 010000020003RA ST6.2 ETV100 04 05000006001070208000670900001000001101221301401501611711811912000000081942100340022004600230040002400050525000202600420270000028000002900100300085031000043200033150340003500000036999900370000003800000039000000400000004100001848874200000430000044 C0761275452020-06-25:01:04:39462020-06-24:10:48:5347Test Parameter Set 482490550Identifier result part 2 51Identifier result part 3 52Identifier result part 4 "; + + AssertBuildAndParse(package, new Mid0061(4) + { + CellId = 0, + ChannelId = 0, + TorqueControllerName = "RA ST6.2 ETV100", + VinNumber = "", + JobId = 0, + ParameterSetId = 1, + BatchSize = 0, + BatchCounter = 0, + TighteningStatus = false, + TorqueStatus = TighteningValueStatus.Low, + AngleStatus = TighteningValueStatus.Low, + TorqueMinLimit = 34m, + TorqueMaxLimit = 46m, + TorqueFinalTarget = 40m, + Torque = 5.05m, + AngleMinLimit = 20, + AngleMaxLimit = 420, + AngleFinalTarget = 0, + Angle = 0, + Timestamp = new DateTime(2020, 6, 25, 1, 4, 39), + LastChangeInParameterSet = new DateTime(2020, 6, 24, 10, 48, 53), + BatchStatus = BatchStatus.NotUsed, + TighteningId = 184887L, + Strategy = Strategy.TorqueControlAndAngleMonitoring, + StrategyOptions = new StrategyOptions() + { + Torque = true, + Angle = true, + Batch = false, + PvtMonitoring = false, + PvtCompensate = false, + Selftap = false, + Rundown = true, + CM = false, + DsControl = false, + ClickWrench = false, + RbwMonitoring = false + }, + RundownAngleStatus = TighteningValueStatus.Low, + CurrentMonitoringStatus = TighteningValueStatus.Ok, + SelftapStatus = TighteningValueStatus.Ok, + PrevailTorqueMonitoringStatus = TighteningValueStatus.Ok, + PrevailTorqueCompensateStatus = TighteningValueStatus.Ok, + TighteningErrorStatus = new TighteningErrorStatus() + { + RundownAngleMaxShutOff = false, + RundownAngleMinShutOff = true, + TorqueMaxShutOff = false, + AngleMaxShutOff = false, + SelftapTorqueMaxShutOff = false, + SelftapTorqueMinShutOff = false, + PrevailTorqueMaxShutOff = false, + PrevailTorqueMinShutOff = false, + PrevailTorqueCompensateOverflow = false, + CurrentMonitoringMaxShutOff = false, + PostViewTorqueMinTorqueShutOff = false, + PostViewTorqueMaxTorqueShutOff = false, + PostViewTorqueAngleTooSmall = false, + TriggerLost = true, + TorqueLessThanTarget = false, + ToolHot = false, + MultistageAbort = false, + Rehit = false, + DsMeasureFailed = false, + CurrentLimitReached = false, + EndTimeOutShutOff = false, + RemoveFastenerLimitExceeded = false, + DisableDrive = false, + TransducerLost = false, + TransducerShorted = false, + TransducerCorrupt = false, + SyncTimeout = false, + DynamicCurrentMonitoringMin = false, + DynamicCurrentMonitoringMax = false, + AngleMaxMonitor = false, + YieldNutOff = false, + YieldTooFewSamples = false + }, + RundownAngleMin = 100, + RundownAngleMax = 850, + RundownAngle = 4, + CurrentMonitoringMin = 0, + CurrentMonitoringMax = 150, + CurrentMonitoringValue = 0, + SelftapMin = 0m, + SelftapMax = 9999m, + SelftapTorque = 0m, + PrevailTorqueMonitoringMin = 0m, + PrevailTorqueMonitoringMax = 0m, + PrevailTorque = 0m, + JobSequenceNumber = 0, + SyncTighteningId = 0, + ToolSerialNumber = " C0761275", + ParameterSetName = "Test Parameter Set", + TorqueValuesUnit = TorqueValuesUnit.LbfFt, + ResultType = ResultType.BypassParameterSetResult, + IdentifierResultPart2 = "Identifier result part 2", + IdentifierResultPart3 = "Identifier result part 3", + IdentifierResultPart4 = "Identifier result part 4" + }); + } + + [TestMethod] + [TestCategory("Revision 5"), TestCategory("Pack")] + public void Mid0061PackRevision5() + { + string package = "05060061005 010000020003RA ST6.2 ETV100 04 05000006001070208000670900001000001101221301401501611711811912000000081942100340022004600230040002400050525000202600420270000028000002900100300085031000043200033150340003500000036999900370000003800000039000000400000004100001848874200000430000044 C0761275452020-06-25:01:04:39462020-06-24:10:48:5347Test Parameter Set 482490550Identifier result part 2 51Identifier result part 3 52Identifier result part 4 53E124"; + + AssertBuildAndParse(package, new Mid0061(5) + { + CellId = 0, + ChannelId = 0, + TorqueControllerName = "RA ST6.2 ETV100", + VinNumber = "", + JobId = 0, + ParameterSetId = 1, + BatchSize = 0, + BatchCounter = 0, + TighteningStatus = false, + TorqueStatus = TighteningValueStatus.Low, + AngleStatus = TighteningValueStatus.Low, + TorqueMinLimit = 34m, + TorqueMaxLimit = 46m, + TorqueFinalTarget = 40m, + Torque = 5.05m, + AngleMinLimit = 20, + AngleMaxLimit = 420, + AngleFinalTarget = 0, + Angle = 0, + Timestamp = new DateTime(2020, 6, 25, 1, 4, 39), + LastChangeInParameterSet = new DateTime(2020, 6, 24, 10, 48, 53), + BatchStatus = BatchStatus.NotUsed, + TighteningId = 184887L, + Strategy = Strategy.TorqueControlAndAngleMonitoring, + StrategyOptions = new StrategyOptions() + { + Torque = true, + Angle = true, + Batch = false, + PvtMonitoring = false, + PvtCompensate = false, + Selftap = false, + Rundown = true, + CM = false, + DsControl = false, + ClickWrench = false, + RbwMonitoring = false + }, + RundownAngleStatus = TighteningValueStatus.Low, + CurrentMonitoringStatus = TighteningValueStatus.Ok, + SelftapStatus = TighteningValueStatus.Ok, + PrevailTorqueMonitoringStatus = TighteningValueStatus.Ok, + PrevailTorqueCompensateStatus = TighteningValueStatus.Ok, + TighteningErrorStatus = new TighteningErrorStatus() + { + RundownAngleMaxShutOff = false, + RundownAngleMinShutOff = true, + TorqueMaxShutOff = false, + AngleMaxShutOff = false, + SelftapTorqueMaxShutOff = false, + SelftapTorqueMinShutOff = false, + PrevailTorqueMaxShutOff = false, + PrevailTorqueMinShutOff = false, + PrevailTorqueCompensateOverflow = false, + CurrentMonitoringMaxShutOff = false, + PostViewTorqueMinTorqueShutOff = false, + PostViewTorqueMaxTorqueShutOff = false, + PostViewTorqueAngleTooSmall = false, + TriggerLost = true, + TorqueLessThanTarget = false, + ToolHot = false, + MultistageAbort = false, + Rehit = false, + DsMeasureFailed = false, + CurrentLimitReached = false, + EndTimeOutShutOff = false, + RemoveFastenerLimitExceeded = false, + DisableDrive = false, + TransducerLost = false, + TransducerShorted = false, + TransducerCorrupt = false, + SyncTimeout = false, + DynamicCurrentMonitoringMin = false, + DynamicCurrentMonitoringMax = false, + AngleMaxMonitor = false, + YieldNutOff = false, + YieldTooFewSamples = false + }, + RundownAngleMin = 100, + RundownAngleMax = 850, + RundownAngle = 4, + CurrentMonitoringMin = 0, + CurrentMonitoringMax = 150, + CurrentMonitoringValue = 0, + SelftapMin = 0m, + SelftapMax = 9999m, + SelftapTorque = 0m, + PrevailTorqueMonitoringMin = 0m, + PrevailTorqueMonitoringMax = 0m, + PrevailTorque = 0m, + JobSequenceNumber = 0, + SyncTighteningId = 0, + ToolSerialNumber = " C0761275", + ParameterSetName = "Test Parameter Set", + TorqueValuesUnit = TorqueValuesUnit.LbfFt, + ResultType = ResultType.BypassParameterSetResult, + IdentifierResultPart2 = "Identifier result part 2", + IdentifierResultPart3 = "Identifier result part 3", + IdentifierResultPart4 = "Identifier result part 4", + CustomerTighteningErrorCode = "E124" + }); + } + + [TestMethod] + [TestCategory("Revision 6"), TestCategory("Pack")] + public void Mid0061PackRevision6() + { + string package = "05260061006 010000020003RA ST6.2 ETV100 04 05000006001070208000670900001000001101221301401501611711811912000000081942100340022004600230040002400050525000202600420270000028000002900100300085031000043200033150340003500000036999900370000003800000039000000400000004100001848874200000430000044 C0761275452020-06-25:01:04:39462020-06-24:10:48:5347Test Parameter Set 482490550Identifier result part 2 51Identifier result part 3 52Identifier result part 4 53E12454001500550000000042"; + + AssertBuildAndParse(package, new Mid0061(6) + { + CellId = 0, + ChannelId = 0, + TorqueControllerName = "RA ST6.2 ETV100", + VinNumber = "", + JobId = 0, + ParameterSetId = 1, + BatchSize = 0, + BatchCounter = 0, + TighteningStatus = false, + TorqueStatus = TighteningValueStatus.Low, + AngleStatus = TighteningValueStatus.Low, + TorqueMinLimit = 34m, + TorqueMaxLimit = 46m, + TorqueFinalTarget = 40m, + Torque = 5.05m, + AngleMinLimit = 20, + AngleMaxLimit = 420, + AngleFinalTarget = 0, + Angle = 0, + Timestamp = new DateTime(2020, 6, 25, 1, 4, 39), + LastChangeInParameterSet = new DateTime(2020, 6, 24, 10, 48, 53), + BatchStatus = BatchStatus.NotUsed, + TighteningId = 184887L, + Strategy = Strategy.TorqueControlAndAngleMonitoring, + StrategyOptions = new StrategyOptions() + { + Torque = true, + Angle = true, + Batch = false, + PvtMonitoring = false, + PvtCompensate = false, + Selftap = false, + Rundown = true, + CM = false, + DsControl = false, + ClickWrench = false, + RbwMonitoring = false + }, + RundownAngleStatus = TighteningValueStatus.Low, + CurrentMonitoringStatus = TighteningValueStatus.Ok, + SelftapStatus = TighteningValueStatus.Ok, + PrevailTorqueMonitoringStatus = TighteningValueStatus.Ok, + PrevailTorqueCompensateStatus = TighteningValueStatus.Ok, + TighteningErrorStatus = new TighteningErrorStatus() + { + RundownAngleMaxShutOff = false, + RundownAngleMinShutOff = true, + TorqueMaxShutOff = false, + AngleMaxShutOff = false, + SelftapTorqueMaxShutOff = false, + SelftapTorqueMinShutOff = false, + PrevailTorqueMaxShutOff = false, + PrevailTorqueMinShutOff = false, + PrevailTorqueCompensateOverflow = false, + CurrentMonitoringMaxShutOff = false, + PostViewTorqueMinTorqueShutOff = false, + PostViewTorqueMaxTorqueShutOff = false, + PostViewTorqueAngleTooSmall = false, + TriggerLost = true, + TorqueLessThanTarget = false, + ToolHot = false, + MultistageAbort = false, + Rehit = false, + DsMeasureFailed = false, + CurrentLimitReached = false, + EndTimeOutShutOff = false, + RemoveFastenerLimitExceeded = false, + DisableDrive = false, + TransducerLost = false, + TransducerShorted = false, + TransducerCorrupt = false, + SyncTimeout = false, + DynamicCurrentMonitoringMin = false, + DynamicCurrentMonitoringMax = false, + AngleMaxMonitor = false, + YieldNutOff = false, + YieldTooFewSamples = false + }, + RundownAngleMin = 100, + RundownAngleMax = 850, + RundownAngle = 4, + CurrentMonitoringMin = 0, + CurrentMonitoringMax = 150, + CurrentMonitoringValue = 0, + SelftapMin = 0m, + SelftapMax = 9999m, + SelftapTorque = 0m, + PrevailTorqueMonitoringMin = 0m, + PrevailTorqueMonitoringMax = 0m, + PrevailTorque = 0m, + JobSequenceNumber = 0, + SyncTighteningId = 0, + ToolSerialNumber = " C0761275", + ParameterSetName = "Test Parameter Set", + TorqueValuesUnit = TorqueValuesUnit.LbfFt, + ResultType = ResultType.BypassParameterSetResult, + IdentifierResultPart2 = "Identifier result part 2", + IdentifierResultPart3 = "Identifier result part 3", + IdentifierResultPart4 = "Identifier result part 4", + CustomerTighteningErrorCode = "E124", + PrevailTorqueCompensateValue = 15m, + TighteningErrorStatus2 = new TighteningErrorStatus2() + { + DriveDeactivated = false, + ToolStall = true, + DriveHot = false, + GradientMonitoringHigh = true, + GradientMonitoringLow = false, + ReactionBarFailed = true, + SnugMax = false, + CycleAbort = false, + NeckingFailure = false, + EffectiveLoosening = false, + OverSpeed = false, + NoResidualTorque = false, + PositioningFail = false, + SnugMonLow = false, + SnugMonHigh = false, + DynamicMinCurrent = false, + DynamicMaxCurrent = false, + LatentResult = false, + Reserved = new byte[] + { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + } + } + }); + } + + [TestMethod] + [TestCategory("Revision 7"), TestCategory("Pack")] + public void Mid0061PackRevision7() + { + string package = "05440061007 010000020003RA ST6.2 ETV100 04 05000006001070208000670900001000001101221301401501611711811912000000081942100340022004600230040002400050525000202600420270000028000002900100300085031000043200033150340003500000036999900370000003800000039000000400000004100001848874200000430000044 C0761275452020-06-25:01:04:39462020-06-24:10:48:5347Test Parameter Set 482490550Identifier result part 2 51Identifier result part 3 52Identifier result part 4 53E12454001500550000000042560010000570999900"; + + AssertBuildAndParse(package, new Mid0061(7) + { + CellId = 0, + ChannelId = 0, + TorqueControllerName = "RA ST6.2 ETV100", + VinNumber = "", + JobId = 0, + ParameterSetId = 1, + BatchSize = 0, + BatchCounter = 0, + TighteningStatus = false, + TorqueStatus = TighteningValueStatus.Low, + AngleStatus = TighteningValueStatus.Low, + TorqueMinLimit = 34m, + TorqueMaxLimit = 46m, + TorqueFinalTarget = 40m, + Torque = 5.05m, + AngleMinLimit = 20, + AngleMaxLimit = 420, + AngleFinalTarget = 0, + Angle = 0, + Timestamp = new DateTime(2020, 6, 25, 1, 4, 39), + LastChangeInParameterSet = new DateTime(2020, 6, 24, 10, 48, 53), + BatchStatus = BatchStatus.NotUsed, + TighteningId = 184887L, + Strategy = Strategy.TorqueControlAndAngleMonitoring, + StrategyOptions = new StrategyOptions() + { + Torque = true, + Angle = true, + Batch = false, + PvtMonitoring = false, + PvtCompensate = false, + Selftap = false, + Rundown = true, + CM = false, + DsControl = false, + ClickWrench = false, + RbwMonitoring = false + }, + RundownAngleStatus = TighteningValueStatus.Low, + CurrentMonitoringStatus = TighteningValueStatus.Ok, + SelftapStatus = TighteningValueStatus.Ok, + PrevailTorqueMonitoringStatus = TighteningValueStatus.Ok, + PrevailTorqueCompensateStatus = TighteningValueStatus.Ok, + TighteningErrorStatus = new TighteningErrorStatus() + { + RundownAngleMaxShutOff = false, + RundownAngleMinShutOff = true, + TorqueMaxShutOff = false, + AngleMaxShutOff = false, + SelftapTorqueMaxShutOff = false, + SelftapTorqueMinShutOff = false, + PrevailTorqueMaxShutOff = false, + PrevailTorqueMinShutOff = false, + PrevailTorqueCompensateOverflow = false, + CurrentMonitoringMaxShutOff = false, + PostViewTorqueMinTorqueShutOff = false, + PostViewTorqueMaxTorqueShutOff = false, + PostViewTorqueAngleTooSmall = false, + TriggerLost = true, + TorqueLessThanTarget = false, + ToolHot = false, + MultistageAbort = false, + Rehit = false, + DsMeasureFailed = false, + CurrentLimitReached = false, + EndTimeOutShutOff = false, + RemoveFastenerLimitExceeded = false, + DisableDrive = false, + TransducerLost = false, + TransducerShorted = false, + TransducerCorrupt = false, + SyncTimeout = false, + DynamicCurrentMonitoringMin = false, + DynamicCurrentMonitoringMax = false, + AngleMaxMonitor = false, + YieldNutOff = false, + YieldTooFewSamples = false + }, + RundownAngleMin = 100, + RundownAngleMax = 850, + RundownAngle = 4, + CurrentMonitoringMin = 0, + CurrentMonitoringMax = 150, + CurrentMonitoringValue = 0, + SelftapMin = 0m, + SelftapMax = 9999m, + SelftapTorque = 0m, + PrevailTorqueMonitoringMin = 0m, + PrevailTorqueMonitoringMax = 0m, + PrevailTorque = 0m, + JobSequenceNumber = 0, + SyncTighteningId = 0, + ToolSerialNumber = " C0761275", + ParameterSetName = "Test Parameter Set", + TorqueValuesUnit = TorqueValuesUnit.LbfFt, + ResultType = ResultType.BypassParameterSetResult, + IdentifierResultPart2 = "Identifier result part 2", + IdentifierResultPart3 = "Identifier result part 3", + IdentifierResultPart4 = "Identifier result part 4", + CustomerTighteningErrorCode = "E124", + PrevailTorqueCompensateValue = 15m, + TighteningErrorStatus2 = new TighteningErrorStatus2() + { + DriveDeactivated = false, + ToolStall = true, + DriveHot = false, + GradientMonitoringHigh = true, + GradientMonitoringLow = false, + ReactionBarFailed = true, + SnugMax = false, + CycleAbort = false, + NeckingFailure = false, + EffectiveLoosening = false, + OverSpeed = false, + NoResidualTorque = false, + PositioningFail = false, + SnugMonLow = false, + SnugMonHigh = false, + DynamicMinCurrent = false, + DynamicMaxCurrent = false, + LatentResult = false, + Reserved = new byte[] + { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + } + }, + CompensatedAngle = 100m, + FinalAngleDecimal = 9999m + }); + } + + [TestMethod] + [TestCategory("Revision 8"), TestCategory("Pack")] + public void Mid0061PackRevision8() + { + string package = "05710061008 010000020003RA ST6.2 ETV100 04 05000006001070208000670900001000001101221301401501611711811912000000081942100340022004600230040002400050525000202600420270000028000002900100300085031000043200033150340003500000036999900370000003800000039000000400000004100001848874200000430000044 C0761275452020-06-25:01:04:39462020-06-24:10:48:5347Test Parameter Set 482490550Identifier result part 2 51Identifier result part 3 52Identifier result part 4 53E12454001500550000000042560010000570999900580010005926065214361005232"; + + AssertBuildAndParse(package, new Mid0061(8) + { + CellId = 0, + ChannelId = 0, + TorqueControllerName = "RA ST6.2 ETV100", + VinNumber = "", + JobId = 0, + ParameterSetId = 1, + BatchSize = 0, + BatchCounter = 0, + TighteningStatus = false, + TorqueStatus = TighteningValueStatus.Low, + AngleStatus = TighteningValueStatus.Low, + TorqueMinLimit = 34m, + TorqueMaxLimit = 46m, + TorqueFinalTarget = 40m, + Torque = 5.05m, + AngleMinLimit = 20, + AngleMaxLimit = 420, + AngleFinalTarget = 0, + Angle = 0, + Timestamp = new DateTime(2020, 6, 25, 1, 4, 39), + LastChangeInParameterSet = new DateTime(2020, 6, 24, 10, 48, 53), + BatchStatus = BatchStatus.NotUsed, + TighteningId = 184887L, + Strategy = Strategy.TorqueControlAndAngleMonitoring, + StrategyOptions = new StrategyOptions() + { + Torque = true, + Angle = true, + Batch = false, + PvtMonitoring = false, + PvtCompensate = false, + Selftap = false, + Rundown = true, + CM = false, + DsControl = false, + ClickWrench = false, + RbwMonitoring = false + }, + RundownAngleStatus = TighteningValueStatus.Low, + CurrentMonitoringStatus = TighteningValueStatus.Ok, + SelftapStatus = TighteningValueStatus.Ok, + PrevailTorqueMonitoringStatus = TighteningValueStatus.Ok, + PrevailTorqueCompensateStatus = TighteningValueStatus.Ok, + TighteningErrorStatus = new TighteningErrorStatus() + { + RundownAngleMaxShutOff = false, + RundownAngleMinShutOff = true, + TorqueMaxShutOff = false, + AngleMaxShutOff = false, + SelftapTorqueMaxShutOff = false, + SelftapTorqueMinShutOff = false, + PrevailTorqueMaxShutOff = false, + PrevailTorqueMinShutOff = false, + PrevailTorqueCompensateOverflow = false, + CurrentMonitoringMaxShutOff = false, + PostViewTorqueMinTorqueShutOff = false, + PostViewTorqueMaxTorqueShutOff = false, + PostViewTorqueAngleTooSmall = false, + TriggerLost = true, + TorqueLessThanTarget = false, + ToolHot = false, + MultistageAbort = false, + Rehit = false, + DsMeasureFailed = false, + CurrentLimitReached = false, + EndTimeOutShutOff = false, + RemoveFastenerLimitExceeded = false, + DisableDrive = false, + TransducerLost = false, + TransducerShorted = false, + TransducerCorrupt = false, + SyncTimeout = false, + DynamicCurrentMonitoringMin = false, + DynamicCurrentMonitoringMax = false, + AngleMaxMonitor = false, + YieldNutOff = false, + YieldTooFewSamples = false + }, + RundownAngleMin = 100, + RundownAngleMax = 850, + RundownAngle = 4, + CurrentMonitoringMin = 0, + CurrentMonitoringMax = 150, + CurrentMonitoringValue = 0, + SelftapMin = 0m, + SelftapMax = 9999m, + SelftapTorque = 0m, + PrevailTorqueMonitoringMin = 0m, + PrevailTorqueMonitoringMax = 0m, + PrevailTorque = 0m, + JobSequenceNumber = 0, + SyncTighteningId = 0, + ToolSerialNumber = " C0761275", + ParameterSetName = "Test Parameter Set", + TorqueValuesUnit = TorqueValuesUnit.LbfFt, + ResultType = ResultType.BypassParameterSetResult, + IdentifierResultPart2 = "Identifier result part 2", + IdentifierResultPart3 = "Identifier result part 3", + IdentifierResultPart4 = "Identifier result part 4", + CustomerTighteningErrorCode = "E124", + PrevailTorqueCompensateValue = 15m, + TighteningErrorStatus2 = new TighteningErrorStatus2() + { + DriveDeactivated = false, + ToolStall = true, + DriveHot = false, + GradientMonitoringHigh = true, + GradientMonitoringLow = false, + ReactionBarFailed = true, + SnugMax = false, + CycleAbort = false, + NeckingFailure = false, + EffectiveLoosening = false, + OverSpeed = false, + NoResidualTorque = false, + PositioningFail = false, + SnugMonLow = false, + SnugMonHigh = false, + DynamicMinCurrent = false, + DynamicMaxCurrent = false, + LatentResult = false, + Reserved = new byte[] + { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + } + }, + CompensatedAngle = 100m, + FinalAngleDecimal = 9999m, + StartFinalAngle = 10m, + PostViewTorqueActivated = PostViewTorque.OnlyPVTHOn, + PostViewTorqueHigh = 6521.43m, + PostViewTorqueLow = 52.32m + }); + } + + [TestMethod] + [TestCategory("Revision 9"), TestCategory("Pack")] + public void Mid0061PackRevision9() + { + string package = "05920061009 010000020003RA ST6.2 ETV100 04 05000006001070208000670900001000001101221301401501611711811912000000081942100340022004600230040002400050525000202600420270000028000002900100300085031000043200033150340003500000036999900370000003800000039000000400000004100001848874200000430000044 C0761275452020-06-25:01:04:39462020-06-24:10:48:5347Test Parameter Set 482490550Identifier result part 2 51Identifier result part 3 52Identifier result part 4 53E12454001500550000000042560010000570999900580010005926065214361005232620010063000506400150"; + + AssertBuildAndParse(package, new Mid0061(9) + { + CellId = 0, + ChannelId = 0, + TorqueControllerName = "RA ST6.2 ETV100", + VinNumber = "", + JobId = 0, + ParameterSetId = 1, + BatchSize = 0, + BatchCounter = 0, + TighteningStatus = false, + TorqueStatus = TighteningValueStatus.Low, + AngleStatus = TighteningValueStatus.Low, + TorqueMinLimit = 34m, + TorqueMaxLimit = 46m, + TorqueFinalTarget = 40m, + Torque = 5.05m, + AngleMinLimit = 20, + AngleMaxLimit = 420, + AngleFinalTarget = 0, + Angle = 0, + Timestamp = new DateTime(2020, 6, 25, 1, 4, 39), + LastChangeInParameterSet = new DateTime(2020, 6, 24, 10, 48, 53), + BatchStatus = BatchStatus.NotUsed, + TighteningId = 184887L, + Strategy = Strategy.TorqueControlAndAngleMonitoring, + StrategyOptions = new StrategyOptions() + { + Torque = true, + Angle = true, + Batch = false, + PvtMonitoring = false, + PvtCompensate = false, + Selftap = false, + Rundown = true, + CM = false, + DsControl = false, + ClickWrench = false, + RbwMonitoring = false + }, + RundownAngleStatus = TighteningValueStatus.Low, + CurrentMonitoringStatus = TighteningValueStatus.Ok, + SelftapStatus = TighteningValueStatus.Ok, + PrevailTorqueMonitoringStatus = TighteningValueStatus.Ok, + PrevailTorqueCompensateStatus = TighteningValueStatus.Ok, + TighteningErrorStatus = new TighteningErrorStatus() + { + RundownAngleMaxShutOff = false, + RundownAngleMinShutOff = true, + TorqueMaxShutOff = false, + AngleMaxShutOff = false, + SelftapTorqueMaxShutOff = false, + SelftapTorqueMinShutOff = false, + PrevailTorqueMaxShutOff = false, + PrevailTorqueMinShutOff = false, + PrevailTorqueCompensateOverflow = false, + CurrentMonitoringMaxShutOff = false, + PostViewTorqueMinTorqueShutOff = false, + PostViewTorqueMaxTorqueShutOff = false, + PostViewTorqueAngleTooSmall = false, + TriggerLost = true, + TorqueLessThanTarget = false, + ToolHot = false, + MultistageAbort = false, + Rehit = false, + DsMeasureFailed = false, + CurrentLimitReached = false, + EndTimeOutShutOff = false, + RemoveFastenerLimitExceeded = false, + DisableDrive = false, + TransducerLost = false, + TransducerShorted = false, + TransducerCorrupt = false, + SyncTimeout = false, + DynamicCurrentMonitoringMin = false, + DynamicCurrentMonitoringMax = false, + AngleMaxMonitor = false, + YieldNutOff = false, + YieldTooFewSamples = false + }, + RundownAngleMin = 100, + RundownAngleMax = 850, + RundownAngle = 4, + CurrentMonitoringMin = 0, + CurrentMonitoringMax = 150, + CurrentMonitoringValue = 0, + SelftapMin = 0m, + SelftapMax = 9999m, + SelftapTorque = 0m, + PrevailTorqueMonitoringMin = 0m, + PrevailTorqueMonitoringMax = 0m, + PrevailTorque = 0m, + JobSequenceNumber = 0, + SyncTighteningId = 0, + ToolSerialNumber = " C0761275", + ParameterSetName = "Test Parameter Set", + TorqueValuesUnit = TorqueValuesUnit.LbfFt, + ResultType = ResultType.BypassParameterSetResult, + IdentifierResultPart2 = "Identifier result part 2", + IdentifierResultPart3 = "Identifier result part 3", + IdentifierResultPart4 = "Identifier result part 4", + CustomerTighteningErrorCode = "E124", + PrevailTorqueCompensateValue = 15m, + TighteningErrorStatus2 = new TighteningErrorStatus2() + { + DriveDeactivated = false, + ToolStall = true, + DriveHot = false, + GradientMonitoringHigh = true, + GradientMonitoringLow = false, + ReactionBarFailed = true, + SnugMax = false, + CycleAbort = false, + NeckingFailure = false, + EffectiveLoosening = false, + OverSpeed = false, + NoResidualTorque = false, + PositioningFail = false, + SnugMonLow = false, + SnugMonHigh = false, + DynamicMinCurrent = false, + DynamicMaxCurrent = false, + LatentResult = false, + Reserved = new byte[] + { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + } + }, + CompensatedAngle = 100m, + FinalAngleDecimal = 9999m, + StartFinalAngle = 10m, + PostViewTorqueActivated = PostViewTorque.OnlyPVTHOn, + PostViewTorqueHigh = 6521.43m, + PostViewTorqueLow = 52.32m, + CurrentMonitoringAmpere = 1m, + CurrentMonitoringAmpereMin = 0.5m, + CurrentMonitoringAmpereMax = 1.5m + }); + } + + [TestMethod] + [TestCategory("Revision 10"), TestCategory("Pack")] + public void Mid0061PackRevision10() + { + string package = "06620061010 010000020003RA ST6.2 ETV100 04 05000006001070208000670900001000001101221301401501611711811912000000081942100340022004600230040002400050525000202600420270000028000002900100300085031000043200033150340003500000036999900370000003800000039000000400000004100001848874200000430000044 C0761275452020-06-25:01:04:39462020-06-24:10:48:5347Test Parameter Set 482490550Identifier result part 2 51Identifier result part 3 52Identifier result part 4 53E124540015005500000000425600100005709999005800100059260652143610052326200100630005064001506500001660000167168-00206900100700012071003291720010057300011074009000"; + + AssertBuildAndParse(package, new Mid0061(10) + { + CellId = 0, + ChannelId = 0, + TorqueControllerName = "RA ST6.2 ETV100", + VinNumber = "", + JobId = 0, + ParameterSetId = 1, + BatchSize = 0, + BatchCounter = 0, + TighteningStatus = false, + TorqueStatus = TighteningValueStatus.Low, + AngleStatus = TighteningValueStatus.Low, + TorqueMinLimit = 34m, + TorqueMaxLimit = 46m, + TorqueFinalTarget = 40m, + Torque = 5.05m, + AngleMinLimit = 20, + AngleMaxLimit = 420, + AngleFinalTarget = 0, + Angle = 0, + Timestamp = new DateTime(2020, 6, 25, 1, 4, 39), + LastChangeInParameterSet = new DateTime(2020, 6, 24, 10, 48, 53), + BatchStatus = BatchStatus.NotUsed, + TighteningId = 184887L, + Strategy = Strategy.TorqueControlAndAngleMonitoring, + StrategyOptions = new StrategyOptions() + { + Torque = true, + Angle = true, + Batch = false, + PvtMonitoring = false, + PvtCompensate = false, + Selftap = false, + Rundown = true, + CM = false, + DsControl = false, + ClickWrench = false, + RbwMonitoring = false + }, + RundownAngleStatus = TighteningValueStatus.Low, + CurrentMonitoringStatus = TighteningValueStatus.Ok, + SelftapStatus = TighteningValueStatus.Ok, + PrevailTorqueMonitoringStatus = TighteningValueStatus.Ok, + PrevailTorqueCompensateStatus = TighteningValueStatus.Ok, + TighteningErrorStatus = new TighteningErrorStatus() + { + RundownAngleMaxShutOff = false, + RundownAngleMinShutOff = true, + TorqueMaxShutOff = false, + AngleMaxShutOff = false, + SelftapTorqueMaxShutOff = false, + SelftapTorqueMinShutOff = false, + PrevailTorqueMaxShutOff = false, + PrevailTorqueMinShutOff = false, + PrevailTorqueCompensateOverflow = false, + CurrentMonitoringMaxShutOff = false, + PostViewTorqueMinTorqueShutOff = false, + PostViewTorqueMaxTorqueShutOff = false, + PostViewTorqueAngleTooSmall = false, + TriggerLost = true, + TorqueLessThanTarget = false, + ToolHot = false, + MultistageAbort = false, + Rehit = false, + DsMeasureFailed = false, + CurrentLimitReached = false, + EndTimeOutShutOff = false, + RemoveFastenerLimitExceeded = false, + DisableDrive = false, + TransducerLost = false, + TransducerShorted = false, + TransducerCorrupt = false, + SyncTimeout = false, + DynamicCurrentMonitoringMin = false, + DynamicCurrentMonitoringMax = false, + AngleMaxMonitor = false, + YieldNutOff = false, + YieldTooFewSamples = false + }, + RundownAngleMin = 100, + RundownAngleMax = 850, + RundownAngle = 4, + CurrentMonitoringMin = 0, + CurrentMonitoringMax = 150, + CurrentMonitoringValue = 0, + SelftapMin = 0m, + SelftapMax = 9999m, + SelftapTorque = 0m, + PrevailTorqueMonitoringMin = 0m, + PrevailTorqueMonitoringMax = 0m, + PrevailTorque = 0m, + JobSequenceNumber = 0, + SyncTighteningId = 0, + ToolSerialNumber = " C0761275", + ParameterSetName = "Test Parameter Set", + TorqueValuesUnit = TorqueValuesUnit.LbfFt, + ResultType = ResultType.BypassParameterSetResult, + IdentifierResultPart2 = "Identifier result part 2", + IdentifierResultPart3 = "Identifier result part 3", + IdentifierResultPart4 = "Identifier result part 4", + CustomerTighteningErrorCode = "E124", + PrevailTorqueCompensateValue = 15m, + TighteningErrorStatus2 = new TighteningErrorStatus2() + { + DriveDeactivated = false, + ToolStall = true, + DriveHot = false, + GradientMonitoringHigh = true, + GradientMonitoringLow = false, + ReactionBarFailed = true, + SnugMax = false, + CycleAbort = false, + NeckingFailure = false, + EffectiveLoosening = false, + OverSpeed = false, + NoResidualTorque = false, + PositioningFail = false, + SnugMonLow = false, + SnugMonHigh = false, + DynamicMinCurrent = false, + DynamicMaxCurrent = false, + LatentResult = false, + Reserved = new byte[] + { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + } + }, + CompensatedAngle = 100m, + FinalAngleDecimal = 9999m, + StartFinalAngle = 10m, + PostViewTorqueActivated = PostViewTorque.OnlyPVTHOn, + PostViewTorqueHigh = 6521.43m, + PostViewTorqueLow = 52.32m, + CurrentMonitoringAmpere = 1m, + CurrentMonitoringAmpereMin = 0.5m, + CurrentMonitoringAmpereMax = 1.5m, + AngleNumeratorScaleFactor = 1, + AngleDenominatorScaleFactor = 1, + OverallAngleStatus = TighteningValueStatus.Ok, + OverallAngleMin = -20, + OverallAngleMax = 100, + OverallAngle = 120, + PeakTorque = 32.91m, + ResidualBreakawayTorque = 10.05m, + StartRundownAngle = 1.1m, + RundownAngleComplete = 90m + }); + } + + [TestMethod] + [TestCategory("Revision 11"), TestCategory("Pack")] + public void Mid0061PackRevision11() + { + string package = "06770061011 010000020003RA ST6.2 ETV100 04 05000006001070208000670900001000001101221301401501611711811912000000081942100340022004600230040002400050525000202600420270000028000002900100300085031000043200033150340003500000036999900370000003800000039000000400000004100001848874200000430000044 C0761275452020-06-25:01:04:39462020-06-24:10:48:5347Test Parameter Set 482490550Identifier result part 2 51Identifier result part 3 52Identifier result part 4 53E124540015005500000000425600100005709999005800100059260652143610052326200100630005064001506500001660000167168-00206900100700012071003291720010057300011074009000750001207600001"; + + AssertBuildAndParse(package, new Mid0061(11) + { + CellId = 0, + ChannelId = 0, + TorqueControllerName = "RA ST6.2 ETV100", + VinNumber = "", + JobId = 0, + ParameterSetId = 1, + BatchSize = 0, + BatchCounter = 0, + TighteningStatus = false, + TorqueStatus = TighteningValueStatus.Low, + AngleStatus = TighteningValueStatus.Low, + TorqueMinLimit = 34m, + TorqueMaxLimit = 46m, + TorqueFinalTarget = 40m, + Torque = 5.05m, + AngleMinLimit = 20, + AngleMaxLimit = 420, + AngleFinalTarget = 0, + Angle = 0, + Timestamp = new DateTime(2020, 6, 25, 1, 4, 39), + LastChangeInParameterSet = new DateTime(2020, 6, 24, 10, 48, 53), + BatchStatus = BatchStatus.NotUsed, + TighteningId = 184887L, + Strategy = Strategy.TorqueControlAndAngleMonitoring, + StrategyOptions = new StrategyOptions() + { + Torque = true, + Angle = true, + Batch = false, + PvtMonitoring = false, + PvtCompensate = false, + Selftap = false, + Rundown = true, + CM = false, + DsControl = false, + ClickWrench = false, + RbwMonitoring = false + }, + RundownAngleStatus = TighteningValueStatus.Low, + CurrentMonitoringStatus = TighteningValueStatus.Ok, + SelftapStatus = TighteningValueStatus.Ok, + PrevailTorqueMonitoringStatus = TighteningValueStatus.Ok, + PrevailTorqueCompensateStatus = TighteningValueStatus.Ok, + TighteningErrorStatus = new TighteningErrorStatus() + { + RundownAngleMaxShutOff = false, + RundownAngleMinShutOff = true, + TorqueMaxShutOff = false, + AngleMaxShutOff = false, + SelftapTorqueMaxShutOff = false, + SelftapTorqueMinShutOff = false, + PrevailTorqueMaxShutOff = false, + PrevailTorqueMinShutOff = false, + PrevailTorqueCompensateOverflow = false, + CurrentMonitoringMaxShutOff = false, + PostViewTorqueMinTorqueShutOff = false, + PostViewTorqueMaxTorqueShutOff = false, + PostViewTorqueAngleTooSmall = false, + TriggerLost = true, + TorqueLessThanTarget = false, + ToolHot = false, + MultistageAbort = false, + Rehit = false, + DsMeasureFailed = false, + CurrentLimitReached = false, + EndTimeOutShutOff = false, + RemoveFastenerLimitExceeded = false, + DisableDrive = false, + TransducerLost = false, + TransducerShorted = false, + TransducerCorrupt = false, + SyncTimeout = false, + DynamicCurrentMonitoringMin = false, + DynamicCurrentMonitoringMax = false, + AngleMaxMonitor = false, + YieldNutOff = false, + YieldTooFewSamples = false + }, + RundownAngleMin = 100, + RundownAngleMax = 850, + RundownAngle = 4, + CurrentMonitoringMin = 0, + CurrentMonitoringMax = 150, + CurrentMonitoringValue = 0, + SelftapMin = 0m, + SelftapMax = 9999m, + SelftapTorque = 0m, + PrevailTorqueMonitoringMin = 0m, + PrevailTorqueMonitoringMax = 0m, + PrevailTorque = 0m, + JobSequenceNumber = 0, + SyncTighteningId = 0, + ToolSerialNumber = " C0761275", + ParameterSetName = "Test Parameter Set", + TorqueValuesUnit = TorqueValuesUnit.LbfFt, + ResultType = ResultType.BypassParameterSetResult, + IdentifierResultPart2 = "Identifier result part 2", + IdentifierResultPart3 = "Identifier result part 3", + IdentifierResultPart4 = "Identifier result part 4", + CustomerTighteningErrorCode = "E124", + PrevailTorqueCompensateValue = 15m, + TighteningErrorStatus2 = new TighteningErrorStatus2() + { + DriveDeactivated = false, + ToolStall = true, + DriveHot = false, + GradientMonitoringHigh = true, + GradientMonitoringLow = false, + ReactionBarFailed = true, + SnugMax = false, + CycleAbort = false, + NeckingFailure = false, + EffectiveLoosening = false, + OverSpeed = false, + NoResidualTorque = false, + PositioningFail = false, + SnugMonLow = false, + SnugMonHigh = false, + DynamicMinCurrent = false, + DynamicMaxCurrent = false, + LatentResult = false, + Reserved = new byte[] + { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + } + }, + CompensatedAngle = 100m, + FinalAngleDecimal = 9999m, + StartFinalAngle = 10m, + PostViewTorqueActivated = PostViewTorque.OnlyPVTHOn, + PostViewTorqueHigh = 6521.43m, + PostViewTorqueLow = 52.32m, + CurrentMonitoringAmpere = 1m, + CurrentMonitoringAmpereMin = 0.5m, + CurrentMonitoringAmpereMax = 1.5m, + AngleNumeratorScaleFactor = 1, + AngleDenominatorScaleFactor = 1, + OverallAngleStatus = TighteningValueStatus.Ok, + OverallAngleMin = -20, + OverallAngleMax = 100, + OverallAngle = 120, + PeakTorque = 32.91m, + ResidualBreakawayTorque = 10.05m, + StartRundownAngle = 1.1m, + RundownAngleComplete = 90m, + ClickTorque = 1.2m, + ClickAngle = 1 + }); + } + + [TestMethod] + [TestCategory("Revision 998"), TestCategory("Pack")] + public void Mid0061PackRevision998() + { + string package = "05580061998 010000020003RA ST6.2 ETV100 04 05000006001070208000670900001000001101221301401501611711811912000000081942100340022004600230040002400050525000202600420270000028000002900100300085031000043200033150340003500000036999900370000003800000039000000400000004100001848874200000430000044 C0761275452020-06-25:01:04:39462020-06-24:10:48:5347Test Parameter Set 482490550Identifier result part 2 51Identifier result part 3 52Identifier result part 4 53E1245400150055000000004256025702580200000010001200000080"; + + AssertBuildAndParse(package, new Mid0061(998) + { + CellId = 0, + ChannelId = 0, + TorqueControllerName = "RA ST6.2 ETV100", + VinNumber = "", + JobId = 0, + ParameterSetId = 1, + BatchSize = 0, + BatchCounter = 0, + TighteningStatus = false, + TorqueStatus = TighteningValueStatus.Low, + AngleStatus = TighteningValueStatus.Low, + TorqueMinLimit = 34m, + TorqueMaxLimit = 46m, + TorqueFinalTarget = 40m, + Torque = 5.05m, + AngleMinLimit = 20, + AngleMaxLimit = 420, + AngleFinalTarget = 0, + Angle = 0, + Timestamp = new DateTime(2020, 6, 25, 1, 4, 39), + LastChangeInParameterSet = new DateTime(2020, 6, 24, 10, 48, 53), + BatchStatus = BatchStatus.NotUsed, + TighteningId = 184887L, + Strategy = Strategy.TorqueControlAndAngleMonitoring, + StrategyOptions = new StrategyOptions() + { + Torque = true, + Angle = true, + Batch = false, + PvtMonitoring = false, + PvtCompensate = false, + Selftap = false, + Rundown = true, + CM = false, + DsControl = false, + ClickWrench = false, + RbwMonitoring = false + }, + RundownAngleStatus = TighteningValueStatus.Low, + CurrentMonitoringStatus = TighteningValueStatus.Ok, + SelftapStatus = TighteningValueStatus.Ok, + PrevailTorqueMonitoringStatus = TighteningValueStatus.Ok, + PrevailTorqueCompensateStatus = TighteningValueStatus.Ok, + TighteningErrorStatus = new TighteningErrorStatus() + { + RundownAngleMaxShutOff = false, + RundownAngleMinShutOff = true, + TorqueMaxShutOff = false, + AngleMaxShutOff = false, + SelftapTorqueMaxShutOff = false, + SelftapTorqueMinShutOff = false, + PrevailTorqueMaxShutOff = false, + PrevailTorqueMinShutOff = false, + PrevailTorqueCompensateOverflow = false, + CurrentMonitoringMaxShutOff = false, + PostViewTorqueMinTorqueShutOff = false, + PostViewTorqueMaxTorqueShutOff = false, + PostViewTorqueAngleTooSmall = false, + TriggerLost = true, + TorqueLessThanTarget = false, + ToolHot = false, + MultistageAbort = false, + Rehit = false, + DsMeasureFailed = false, + CurrentLimitReached = false, + EndTimeOutShutOff = false, + RemoveFastenerLimitExceeded = false, + DisableDrive = false, + TransducerLost = false, + TransducerShorted = false, + TransducerCorrupt = false, + SyncTimeout = false, + DynamicCurrentMonitoringMin = false, + DynamicCurrentMonitoringMax = false, + AngleMaxMonitor = false, + YieldNutOff = false, + YieldTooFewSamples = false + }, + RundownAngleMin = 100, + RundownAngleMax = 850, + RundownAngle = 4, + CurrentMonitoringMin = 0, + CurrentMonitoringMax = 150, + CurrentMonitoringValue = 0, + SelftapMin = 0m, + SelftapMax = 9999m, + SelftapTorque = 0m, + PrevailTorqueMonitoringMin = 0m, + PrevailTorqueMonitoringMax = 0m, + PrevailTorque = 0m, + JobSequenceNumber = 0, + SyncTighteningId = 0, + ToolSerialNumber = " C0761275", + ParameterSetName = "Test Parameter Set", + TorqueValuesUnit = TorqueValuesUnit.LbfFt, + ResultType = ResultType.BypassParameterSetResult, + IdentifierResultPart2 = "Identifier result part 2", + IdentifierResultPart3 = "Identifier result part 3", + IdentifierResultPart4 = "Identifier result part 4", + CustomerTighteningErrorCode = "E124", + PrevailTorqueCompensateValue = 15m, + TighteningErrorStatus2 = new TighteningErrorStatus2() + { + DriveDeactivated = false, + ToolStall = true, + DriveHot = false, + GradientMonitoringHigh = true, + GradientMonitoringLow = false, + ReactionBarFailed = true, + SnugMax = false, + CycleAbort = false, + NeckingFailure = false, + EffectiveLoosening = false, + OverSpeed = false, + NoResidualTorque = false, + PositioningFail = false, + SnugMonLow = false, + SnugMonHigh = false, + DynamicMinCurrent = false, + DynamicMaxCurrent = false, + LatentResult = false, + Reserved = new byte[] + { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + } + }, + CompensatedAngle = 0m, + FinalAngleDecimal = 0m, + StartFinalAngle = 0m, + PostViewTorqueActivated = PostViewTorque.Off, + PostViewTorqueHigh = 0m, + PostViewTorqueLow = 0m, + CurrentMonitoringAmpere = 0m, + CurrentMonitoringAmpereMin = 0m, + CurrentMonitoringAmpereMax = 0m, + AngleNumeratorScaleFactor = 0, + AngleDenominatorScaleFactor = 0, + OverallAngleStatus = TighteningValueStatus.Low, + OverallAngleMin = 0, + OverallAngleMax = 0, + OverallAngle = 0, + PeakTorque = 0m, + ResidualBreakawayTorque = 0m, + StartRundownAngle = 0m, + RundownAngleComplete = 0m, + ClickTorque = 0m, + ClickAngle = 0, + SelectedIdentifierNumber = 0, + NumberOfStagesInMultistage = 2, + NumberOfStageResults = 2, + StageResults = new List() + { + new StageResult() + { + Torque = 200m, + Angle = 100 + }, + new StageResult() + { + Torque = 120m, + Angle = 80 + } + } + }); + } + + [TestMethod] + [TestCategory("Revision 999"), TestCategory("Pack")] + public void Mid0061PackRevision999() + { + string package = "01210061999 KPOL3456JKLO897 02001002000192111000500003602001-06-02:09:54:092000-06-02:09:54:094294967295"; + + AssertBuildAndParse(package, new Mid0061(999) + { + CellId = 0, + ChannelId = 0, + VinNumber = "KPOL3456JKLO897", + JobId = 2, + ParameterSetId = 1, + BatchSize = 20, + BatchCounter = 19, + TighteningStatus = true, + TorqueStatus = TighteningValueStatus.Ok, + AngleStatus = TighteningValueStatus.Ok, + TorqueMinLimit = 0m, + TorqueMaxLimit = 0m, + TorqueFinalTarget = 0m, + Torque = 5m, + AngleMinLimit = 0, + AngleMaxLimit = 0, + AngleFinalTarget = 0, + Angle = 360, + Timestamp = new DateTime(2001, 6, 2, 9, 54, 9), + LastChangeInParameterSet = new DateTime(2000, 6, 2, 9, 54, 9), + BatchStatus = BatchStatus.NotUsed, + TighteningId = 4294967295L, + Strategy = (Strategy)0, + RundownAngleStatus = TighteningValueStatus.Low, + CurrentMonitoringStatus = TighteningValueStatus.Low, + SelftapStatus = TighteningValueStatus.Low, + PrevailTorqueMonitoringStatus = TighteningValueStatus.Low, + PrevailTorqueCompensateStatus = TighteningValueStatus.Low, + RundownAngleMin = 0, + RundownAngleMax = 0, + RundownAngle = 0, + CurrentMonitoringMin = 0, + CurrentMonitoringMax = 0, + CurrentMonitoringValue = 0, + SelftapMin = 0m, + SelftapMax = 0m, + SelftapTorque = 0m, + PrevailTorqueMonitoringMin = 0m, + PrevailTorqueMonitoringMax = 0m, + PrevailTorque = 0m, + JobSequenceNumber = 0, + SyncTighteningId = 0, + TorqueValuesUnit = (TorqueValuesUnit)0, + ResultType = (ResultType)0, + PrevailTorqueCompensateValue = 0m, + CompensatedAngle = 0m, + FinalAngleDecimal = 0m, + StartFinalAngle = 0m, + PostViewTorqueActivated = PostViewTorque.Off, + PostViewTorqueHigh = 0m, + PostViewTorqueLow = 0m, + CurrentMonitoringAmpere = 0m, + CurrentMonitoringAmpereMin = 0m, + CurrentMonitoringAmpereMax = 0m, + AngleNumeratorScaleFactor = 0, + AngleDenominatorScaleFactor = 0, + OverallAngleStatus = TighteningValueStatus.Low, + OverallAngleMin = 0, + OverallAngleMax = 0, + OverallAngle = 0, + PeakTorque = 0m, + ResidualBreakawayTorque = 0m, + StartRundownAngle = 0m, + RundownAngleComplete = 0m, + ClickTorque = 0m, + ClickAngle = 0, + SelectedIdentifierNumber = 0, + NumberOfStagesInMultistage = 0, + NumberOfStageResults = 0 + }); + } } } diff --git a/src/MIDTesters.Core/Tightening/TestMid0062.cs b/src/MIDTesters.Core/Tightening/TestMid0062.cs index 6a94ef1c..b8d090f4 100644 --- a/src/MIDTesters.Core/Tightening/TestMid0062.cs +++ b/src/MIDTesters.Core/Tightening/TestMid0062.cs @@ -29,5 +29,16 @@ public void Mid0062ByteAllRevisions() Assert.AreEqual(typeof(Mid0062), mid.GetType()); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 5"), TestCategory("Pack")] + public void Mid0062PackAllRevisions() + { + string package = "00200062005 "; + + AssertBuildAndParse(package, new Mid0062(5) + { + }); + } } } diff --git a/src/MIDTesters.Core/Tightening/TestMid0063.cs b/src/MIDTesters.Core/Tightening/TestMid0063.cs index 5bd0437c..65edff38 100644 --- a/src/MIDTesters.Core/Tightening/TestMid0063.cs +++ b/src/MIDTesters.Core/Tightening/TestMid0063.cs @@ -29,5 +29,16 @@ public void Mid0063ByteAllRevisions() Assert.AreEqual(typeof(Mid0063), mid.GetType()); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0063PackAllRevisions() + { + string package = "00200063002 "; + + AssertBuildAndParse(package, new Mid0063(2) + { + }); + } } } diff --git a/src/MIDTesters.Core/Tightening/TestMid0064.cs b/src/MIDTesters.Core/Tightening/TestMid0064.cs index afc086f9..3b391940 100644 --- a/src/MIDTesters.Core/Tightening/TestMid0064.cs +++ b/src/MIDTesters.Core/Tightening/TestMid0064.cs @@ -58,5 +58,30 @@ public void Mid0064ByteRevision10() Assert.IsTrue(mid.OfflineResult); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0064PackRevision1() + { + string package = "00300064001 0123456789"; + + AssertBuildAndParse(package, new Mid0064(1) + { + TighteningId = 123456789L + }); + } + + [TestMethod] + [TestCategory("Revision 10"), TestCategory("Pack")] + public void Mid0064PackRevision10() + { + string package = "00310064010 01234567891"; + + AssertBuildAndParse(package, new Mid0064(10) + { + TighteningId = 123456789L, + OfflineResult = true + }); + } } } diff --git a/src/MIDTesters.Core/Tightening/TestMid0065.cs b/src/MIDTesters.Core/Tightening/TestMid0065.cs index e9ab52bd..8a6b5757 100644 --- a/src/MIDTesters.Core/Tightening/TestMid0065.cs +++ b/src/MIDTesters.Core/Tightening/TestMid0065.cs @@ -1515,5 +1515,1203 @@ public void Mid0065ByteRevision11() Assert.AreEqual(1, mid.ClickAngle); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0065PackRevision1() + { + string package = "01180065001 01012345678902AIRBAG 03001040002050060070080014670900046102001-04-22:14:54:34112"; + + AssertBuildAndParse(package, new Mid0065(1) + { + TighteningId = 123456789L, + VinNumber = "AIRBAG", + ParameterSetId = 1, + BatchCounter = 2, + TighteningStatus = false, + TorqueStatus = TighteningValueStatus.Low, + AngleStatus = TighteningValueStatus.Low, + Torque = 14.67m, + Angle = 46, + Timestamp = new DateTime(2001, 4, 22, 14, 54, 34), + BatchStatus = BatchStatus.NotUsed + }); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0065PackRevision2() + { + string package = "02260065002 01012345678902AIRBAG 030001040020510060093807000008000009010011112013214115016217118000004189819001000200036021002502200223000215240010252500012266553527ABCDEFG-123 282019-01-05:09:00:05"; + + AssertBuildAndParse(package, new Mid0065(2) + { + TighteningId = 123456789L, + VinNumber = "AIRBAG", + ParameterSetId = 2, + BatchCounter = 0, + TighteningStatus = false, + TorqueStatus = TighteningValueStatus.Ok, + AngleStatus = TighteningValueStatus.Low, + Torque = 10m, + Angle = 360, + Timestamp = new DateTime(2019, 1, 5, 9, 0, 5), + BatchStatus = BatchStatus.Nok, + JobId = 1, + Strategy = Strategy.RotateSpindleForward, + StrategyOptions = new StrategyOptions() + { + Torque = false, + Angle = true, + Batch = false, + PvtMonitoring = true, + PvtCompensate = false, + Selftap = true, + Rundown = false, + CM = true, + DsControl = true, + ClickWrench = true, + RbwMonitoring = false + }, + BatchSize = 0, + RundownAngleStatus = TighteningValueStatus.High, + CurrentMonitoringStatus = TighteningValueStatus.Ok, + SelftapStatus = TighteningValueStatus.Low, + PrevailTorqueMonitoringStatus = TighteningValueStatus.High, + PrevailTorqueCompensateStatus = TighteningValueStatus.Ok, + TighteningErrorStatus = new TighteningErrorStatus() + { + RundownAngleMaxShutOff = false, + RundownAngleMinShutOff = true, + TorqueMaxShutOff = false, + AngleMaxShutOff = true, + SelftapTorqueMaxShutOff = false, + SelftapTorqueMinShutOff = true, + PrevailTorqueMaxShutOff = false, + PrevailTorqueMinShutOff = true, + PrevailTorqueCompensateOverflow = true, + CurrentMonitoringMaxShutOff = true, + PostViewTorqueMinTorqueShutOff = false, + PostViewTorqueMaxTorqueShutOff = false, + PostViewTorqueAngleTooSmall = false, + TriggerLost = true, + TorqueLessThanTarget = false, + ToolHot = true, + MultistageAbort = false, + Rehit = false, + DsMeasureFailed = false, + CurrentLimitReached = false, + EndTimeOutShutOff = false, + RemoveFastenerLimitExceeded = false, + DisableDrive = false, + TransducerLost = false, + TransducerShorted = false, + TransducerCorrupt = false, + SyncTimeout = false, + DynamicCurrentMonitoringMin = false, + DynamicCurrentMonitoringMax = false, + AngleMaxMonitor = false, + YieldNutOff = false, + YieldTooFewSamples = false + }, + RundownAngle = 250, + CurrentMonitoringValue = 2, + SelftapTorque = 2.15m, + PrevailTorque = 10.25m, + JobSequenceNumber = 12, + SyncTighteningId = 65535, + ToolSerialNumber = "ABCDEFG-123" + }); + } + + [TestMethod] + [TestCategory("Revision 3"), TestCategory("Pack")] + public void Mid0065PackRevision3() + { + string package = "02330065003 01012345678902AIRBAG 030001040020510060093807000008000009010011112013214115016217118000004189819001000200036021002502200223000215240010252500012266553527ABCDEFG-123 282019-01-05:09:00:052973008"; + + AssertBuildAndParse(package, new Mid0065(3) + { + TighteningId = 123456789L, + VinNumber = "AIRBAG", + ParameterSetId = 2, + BatchCounter = 0, + TighteningStatus = false, + TorqueStatus = TighteningValueStatus.Ok, + AngleStatus = TighteningValueStatus.Low, + Torque = 10m, + Angle = 360, + Timestamp = new DateTime(2019, 1, 5, 9, 0, 5), + BatchStatus = BatchStatus.Nok, + JobId = 1, + Strategy = Strategy.RotateSpindleForward, + StrategyOptions = new StrategyOptions() + { + Torque = false, + Angle = true, + Batch = false, + PvtMonitoring = true, + PvtCompensate = false, + Selftap = true, + Rundown = false, + CM = true, + DsControl = true, + ClickWrench = true, + RbwMonitoring = false + }, + BatchSize = 0, + RundownAngleStatus = TighteningValueStatus.High, + CurrentMonitoringStatus = TighteningValueStatus.Ok, + SelftapStatus = TighteningValueStatus.Low, + PrevailTorqueMonitoringStatus = TighteningValueStatus.High, + PrevailTorqueCompensateStatus = TighteningValueStatus.Ok, + TighteningErrorStatus = new TighteningErrorStatus() + { + RundownAngleMaxShutOff = false, + RundownAngleMinShutOff = true, + TorqueMaxShutOff = false, + AngleMaxShutOff = true, + SelftapTorqueMaxShutOff = false, + SelftapTorqueMinShutOff = true, + PrevailTorqueMaxShutOff = false, + PrevailTorqueMinShutOff = true, + PrevailTorqueCompensateOverflow = true, + CurrentMonitoringMaxShutOff = true, + PostViewTorqueMinTorqueShutOff = false, + PostViewTorqueMaxTorqueShutOff = false, + PostViewTorqueAngleTooSmall = false, + TriggerLost = true, + TorqueLessThanTarget = false, + ToolHot = true, + MultistageAbort = false, + Rehit = false, + DsMeasureFailed = false, + CurrentLimitReached = false, + EndTimeOutShutOff = false, + RemoveFastenerLimitExceeded = false, + DisableDrive = false, + TransducerLost = false, + TransducerShorted = false, + TransducerCorrupt = false, + SyncTimeout = false, + DynamicCurrentMonitoringMin = false, + DynamicCurrentMonitoringMax = false, + AngleMaxMonitor = false, + YieldNutOff = false, + YieldTooFewSamples = false + }, + RundownAngle = 250, + CurrentMonitoringValue = 2, + SelftapTorque = 2.15m, + PrevailTorque = 10.25m, + JobSequenceNumber = 12, + SyncTighteningId = 65535, + ToolSerialNumber = "ABCDEFG-123", + TorqueValuesUnit = TorqueValuesUnit.Percentage, + ResultType = ResultType.ReferenceSetup + }); + } + + [TestMethod] + [TestCategory("Revision 4"), TestCategory("Pack")] + public void Mid0065PackRevision4() + { + string package = "03140065004 01012345678902AIRBAG 030001040020510060093807000008000009010011112013214115016217118000004189819001000200036021002502200223000215240010252500012266553527ABCDEFG-123 282019-01-05:09:00:05297300831Identifier result part 2 32Identifier result part 3 33Identifier result part 4 "; + + AssertBuildAndParse(package, new Mid0065(4) + { + TighteningId = 123456789L, + VinNumber = "AIRBAG", + ParameterSetId = 2, + BatchCounter = 0, + TighteningStatus = false, + TorqueStatus = TighteningValueStatus.Ok, + AngleStatus = TighteningValueStatus.Low, + Torque = 10m, + Angle = 360, + Timestamp = new DateTime(2019, 1, 5, 9, 0, 5), + BatchStatus = BatchStatus.Nok, + JobId = 1, + Strategy = Strategy.RotateSpindleForward, + StrategyOptions = new StrategyOptions() + { + Torque = false, + Angle = true, + Batch = false, + PvtMonitoring = true, + PvtCompensate = false, + Selftap = true, + Rundown = false, + CM = true, + DsControl = true, + ClickWrench = true, + RbwMonitoring = false + }, + BatchSize = 0, + RundownAngleStatus = TighteningValueStatus.High, + CurrentMonitoringStatus = TighteningValueStatus.Ok, + SelftapStatus = TighteningValueStatus.Low, + PrevailTorqueMonitoringStatus = TighteningValueStatus.High, + PrevailTorqueCompensateStatus = TighteningValueStatus.Ok, + TighteningErrorStatus = new TighteningErrorStatus() + { + RundownAngleMaxShutOff = false, + RundownAngleMinShutOff = true, + TorqueMaxShutOff = false, + AngleMaxShutOff = true, + SelftapTorqueMaxShutOff = false, + SelftapTorqueMinShutOff = true, + PrevailTorqueMaxShutOff = false, + PrevailTorqueMinShutOff = true, + PrevailTorqueCompensateOverflow = true, + CurrentMonitoringMaxShutOff = true, + PostViewTorqueMinTorqueShutOff = false, + PostViewTorqueMaxTorqueShutOff = false, + PostViewTorqueAngleTooSmall = false, + TriggerLost = true, + TorqueLessThanTarget = false, + ToolHot = true, + MultistageAbort = false, + Rehit = false, + DsMeasureFailed = false, + CurrentLimitReached = false, + EndTimeOutShutOff = false, + RemoveFastenerLimitExceeded = false, + DisableDrive = false, + TransducerLost = false, + TransducerShorted = false, + TransducerCorrupt = false, + SyncTimeout = false, + DynamicCurrentMonitoringMin = false, + DynamicCurrentMonitoringMax = false, + AngleMaxMonitor = false, + YieldNutOff = false, + YieldTooFewSamples = false + }, + RundownAngle = 250, + CurrentMonitoringValue = 2, + SelftapTorque = 2.15m, + PrevailTorque = 10.25m, + JobSequenceNumber = 12, + SyncTighteningId = 65535, + ToolSerialNumber = "ABCDEFG-123", + TorqueValuesUnit = TorqueValuesUnit.Percentage, + ResultType = ResultType.ReferenceSetup, + IdentifierResultPart2 = "Identifier result part 2", + IdentifierResultPart3 = "Identifier result part 3", + IdentifierResultPart4 = "Identifier result part 4" + }); + } + + [TestMethod] + [TestCategory("Revision 5"), TestCategory("Pack")] + public void Mid0065PackRevision5() + { + string package = "03200065005 01012345678902AIRBAG 030001040020510060093807000008000009010011112013214115016217118000004189819001000200036021002502200223000215240010252500012266553527ABCDEFG-123 282019-01-05:09:00:05297300831Identifier result part 2 32Identifier result part 3 33Identifier result part 4 34E124"; + + AssertBuildAndParse(package, new Mid0065(5) + { + TighteningId = 123456789L, + VinNumber = "AIRBAG", + ParameterSetId = 2, + BatchCounter = 0, + TighteningStatus = false, + TorqueStatus = TighteningValueStatus.Ok, + AngleStatus = TighteningValueStatus.Low, + Torque = 10m, + Angle = 360, + Timestamp = new DateTime(2019, 1, 5, 9, 0, 5), + BatchStatus = BatchStatus.Nok, + JobId = 1, + Strategy = Strategy.RotateSpindleForward, + StrategyOptions = new StrategyOptions() + { + Torque = false, + Angle = true, + Batch = false, + PvtMonitoring = true, + PvtCompensate = false, + Selftap = true, + Rundown = false, + CM = true, + DsControl = true, + ClickWrench = true, + RbwMonitoring = false + }, + BatchSize = 0, + RundownAngleStatus = TighteningValueStatus.High, + CurrentMonitoringStatus = TighteningValueStatus.Ok, + SelftapStatus = TighteningValueStatus.Low, + PrevailTorqueMonitoringStatus = TighteningValueStatus.High, + PrevailTorqueCompensateStatus = TighteningValueStatus.Ok, + TighteningErrorStatus = new TighteningErrorStatus() + { + RundownAngleMaxShutOff = false, + RundownAngleMinShutOff = true, + TorqueMaxShutOff = false, + AngleMaxShutOff = true, + SelftapTorqueMaxShutOff = false, + SelftapTorqueMinShutOff = true, + PrevailTorqueMaxShutOff = false, + PrevailTorqueMinShutOff = true, + PrevailTorqueCompensateOverflow = true, + CurrentMonitoringMaxShutOff = true, + PostViewTorqueMinTorqueShutOff = false, + PostViewTorqueMaxTorqueShutOff = false, + PostViewTorqueAngleTooSmall = false, + TriggerLost = true, + TorqueLessThanTarget = false, + ToolHot = true, + MultistageAbort = false, + Rehit = false, + DsMeasureFailed = false, + CurrentLimitReached = false, + EndTimeOutShutOff = false, + RemoveFastenerLimitExceeded = false, + DisableDrive = false, + TransducerLost = false, + TransducerShorted = false, + TransducerCorrupt = false, + SyncTimeout = false, + DynamicCurrentMonitoringMin = false, + DynamicCurrentMonitoringMax = false, + AngleMaxMonitor = false, + YieldNutOff = false, + YieldTooFewSamples = false + }, + RundownAngle = 250, + CurrentMonitoringValue = 2, + SelftapTorque = 2.15m, + PrevailTorque = 10.25m, + JobSequenceNumber = 12, + SyncTighteningId = 65535, + ToolSerialNumber = "ABCDEFG-123", + TorqueValuesUnit = TorqueValuesUnit.Percentage, + ResultType = ResultType.ReferenceSetup, + IdentifierResultPart2 = "Identifier result part 2", + IdentifierResultPart3 = "Identifier result part 3", + IdentifierResultPart4 = "Identifier result part 4", + CustomerTighteningErrorCode = "E124" + }); + } + + [TestMethod] + [TestCategory("Revision 6"), TestCategory("Pack")] + public void Mid0065PackRevision6() + { + string package = "03400065006 01012345678902AIRBAG 030001040020510060093807000008000009010011112013214115016217118000004189819001000200036021002502200223000215240010252500012266553527ABCDEFG-123 282019-01-05:09:00:05297300831Identifier result part 2 32Identifier result part 3 33Identifier result part 4 34E12435005100360000000042"; + + AssertBuildAndParse(package, new Mid0065(6) + { + TighteningId = 123456789L, + VinNumber = "AIRBAG", + ParameterSetId = 2, + BatchCounter = 0, + TighteningStatus = false, + TorqueStatus = TighteningValueStatus.Ok, + AngleStatus = TighteningValueStatus.Low, + Torque = 10m, + Angle = 360, + Timestamp = new DateTime(2019, 1, 5, 9, 0, 5), + BatchStatus = BatchStatus.Nok, + JobId = 1, + Strategy = Strategy.RotateSpindleForward, + StrategyOptions = new StrategyOptions() + { + Torque = false, + Angle = true, + Batch = false, + PvtMonitoring = true, + PvtCompensate = false, + Selftap = true, + Rundown = false, + CM = true, + DsControl = true, + ClickWrench = true, + RbwMonitoring = false + }, + BatchSize = 0, + RundownAngleStatus = TighteningValueStatus.High, + CurrentMonitoringStatus = TighteningValueStatus.Ok, + SelftapStatus = TighteningValueStatus.Low, + PrevailTorqueMonitoringStatus = TighteningValueStatus.High, + PrevailTorqueCompensateStatus = TighteningValueStatus.Ok, + TighteningErrorStatus = new TighteningErrorStatus() + { + RundownAngleMaxShutOff = false, + RundownAngleMinShutOff = true, + TorqueMaxShutOff = false, + AngleMaxShutOff = true, + SelftapTorqueMaxShutOff = false, + SelftapTorqueMinShutOff = true, + PrevailTorqueMaxShutOff = false, + PrevailTorqueMinShutOff = true, + PrevailTorqueCompensateOverflow = true, + CurrentMonitoringMaxShutOff = true, + PostViewTorqueMinTorqueShutOff = false, + PostViewTorqueMaxTorqueShutOff = false, + PostViewTorqueAngleTooSmall = false, + TriggerLost = true, + TorqueLessThanTarget = false, + ToolHot = true, + MultistageAbort = false, + Rehit = false, + DsMeasureFailed = false, + CurrentLimitReached = false, + EndTimeOutShutOff = false, + RemoveFastenerLimitExceeded = false, + DisableDrive = false, + TransducerLost = false, + TransducerShorted = false, + TransducerCorrupt = false, + SyncTimeout = false, + DynamicCurrentMonitoringMin = false, + DynamicCurrentMonitoringMax = false, + AngleMaxMonitor = false, + YieldNutOff = false, + YieldTooFewSamples = false + }, + RundownAngle = 250, + CurrentMonitoringValue = 2, + SelftapTorque = 2.15m, + PrevailTorque = 10.25m, + JobSequenceNumber = 12, + SyncTighteningId = 65535, + ToolSerialNumber = "ABCDEFG-123", + TorqueValuesUnit = TorqueValuesUnit.Percentage, + ResultType = ResultType.ReferenceSetup, + IdentifierResultPart2 = "Identifier result part 2", + IdentifierResultPart3 = "Identifier result part 3", + IdentifierResultPart4 = "Identifier result part 4", + CustomerTighteningErrorCode = "E124", + PrevailTorqueCompensateValue = 51m, + TighteningErrorStatus2 = new TighteningErrorStatus2() + { + DriveDeactivated = false, + ToolStall = true, + DriveHot = false, + GradientMonitoringHigh = true, + GradientMonitoringLow = false, + ReactionBarFailed = true, + SnugMax = false, + CycleAbort = false, + NeckingFailure = false, + EffectiveLoosening = false, + OverSpeed = false, + NoResidualTorque = false, + PositioningFail = false, + SnugMonLow = false, + SnugMonHigh = false, + DynamicMinCurrent = false, + DynamicMaxCurrent = false, + LatentResult = false, + Reserved = new byte[] + { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + } + } + }); + } + + [TestMethod] + [TestCategory("Revision 7"), TestCategory("Pack")] + public void Mid0065PackRevision7() + { + string package = "03790065007 01012345678902AIRBAG 030001040020510060093807000008000009010011112013214115016217118000004189819001000200036021002502200223000215240010252500012266553527ABCDEFG-123 282019-01-05:09:00:05297300831Identifier result part 2 32Identifier result part 3 33Identifier result part 4 34E1243500510036000000004237429496729538Station Name "; + + AssertBuildAndParse(package, new Mid0065(7) + { + TighteningId = 123456789L, + VinNumber = "AIRBAG", + ParameterSetId = 2, + BatchCounter = 0, + TighteningStatus = false, + TorqueStatus = TighteningValueStatus.Ok, + AngleStatus = TighteningValueStatus.Low, + Torque = 10m, + Angle = 360, + Timestamp = new DateTime(2019, 1, 5, 9, 0, 5), + BatchStatus = BatchStatus.Nok, + JobId = 1, + Strategy = Strategy.RotateSpindleForward, + StrategyOptions = new StrategyOptions() + { + Torque = false, + Angle = true, + Batch = false, + PvtMonitoring = true, + PvtCompensate = false, + Selftap = true, + Rundown = false, + CM = true, + DsControl = true, + ClickWrench = true, + RbwMonitoring = false + }, + BatchSize = 0, + RundownAngleStatus = TighteningValueStatus.High, + CurrentMonitoringStatus = TighteningValueStatus.Ok, + SelftapStatus = TighteningValueStatus.Low, + PrevailTorqueMonitoringStatus = TighteningValueStatus.High, + PrevailTorqueCompensateStatus = TighteningValueStatus.Ok, + TighteningErrorStatus = new TighteningErrorStatus() + { + RundownAngleMaxShutOff = false, + RundownAngleMinShutOff = true, + TorqueMaxShutOff = false, + AngleMaxShutOff = true, + SelftapTorqueMaxShutOff = false, + SelftapTorqueMinShutOff = true, + PrevailTorqueMaxShutOff = false, + PrevailTorqueMinShutOff = true, + PrevailTorqueCompensateOverflow = true, + CurrentMonitoringMaxShutOff = true, + PostViewTorqueMinTorqueShutOff = false, + PostViewTorqueMaxTorqueShutOff = false, + PostViewTorqueAngleTooSmall = false, + TriggerLost = true, + TorqueLessThanTarget = false, + ToolHot = true, + MultistageAbort = false, + Rehit = false, + DsMeasureFailed = false, + CurrentLimitReached = false, + EndTimeOutShutOff = false, + RemoveFastenerLimitExceeded = false, + DisableDrive = false, + TransducerLost = false, + TransducerShorted = false, + TransducerCorrupt = false, + SyncTimeout = false, + DynamicCurrentMonitoringMin = false, + DynamicCurrentMonitoringMax = false, + AngleMaxMonitor = false, + YieldNutOff = false, + YieldTooFewSamples = false + }, + RundownAngle = 250, + CurrentMonitoringValue = 2, + SelftapTorque = 2.15m, + PrevailTorque = 10.25m, + JobSequenceNumber = 12, + SyncTighteningId = 65535, + ToolSerialNumber = "ABCDEFG-123", + TorqueValuesUnit = TorqueValuesUnit.Percentage, + ResultType = ResultType.ReferenceSetup, + IdentifierResultPart2 = "Identifier result part 2", + IdentifierResultPart3 = "Identifier result part 3", + IdentifierResultPart4 = "Identifier result part 4", + CustomerTighteningErrorCode = "E124", + PrevailTorqueCompensateValue = 51m, + TighteningErrorStatus2 = new TighteningErrorStatus2() + { + DriveDeactivated = false, + ToolStall = true, + DriveHot = false, + GradientMonitoringHigh = true, + GradientMonitoringLow = false, + ReactionBarFailed = true, + SnugMax = false, + CycleAbort = false, + NeckingFailure = false, + EffectiveLoosening = false, + OverSpeed = false, + NoResidualTorque = false, + PositioningFail = false, + SnugMonLow = false, + SnugMonHigh = false, + DynamicMinCurrent = false, + DynamicMaxCurrent = false, + LatentResult = false, + Reserved = new byte[] + { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + } + }, + StationId = 4294967295L, + StationName = "Station Name" + }); + } + + [TestMethod] + [TestCategory("Revision 8"), TestCategory("Pack")] + public void Mid0065PackRevision8() + { + string package = "04060065008 01012345678902AIRBAG 030001040020510060093807000008000009010011112013214115016217118000004189819001000200036021002502200223000215240010252500012266553527ABCDEFG-123 282019-01-05:09:00:05297300831Identifier result part 2 32Identifier result part 3 33Identifier result part 4 34E1243500510036000000004237429496729538Station Name 390010004024165214342005232"; + + AssertBuildAndParse(package, new Mid0065(8) + { + TighteningId = 123456789L, + VinNumber = "AIRBAG", + ParameterSetId = 2, + BatchCounter = 0, + TighteningStatus = false, + TorqueStatus = TighteningValueStatus.Ok, + AngleStatus = TighteningValueStatus.Low, + Torque = 10m, + Angle = 360, + Timestamp = new DateTime(2019, 1, 5, 9, 0, 5), + BatchStatus = BatchStatus.Nok, + JobId = 1, + Strategy = Strategy.RotateSpindleForward, + StrategyOptions = new StrategyOptions() + { + Torque = false, + Angle = true, + Batch = false, + PvtMonitoring = true, + PvtCompensate = false, + Selftap = true, + Rundown = false, + CM = true, + DsControl = true, + ClickWrench = true, + RbwMonitoring = false + }, + BatchSize = 0, + RundownAngleStatus = TighteningValueStatus.High, + CurrentMonitoringStatus = TighteningValueStatus.Ok, + SelftapStatus = TighteningValueStatus.Low, + PrevailTorqueMonitoringStatus = TighteningValueStatus.High, + PrevailTorqueCompensateStatus = TighteningValueStatus.Ok, + TighteningErrorStatus = new TighteningErrorStatus() + { + RundownAngleMaxShutOff = false, + RundownAngleMinShutOff = true, + TorqueMaxShutOff = false, + AngleMaxShutOff = true, + SelftapTorqueMaxShutOff = false, + SelftapTorqueMinShutOff = true, + PrevailTorqueMaxShutOff = false, + PrevailTorqueMinShutOff = true, + PrevailTorqueCompensateOverflow = true, + CurrentMonitoringMaxShutOff = true, + PostViewTorqueMinTorqueShutOff = false, + PostViewTorqueMaxTorqueShutOff = false, + PostViewTorqueAngleTooSmall = false, + TriggerLost = true, + TorqueLessThanTarget = false, + ToolHot = true, + MultistageAbort = false, + Rehit = false, + DsMeasureFailed = false, + CurrentLimitReached = false, + EndTimeOutShutOff = false, + RemoveFastenerLimitExceeded = false, + DisableDrive = false, + TransducerLost = false, + TransducerShorted = false, + TransducerCorrupt = false, + SyncTimeout = false, + DynamicCurrentMonitoringMin = false, + DynamicCurrentMonitoringMax = false, + AngleMaxMonitor = false, + YieldNutOff = false, + YieldTooFewSamples = false + }, + RundownAngle = 250, + CurrentMonitoringValue = 2, + SelftapTorque = 2.15m, + PrevailTorque = 10.25m, + JobSequenceNumber = 12, + SyncTighteningId = 65535, + ToolSerialNumber = "ABCDEFG-123", + TorqueValuesUnit = TorqueValuesUnit.Percentage, + ResultType = ResultType.ReferenceSetup, + IdentifierResultPart2 = "Identifier result part 2", + IdentifierResultPart3 = "Identifier result part 3", + IdentifierResultPart4 = "Identifier result part 4", + CustomerTighteningErrorCode = "E124", + PrevailTorqueCompensateValue = 51m, + TighteningErrorStatus2 = new TighteningErrorStatus2() + { + DriveDeactivated = false, + ToolStall = true, + DriveHot = false, + GradientMonitoringHigh = true, + GradientMonitoringLow = false, + ReactionBarFailed = true, + SnugMax = false, + CycleAbort = false, + NeckingFailure = false, + EffectiveLoosening = false, + OverSpeed = false, + NoResidualTorque = false, + PositioningFail = false, + SnugMonLow = false, + SnugMonHigh = false, + DynamicMinCurrent = false, + DynamicMaxCurrent = false, + LatentResult = false, + Reserved = new byte[] + { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + } + }, + StationId = 4294967295L, + StationName = "Station Name", + StartFinalAngle = 10m, + PostViewTorqueActivated = PostViewTorque.OnlyPVTHOn, + PostViewTorqueHigh = 6521.43m, + PostViewTorqueLow = 52.32m + }); + } + + [TestMethod] + [TestCategory("Revision 9"), TestCategory("Pack")] + public void Mid0065PackRevision9() + { + string package = "04270065009 01012345678902AIRBAG 030001040020510060093807000008000009010011112013214115016217118000004189819001000200036021002502200223000215240010252500012266553527ABCDEFG-123 282019-01-05:09:00:05297300831Identifier result part 2 32Identifier result part 3 33Identifier result part 4 34E1243500510036000000004237429496729538Station Name 390010004024165214342005232430010044000504500150"; + + AssertBuildAndParse(package, new Mid0065(9) + { + TighteningId = 123456789L, + VinNumber = "AIRBAG", + ParameterSetId = 2, + BatchCounter = 0, + TighteningStatus = false, + TorqueStatus = TighteningValueStatus.Ok, + AngleStatus = TighteningValueStatus.Low, + Torque = 10m, + Angle = 360, + Timestamp = new DateTime(2019, 1, 5, 9, 0, 5), + BatchStatus = BatchStatus.Nok, + JobId = 1, + Strategy = Strategy.RotateSpindleForward, + StrategyOptions = new StrategyOptions() + { + Torque = false, + Angle = true, + Batch = false, + PvtMonitoring = true, + PvtCompensate = false, + Selftap = true, + Rundown = false, + CM = true, + DsControl = true, + ClickWrench = true, + RbwMonitoring = false + }, + BatchSize = 0, + RundownAngleStatus = TighteningValueStatus.High, + CurrentMonitoringStatus = TighteningValueStatus.Ok, + SelftapStatus = TighteningValueStatus.Low, + PrevailTorqueMonitoringStatus = TighteningValueStatus.High, + PrevailTorqueCompensateStatus = TighteningValueStatus.Ok, + TighteningErrorStatus = new TighteningErrorStatus() + { + RundownAngleMaxShutOff = false, + RundownAngleMinShutOff = true, + TorqueMaxShutOff = false, + AngleMaxShutOff = true, + SelftapTorqueMaxShutOff = false, + SelftapTorqueMinShutOff = true, + PrevailTorqueMaxShutOff = false, + PrevailTorqueMinShutOff = true, + PrevailTorqueCompensateOverflow = true, + CurrentMonitoringMaxShutOff = true, + PostViewTorqueMinTorqueShutOff = false, + PostViewTorqueMaxTorqueShutOff = false, + PostViewTorqueAngleTooSmall = false, + TriggerLost = true, + TorqueLessThanTarget = false, + ToolHot = true, + MultistageAbort = false, + Rehit = false, + DsMeasureFailed = false, + CurrentLimitReached = false, + EndTimeOutShutOff = false, + RemoveFastenerLimitExceeded = false, + DisableDrive = false, + TransducerLost = false, + TransducerShorted = false, + TransducerCorrupt = false, + SyncTimeout = false, + DynamicCurrentMonitoringMin = false, + DynamicCurrentMonitoringMax = false, + AngleMaxMonitor = false, + YieldNutOff = false, + YieldTooFewSamples = false + }, + RundownAngle = 250, + CurrentMonitoringValue = 2, + SelftapTorque = 2.15m, + PrevailTorque = 10.25m, + JobSequenceNumber = 12, + SyncTighteningId = 65535, + ToolSerialNumber = "ABCDEFG-123", + TorqueValuesUnit = TorqueValuesUnit.Percentage, + ResultType = ResultType.ReferenceSetup, + IdentifierResultPart2 = "Identifier result part 2", + IdentifierResultPart3 = "Identifier result part 3", + IdentifierResultPart4 = "Identifier result part 4", + CustomerTighteningErrorCode = "E124", + PrevailTorqueCompensateValue = 51m, + TighteningErrorStatus2 = new TighteningErrorStatus2() + { + DriveDeactivated = false, + ToolStall = true, + DriveHot = false, + GradientMonitoringHigh = true, + GradientMonitoringLow = false, + ReactionBarFailed = true, + SnugMax = false, + CycleAbort = false, + NeckingFailure = false, + EffectiveLoosening = false, + OverSpeed = false, + NoResidualTorque = false, + PositioningFail = false, + SnugMonLow = false, + SnugMonHigh = false, + DynamicMinCurrent = false, + DynamicMaxCurrent = false, + LatentResult = false, + Reserved = new byte[] + { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + } + }, + StationId = 4294967295L, + StationName = "Station Name", + StartFinalAngle = 10m, + PostViewTorqueActivated = PostViewTorque.OnlyPVTHOn, + PostViewTorqueHigh = 6521.43m, + PostViewTorqueLow = 52.32m, + CurrentMonitoringAmpere = 1m, + CurrentMonitoringAmpereMin = 0.5m, + CurrentMonitoringAmpereMax = 1.5m + }); + } + + [TestMethod] + [TestCategory("Revision 10"), TestCategory("Pack")] + public void Mid0065PackRevision10() + { + string package = "04970065010 01012345678902AIRBAG 030001040020510060093807000008000009010011112013214115016217118000004189819001000200036021002502200223000215240010252500012266553527ABCDEFG-123 282019-01-05:09:00:05297300831Identifier result part 2 32Identifier result part 3 33Identifier result part 4 34E1243500510036000000004237429496729538Station Name 3900100040241652143420052324300100440005045001504600001470000148149-00205000100510012052003291530010055400011055009000"; + + AssertBuildAndParse(package, new Mid0065(10) + { + TighteningId = 123456789L, + VinNumber = "AIRBAG", + ParameterSetId = 2, + BatchCounter = 0, + TighteningStatus = false, + TorqueStatus = TighteningValueStatus.Ok, + AngleStatus = TighteningValueStatus.Low, + Torque = 10m, + Angle = 360, + Timestamp = new DateTime(2019, 1, 5, 9, 0, 5), + BatchStatus = BatchStatus.Nok, + JobId = 1, + Strategy = Strategy.RotateSpindleForward, + StrategyOptions = new StrategyOptions() + { + Torque = false, + Angle = true, + Batch = false, + PvtMonitoring = true, + PvtCompensate = false, + Selftap = true, + Rundown = false, + CM = true, + DsControl = true, + ClickWrench = true, + RbwMonitoring = false + }, + BatchSize = 0, + RundownAngleStatus = TighteningValueStatus.High, + CurrentMonitoringStatus = TighteningValueStatus.Ok, + SelftapStatus = TighteningValueStatus.Low, + PrevailTorqueMonitoringStatus = TighteningValueStatus.High, + PrevailTorqueCompensateStatus = TighteningValueStatus.Ok, + TighteningErrorStatus = new TighteningErrorStatus() + { + RundownAngleMaxShutOff = false, + RundownAngleMinShutOff = true, + TorqueMaxShutOff = false, + AngleMaxShutOff = true, + SelftapTorqueMaxShutOff = false, + SelftapTorqueMinShutOff = true, + PrevailTorqueMaxShutOff = false, + PrevailTorqueMinShutOff = true, + PrevailTorqueCompensateOverflow = true, + CurrentMonitoringMaxShutOff = true, + PostViewTorqueMinTorqueShutOff = false, + PostViewTorqueMaxTorqueShutOff = false, + PostViewTorqueAngleTooSmall = false, + TriggerLost = true, + TorqueLessThanTarget = false, + ToolHot = true, + MultistageAbort = false, + Rehit = false, + DsMeasureFailed = false, + CurrentLimitReached = false, + EndTimeOutShutOff = false, + RemoveFastenerLimitExceeded = false, + DisableDrive = false, + TransducerLost = false, + TransducerShorted = false, + TransducerCorrupt = false, + SyncTimeout = false, + DynamicCurrentMonitoringMin = false, + DynamicCurrentMonitoringMax = false, + AngleMaxMonitor = false, + YieldNutOff = false, + YieldTooFewSamples = false + }, + RundownAngle = 250, + CurrentMonitoringValue = 2, + SelftapTorque = 2.15m, + PrevailTorque = 10.25m, + JobSequenceNumber = 12, + SyncTighteningId = 65535, + ToolSerialNumber = "ABCDEFG-123", + TorqueValuesUnit = TorqueValuesUnit.Percentage, + ResultType = ResultType.ReferenceSetup, + IdentifierResultPart2 = "Identifier result part 2", + IdentifierResultPart3 = "Identifier result part 3", + IdentifierResultPart4 = "Identifier result part 4", + CustomerTighteningErrorCode = "E124", + PrevailTorqueCompensateValue = 51m, + TighteningErrorStatus2 = new TighteningErrorStatus2() + { + DriveDeactivated = false, + ToolStall = true, + DriveHot = false, + GradientMonitoringHigh = true, + GradientMonitoringLow = false, + ReactionBarFailed = true, + SnugMax = false, + CycleAbort = false, + NeckingFailure = false, + EffectiveLoosening = false, + OverSpeed = false, + NoResidualTorque = false, + PositioningFail = false, + SnugMonLow = false, + SnugMonHigh = false, + DynamicMinCurrent = false, + DynamicMaxCurrent = false, + LatentResult = false, + Reserved = new byte[] + { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + } + }, + StationId = 4294967295L, + StationName = "Station Name", + StartFinalAngle = 10m, + PostViewTorqueActivated = PostViewTorque.OnlyPVTHOn, + PostViewTorqueHigh = 6521.43m, + PostViewTorqueLow = 52.32m, + CurrentMonitoringAmpere = 1m, + CurrentMonitoringAmpereMin = 0.5m, + CurrentMonitoringAmpereMax = 1.5m, + AngleNumeratorScaleFactor = 1, + AngleDenominatorScaleFactor = 1, + OverallAngleStatus = TighteningValueStatus.Ok, + OverallAngleMin = -20, + OverallAngleMax = 100, + OverallAngle = 120, + PeakTorque = 32.91m, + ResidualBreakawayTorque = 10.05m, + StartRundownAngle = 1.1m, + RundownAngleComplete = 90m + }); + } + + [TestMethod] + [TestCategory("Revision 11"), TestCategory("Pack")] + public void Mid0065PackRevision11() + { + string package = "05120065011 01012345678902AIRBAG 030001040020510060093807000008000009010011112013214115016217118000004189819001000200036021002502200223000215240010252500012266553527ABCDEFG-123 282019-01-05:09:00:05297300831Identifier result part 2 32Identifier result part 3 33Identifier result part 4 34E1243500510036000000004237429496729538Station Name 3900100040241652143420052324300100440005045001504600001470000148149-00205000100510012052003291530010055400011055009000560001205700001"; + + AssertBuildAndParse(package, new Mid0065(11) + { + TighteningId = 123456789L, + VinNumber = "AIRBAG", + ParameterSetId = 2, + BatchCounter = 0, + TighteningStatus = false, + TorqueStatus = TighteningValueStatus.Ok, + AngleStatus = TighteningValueStatus.Low, + Torque = 10m, + Angle = 360, + Timestamp = new DateTime(2019, 1, 5, 9, 0, 5), + BatchStatus = BatchStatus.Nok, + JobId = 1, + Strategy = Strategy.RotateSpindleForward, + StrategyOptions = new StrategyOptions() + { + Torque = false, + Angle = true, + Batch = false, + PvtMonitoring = true, + PvtCompensate = false, + Selftap = true, + Rundown = false, + CM = true, + DsControl = true, + ClickWrench = true, + RbwMonitoring = false + }, + BatchSize = 0, + RundownAngleStatus = TighteningValueStatus.High, + CurrentMonitoringStatus = TighteningValueStatus.Ok, + SelftapStatus = TighteningValueStatus.Low, + PrevailTorqueMonitoringStatus = TighteningValueStatus.High, + PrevailTorqueCompensateStatus = TighteningValueStatus.Ok, + TighteningErrorStatus = new TighteningErrorStatus() + { + RundownAngleMaxShutOff = false, + RundownAngleMinShutOff = true, + TorqueMaxShutOff = false, + AngleMaxShutOff = true, + SelftapTorqueMaxShutOff = false, + SelftapTorqueMinShutOff = true, + PrevailTorqueMaxShutOff = false, + PrevailTorqueMinShutOff = true, + PrevailTorqueCompensateOverflow = true, + CurrentMonitoringMaxShutOff = true, + PostViewTorqueMinTorqueShutOff = false, + PostViewTorqueMaxTorqueShutOff = false, + PostViewTorqueAngleTooSmall = false, + TriggerLost = true, + TorqueLessThanTarget = false, + ToolHot = true, + MultistageAbort = false, + Rehit = false, + DsMeasureFailed = false, + CurrentLimitReached = false, + EndTimeOutShutOff = false, + RemoveFastenerLimitExceeded = false, + DisableDrive = false, + TransducerLost = false, + TransducerShorted = false, + TransducerCorrupt = false, + SyncTimeout = false, + DynamicCurrentMonitoringMin = false, + DynamicCurrentMonitoringMax = false, + AngleMaxMonitor = false, + YieldNutOff = false, + YieldTooFewSamples = false + }, + RundownAngle = 250, + CurrentMonitoringValue = 2, + SelftapTorque = 2.15m, + PrevailTorque = 10.25m, + JobSequenceNumber = 12, + SyncTighteningId = 65535, + ToolSerialNumber = "ABCDEFG-123", + TorqueValuesUnit = TorqueValuesUnit.Percentage, + ResultType = ResultType.ReferenceSetup, + IdentifierResultPart2 = "Identifier result part 2", + IdentifierResultPart3 = "Identifier result part 3", + IdentifierResultPart4 = "Identifier result part 4", + CustomerTighteningErrorCode = "E124", + PrevailTorqueCompensateValue = 51m, + TighteningErrorStatus2 = new TighteningErrorStatus2() + { + DriveDeactivated = false, + ToolStall = true, + DriveHot = false, + GradientMonitoringHigh = true, + GradientMonitoringLow = false, + ReactionBarFailed = true, + SnugMax = false, + CycleAbort = false, + NeckingFailure = false, + EffectiveLoosening = false, + OverSpeed = false, + NoResidualTorque = false, + PositioningFail = false, + SnugMonLow = false, + SnugMonHigh = false, + DynamicMinCurrent = false, + DynamicMaxCurrent = false, + LatentResult = false, + Reserved = new byte[] + { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + } + }, + StationId = 4294967295L, + StationName = "Station Name", + StartFinalAngle = 10m, + PostViewTorqueActivated = PostViewTorque.OnlyPVTHOn, + PostViewTorqueHigh = 6521.43m, + PostViewTorqueLow = 52.32m, + CurrentMonitoringAmpere = 1m, + CurrentMonitoringAmpereMin = 0.5m, + CurrentMonitoringAmpereMax = 1.5m, + AngleNumeratorScaleFactor = 1, + AngleDenominatorScaleFactor = 1, + OverallAngleStatus = TighteningValueStatus.Ok, + OverallAngleMin = -20, + OverallAngleMax = 100, + OverallAngle = 120, + PeakTorque = 32.91m, + ResidualBreakawayTorque = 10.05m, + StartRundownAngle = 1.1m, + RundownAngleComplete = 90m, + ClickTorque = 1.2m, + ClickAngle = 1 + }); + } } } diff --git a/src/MIDTesters.Core/Tightening/TestMid0066.cs b/src/MIDTesters.Core/Tightening/TestMid0066.cs index 02eebb88..b6970a94 100644 --- a/src/MIDTesters.Core/Tightening/TestMid0066.cs +++ b/src/MIDTesters.Core/Tightening/TestMid0066.cs @@ -1,6 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter.Tightening; +using OpenProtocolInterpreter; namespace MIDTesters.Tightening { [TestClass] @@ -58,5 +59,30 @@ public void Mid0066ByteRevision2() Assert.AreEqual(11, mid.NumberOfOfflineCurves); AssertEqualPackages(package, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0066PackRevision1() + { + string package = "00240066001 0114"; + + AssertBuildAndParse(package, new Mid0066() + { + NumberOfOfflineResults = 14 + }); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0066PackRevision2() + { + string package = "00300066002 0101402011"; + + AssertBuildAndParse(package, new Mid0066(new Header() { Mid = Mid0066.MID, Revision = 2 }) + { + NumberOfOfflineResults = 14, + NumberOfOfflineCurves = 11 + }); + } } } diff --git a/src/MIDTesters.Core/Tightening/TestMid0902.cs b/src/MIDTesters.Core/Tightening/TestMid0902.cs index 1f792e1a..1db9ac01 100644 --- a/src/MIDTesters.Core/Tightening/TestMid0902.cs +++ b/src/MIDTesters.Core/Tightening/TestMid0902.cs @@ -2,6 +2,8 @@ using OpenProtocolInterpreter.Tightening; using System; +using System.Collections.Generic; +using OpenProtocolInterpreter; namespace MIDTesters.Tightening { [TestClass] @@ -46,5 +48,43 @@ public void Mid0902ByteRevision1() Assert.AreEqual(2, mid.VariableDataFields.Count); AssertEqualPackages(package, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0902PackRevision1() + { + string package = "01450902001 000000213400000000012022-12-21:13:02:3300000058192023-12-10:12:28:54002000050010600000001000100190500000002023-11-09:18:48:14"; + + AssertBuildAndParse(package, new Mid0902() + { + Capacity = 2134L, + OldestSequenceNumber = 1L, + OldestTime = new DateTime(2022, 12, 21, 13, 2, 33), + NewestSequenceNumber = 5819L, + NewestTime = new DateTime(2023, 12, 10, 12, 28, 54), + NumberOfPIDs = 2, + VariableDataFields = new List() + { + new VariableDataField() + { + ParameterId = 5, + Length = 1, + DataType = DataTypeDefinition.Boolean, + Unit = DataUnitType.NoUnit, + StepNumber = 0, + DataValue = "1" + }, + new VariableDataField() + { + ParameterId = 10, + Length = 19, + DataType = DataTypeDefinition.Timestamp, + Unit = DataUnitType.NoUnit, + StepNumber = 0, + DataValue = "2023-11-09:18:48:14" + } + } + }); + } } } diff --git a/src/MIDTesters.Core/Time/TestMid0080.cs b/src/MIDTesters.Core/Time/TestMid0080.cs index 16cf6cf2..28c98094 100644 --- a/src/MIDTesters.Core/Time/TestMid0080.cs +++ b/src/MIDTesters.Core/Time/TestMid0080.cs @@ -29,5 +29,14 @@ public void Mid0080ByteAllRevisions() Assert.AreEqual(typeof(Mid0080), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Pack")] + public void Mid0080PackAllRevisions() + { + string package = "00200080 "; + + AssertBuildAndParse(package, new Mid0080(), true); + } } } diff --git a/src/MIDTesters.Core/Time/TestMid0081.cs b/src/MIDTesters.Core/Time/TestMid0081.cs index 157358f1..2709143e 100644 --- a/src/MIDTesters.Core/Time/TestMid0081.cs +++ b/src/MIDTesters.Core/Time/TestMid0081.cs @@ -30,5 +30,17 @@ public void Mid0081ByteRevision1() Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.Time); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0081PackRevision1() + { + string package = "00390081 2017-12-01:20:12:45"; + + AssertBuildAndParse(package, new Mid0081() + { + Time = new DateTime(2017, 12, 1, 20, 12, 45) + }, true); + } } } diff --git a/src/MIDTesters.Core/Time/TestMid0082.cs b/src/MIDTesters.Core/Time/TestMid0082.cs index 074b65c6..3b50b4cf 100644 --- a/src/MIDTesters.Core/Time/TestMid0082.cs +++ b/src/MIDTesters.Core/Time/TestMid0082.cs @@ -30,5 +30,17 @@ public void Mid0082ByteRevision1() Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.Time); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0082PackRevision1() + { + string package = "00390082 2017-12-01:20:12:45"; + + AssertBuildAndParse(package, new Mid0082() + { + Time = new DateTime(2017, 12, 1, 20, 12, 45) + }, true); + } } } diff --git a/src/MIDTesters.Core/Tool/TestMid0040.cs b/src/MIDTesters.Core/Tool/TestMid0040.cs index 647d76e8..8d1b718b 100644 --- a/src/MIDTesters.Core/Tool/TestMid0040.cs +++ b/src/MIDTesters.Core/Tool/TestMid0040.cs @@ -52,5 +52,28 @@ public void Mid0040ByteRevisions6And7() Assert.AreEqual(1, mid.ToolNumber); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 4"), TestCategory("Pack")] + public void Mid0040PackRevisions1To5() + { + string package = "00200040004 "; + + AssertBuildAndParse(package, new Mid0040(4) + { + }); + } + + [TestMethod] + [TestCategory("Revision 7"), TestCategory("Pack")] + public void Mid0040PackRevisions6And7() + { + string package = "00260040007 010001"; + + AssertBuildAndParse(package, new Mid0040(7) + { + ToolNumber = 1 + }); + } } } diff --git a/src/MIDTesters.Core/Tool/TestMid0041.cs b/src/MIDTesters.Core/Tool/TestMid0041.cs index 66e4722a..76257f6d 100644 --- a/src/MIDTesters.Core/Tool/TestMid0041.cs +++ b/src/MIDTesters.Core/Tool/TestMid0041.cs @@ -377,5 +377,211 @@ public void Mid0041ByteRevision7() Assert.AreEqual(130.00m, mid.DownshiftMinSpeed); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0041PackRevision1() + { + string package = "00810041001 01ABCDEFG-123456024294967295032017-12-01:20:12:4504GFEDCBA-10"; + + AssertBuildAndParse(package, new Mid0041(1) + { + ToolSerialNumber = "ABCDEFG-123456", + ToolNumberOfTightenings = 4294967295L, + LastCalibrationDate = new DateTime(2017, 12, 1, 20, 12, 45), + ControllerSerialNumber = "GFEDCBA-10" + }); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0041PackRevision2() + { + string package = "01560041002 01ABCDEFG-123456024294967295032017-12-01:20:12:4504GFEDCBA-1005002000062018-06-04:10:12:45074284967295081009551011111Version 1.0.0 "; + + AssertBuildAndParse(package, new Mid0041(2) + { + ToolSerialNumber = "ABCDEFG-123456", + ToolNumberOfTightenings = 4294967295L, + LastCalibrationDate = new DateTime(2017, 12, 1, 20, 12, 45), + ControllerSerialNumber = "GFEDCBA-10", + CalibrationValue = 20m, + LastServiceDate = new DateTime(2018, 6, 4, 10, 12, 45), + TighteningsSinceService = 4284967295L, + ToolType = ToolType.STB_Tool, + MotorSize = 55, + OpenEndData = new OpenEndData() + { + UseOpenEnd = true, + TighteningDirection = TighteningDirection.Counterclockwise, + MotorRotation = MotorRotation.Inverted + }, + ControllerSoftwareVersion = "Version 1.0.0" + }); + } + + [TestMethod] + [TestCategory("Revision 3"), TestCategory("Pack")] + public void Mid0041PackRevision3() + { + string package = "01800041003 01ABCDEFG-123456024294967295032017-12-01:20:12:4504GFEDCBA-1005002000062018-06-04:10:12:45074284967295081009551011111Version 1.0.0 120006001300123014004000"; + + AssertBuildAndParse(package, new Mid0041(3) + { + ToolSerialNumber = "ABCDEFG-123456", + ToolNumberOfTightenings = 4294967295L, + LastCalibrationDate = new DateTime(2017, 12, 1, 20, 12, 45), + ControllerSerialNumber = "GFEDCBA-10", + CalibrationValue = 20m, + LastServiceDate = new DateTime(2018, 6, 4, 10, 12, 45), + TighteningsSinceService = 4284967295L, + ToolType = ToolType.STB_Tool, + MotorSize = 55, + OpenEndData = new OpenEndData() + { + UseOpenEnd = true, + TighteningDirection = TighteningDirection.Counterclockwise, + MotorRotation = MotorRotation.Inverted + }, + ControllerSoftwareVersion = "Version 1.0.0", + ToolMaxTorque = 6m, + GearRatio = 12.3m, + ToolFullSpeed = 40m + }); + } + + [TestMethod] + [TestCategory("Revision 4"), TestCategory("Pack")] + public void Mid0041PackRevision4() + { + string package = "01840041004 01ABCDEFG-123456024294967295032017-12-01:20:12:4504GFEDCBA-1005002000062018-06-04:10:12:45074284967295081009551011111Version 1.0.0 1200060013001230140040001503"; + + AssertBuildAndParse(package, new Mid0041(4) + { + ToolSerialNumber = "ABCDEFG-123456", + ToolNumberOfTightenings = 4294967295L, + LastCalibrationDate = new DateTime(2017, 12, 1, 20, 12, 45), + ControllerSerialNumber = "GFEDCBA-10", + CalibrationValue = 20m, + LastServiceDate = new DateTime(2018, 6, 4, 10, 12, 45), + TighteningsSinceService = 4284967295L, + ToolType = ToolType.STB_Tool, + MotorSize = 55, + OpenEndData = new OpenEndData() + { + UseOpenEnd = true, + TighteningDirection = TighteningDirection.Counterclockwise, + MotorRotation = MotorRotation.Inverted + }, + ControllerSoftwareVersion = "Version 1.0.0", + ToolMaxTorque = 6m, + GearRatio = 12.3m, + ToolFullSpeed = 40m, + PrimaryTool = PrimaryTool.IRC_W + }); + } + + [TestMethod] + [TestCategory("Revision 5"), TestCategory("Pack")] + public void Mid0041PackRevision5() + { + string package = "01980041005 01ABCDEFG-123456024294967295032017-12-01:20:12:4504GFEDCBA-1005002000062018-06-04:10:12:45074284967295081009551011111Version 1.0.0 120006001300123014004000150316Tool Model "; + + AssertBuildAndParse(package, new Mid0041(5) + { + ToolSerialNumber = "ABCDEFG-123456", + ToolNumberOfTightenings = 4294967295L, + LastCalibrationDate = new DateTime(2017, 12, 1, 20, 12, 45), + ControllerSerialNumber = "GFEDCBA-10", + CalibrationValue = 20m, + LastServiceDate = new DateTime(2018, 6, 4, 10, 12, 45), + TighteningsSinceService = 4284967295L, + ToolType = ToolType.STB_Tool, + MotorSize = 55, + OpenEndData = new OpenEndData() + { + UseOpenEnd = true, + TighteningDirection = TighteningDirection.Counterclockwise, + MotorRotation = MotorRotation.Inverted + }, + ControllerSoftwareVersion = "Version 1.0.0", + ToolMaxTorque = 6m, + GearRatio = 12.3m, + ToolFullSpeed = 40m, + PrimaryTool = PrimaryTool.IRC_W, + ToolModel = "Tool Model" + }); + } + + [TestMethod] + [TestCategory("Revision 6"), TestCategory("Pack")] + public void Mid0041PackRevision6() + { + string package = "02360041006 01ABCDEFG-123456024294967295032017-12-01:20:12:4504GFEDCBA-1005002000062018-06-04:10:12:45074284967295081009551011111Version 1.0.0 120006001300123014004000150316Tool Model 17000118Tool Article Number "; + + AssertBuildAndParse(package, new Mid0041(6) + { + ToolSerialNumber = "ABCDEFG-123456", + ToolNumberOfTightenings = 4294967295L, + LastCalibrationDate = new DateTime(2017, 12, 1, 20, 12, 45), + ControllerSerialNumber = "GFEDCBA-10", + CalibrationValue = 20m, + LastServiceDate = new DateTime(2018, 6, 4, 10, 12, 45), + TighteningsSinceService = 4284967295L, + ToolType = ToolType.STB_Tool, + MotorSize = 55, + OpenEndData = new OpenEndData() + { + UseOpenEnd = true, + TighteningDirection = TighteningDirection.Counterclockwise, + MotorRotation = MotorRotation.Inverted + }, + ControllerSoftwareVersion = "Version 1.0.0", + ToolMaxTorque = 6m, + GearRatio = 12.3m, + ToolFullSpeed = 40m, + PrimaryTool = PrimaryTool.IRC_W, + ToolModel = "Tool Model", + ToolNumber = 1, + ToolArticleNumber = "Tool Article Number" + }); + } + + [TestMethod] + [TestCategory("Revision 7"), TestCategory("Pack")] + public void Mid0041PackRevision7() + { + string package = "02600041007 01ABCDEFG-123456024294967295032017-12-01:20:12:4504GFEDCBA-1005002000062018-06-04:10:12:45074284967295081009551011111Version 1.0.0 120006001300123014004000150316Tool Model 17000118Tool Article Number 190010002003200021013000"; + + AssertBuildAndParse(package, new Mid0041(7) + { + ToolSerialNumber = "ABCDEFG-123456", + ToolNumberOfTightenings = 4294967295L, + LastCalibrationDate = new DateTime(2017, 12, 1, 20, 12, 45), + ControllerSerialNumber = "GFEDCBA-10", + CalibrationValue = 20m, + LastServiceDate = new DateTime(2018, 6, 4, 10, 12, 45), + TighteningsSinceService = 4284967295L, + ToolType = ToolType.STB_Tool, + MotorSize = 55, + OpenEndData = new OpenEndData() + { + UseOpenEnd = true, + TighteningDirection = TighteningDirection.Counterclockwise, + MotorRotation = MotorRotation.Inverted + }, + ControllerSoftwareVersion = "Version 1.0.0", + ToolMaxTorque = 6m, + GearRatio = 12.3m, + ToolFullSpeed = 40m, + PrimaryTool = PrimaryTool.IRC_W, + ToolModel = "Tool Model", + ToolNumber = 1, + ToolArticleNumber = "Tool Article Number", + RundownMinSpeed = 10m, + DownshiftMaxSpeed = 320m, + DownshiftMinSpeed = 130m + }); + } } } diff --git a/src/MIDTesters.Core/Tool/TestMid0042.cs b/src/MIDTesters.Core/Tool/TestMid0042.cs index 127690ea..eeec966b 100644 --- a/src/MIDTesters.Core/Tool/TestMid0042.cs +++ b/src/MIDTesters.Core/Tool/TestMid0042.cs @@ -55,5 +55,27 @@ public void Mid0042ByteRevision2() Assert.AreEqual(DisableType.Disable, mid.DisableType); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0042PackRevision1() + { + string package = "00200042 "; + + AssertBuildAndParse(package, new Mid0042(1), true); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0042PackRevision2() + { + string package = "00300042002 0100420200"; + + AssertBuildAndParse(package, new Mid0042(2) + { + ToolNumber = 42, + DisableType = DisableType.Disable + }); + } } } diff --git a/src/MIDTesters.Core/Tool/TestMid0043.cs b/src/MIDTesters.Core/Tool/TestMid0043.cs index 99882106..1ac61e60 100644 --- a/src/MIDTesters.Core/Tool/TestMid0043.cs +++ b/src/MIDTesters.Core/Tool/TestMid0043.cs @@ -52,5 +52,26 @@ public void Mid0043ByteRevision2() Assert.AreEqual(32, mid.ToolNumber); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0043PackRevision1() + { + string package = "00200043 "; + + AssertBuildAndParse(package, new Mid0043(1), true); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0043PackRevision2() + { + string package = "00260043002 010032"; + + AssertBuildAndParse(package, new Mid0043(2) + { + ToolNumber = 32 + }); + } } } diff --git a/src/MIDTesters.Core/Tool/TestMid0044.cs b/src/MIDTesters.Core/Tool/TestMid0044.cs index 48a9fe2a..6d0ffd98 100644 --- a/src/MIDTesters.Core/Tool/TestMid0044.cs +++ b/src/MIDTesters.Core/Tool/TestMid0044.cs @@ -29,5 +29,14 @@ public void Mid0044ByteAllRevisions() Assert.AreEqual(typeof(Mid0044), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Pack")] + public void Mid0044PackAllRevisions() + { + string package = "00200044 "; + + AssertBuildAndParse(package, new Mid0044(), true); + } } } diff --git a/src/MIDTesters.Core/Tool/TestMid0045.cs b/src/MIDTesters.Core/Tool/TestMid0045.cs index e4bec91d..625b952c 100644 --- a/src/MIDTesters.Core/Tool/TestMid0045.cs +++ b/src/MIDTesters.Core/Tool/TestMid0045.cs @@ -61,5 +61,32 @@ public void Mid0045ByteRevision2() Assert.AreEqual(2, mid.ChannelNumber); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0045PackRevision1() + { + string package = "00310045 01402003000"; + + AssertBuildAndParse(package, new Mid0045(1) + { + CalibrationValueUnit = CalibrationUnit.Kpm, + CalibrationValue = 30 + }, true); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0045PackRevision2() + { + string package = "00350045002 014020030000301"; + + AssertBuildAndParse(package, new Mid0045(2) + { + CalibrationValueUnit = CalibrationUnit.Kpm, + CalibrationValue = 30, + ChannelNumber = 1 + }); + } } } diff --git a/src/MIDTesters.Core/Tool/TestMid0046.cs b/src/MIDTesters.Core/Tool/TestMid0046.cs index 4aa2d4d3..4ad59ee7 100644 --- a/src/MIDTesters.Core/Tool/TestMid0046.cs +++ b/src/MIDTesters.Core/Tool/TestMid0046.cs @@ -30,5 +30,17 @@ public void Mid0046ByteRevision1() Assert.AreEqual(PrimaryTool.IRC_B, mid.PrimaryTool); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0046PackRevision1() + { + string package = "00240046001 0102"; + + AssertBuildAndParse(package, new Mid0046() + { + PrimaryTool = PrimaryTool.IRC_B + }); + } } } diff --git a/src/MIDTesters.Core/Tool/TestMid0047.cs b/src/MIDTesters.Core/Tool/TestMid0047.cs index 8c7e4210..ccb329c7 100644 --- a/src/MIDTesters.Core/Tool/TestMid0047.cs +++ b/src/MIDTesters.Core/Tool/TestMid0047.cs @@ -30,5 +30,17 @@ public void Mid0047ByteRevision1() Assert.AreEqual(PairingHandlingType.FetchLatestPairingStatus, mid.PairingHandlingType); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0047PackRevision1() + { + string package = "00240047001 0103"; + + AssertBuildAndParse(package, new Mid0047() + { + PairingHandlingType = PairingHandlingType.FetchLatestPairingStatus + }); + } } } diff --git a/src/MIDTesters.Core/Tool/TestMid0048.cs b/src/MIDTesters.Core/Tool/TestMid0048.cs index 5552d199..c5d30e64 100644 --- a/src/MIDTesters.Core/Tool/TestMid0048.cs +++ b/src/MIDTesters.Core/Tool/TestMid0048.cs @@ -33,5 +33,18 @@ public void Mid0047ByteRevision1() Assert.AreEqual(new DateTime(2017, 12, 1, 20, 12, 45), mid.TimeStamp); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0048PackRevision1() + { + string package = "00450048001 0107022017-12-01:20:12:45"; + + AssertBuildAndParse(package, new Mid0048() + { + PairingStatus = PairingStatus.Denied, + TimeStamp = new DateTime(2017, 12, 1, 20, 12, 45) + }); + } } } diff --git a/src/MIDTesters.Core/Tool/TestMid0701.cs b/src/MIDTesters.Core/Tool/TestMid0701.cs index 1695f523..9feeceaf 100644 --- a/src/MIDTesters.Core/Tool/TestMid0701.cs +++ b/src/MIDTesters.Core/Tool/TestMid0701.cs @@ -1,6 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter.Tool; +using System.Collections.Generic; namespace MIDTesters.Tool { [TestClass] @@ -31,5 +32,34 @@ public void Mid0701ByteRevision1() Assert.AreEqual(2, mid.TotalTools); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0701PackRevision1() + { + string package = "02110701001 0020001Tool 1 Serial number Tool 1 Model Name Tool 1 Model Article Number 0002Tool 2 Serial number Tool 2 Model Name Tool 2 Model Article Number "; + + AssertBuildAndParse(package, new Mid0701() + { + TotalTools = 2, + Tools = new List() + { + new ToolData() + { + Number = 1, + SerialNumber = "Tool 1 Serial number", + ModelName = "Tool 1 Model Name", + ModelArticleNumber = "Tool 1 Model Article Number" + }, + new ToolData() + { + Number = 2, + SerialNumber = "Tool 2 Serial number", + ModelName = "Tool 2 Model Name", + ModelArticleNumber = "Tool 2 Model Article Number" + } + } + }); + } } } diff --git a/src/MIDTesters.Core/Tool/TestMid0702.cs b/src/MIDTesters.Core/Tool/TestMid0702.cs index 44345334..4c2999b7 100644 --- a/src/MIDTesters.Core/Tool/TestMid0702.cs +++ b/src/MIDTesters.Core/Tool/TestMid0702.cs @@ -2,6 +2,8 @@ using OpenProtocolInterpreter.Communication; using OpenProtocolInterpreter.Tool; +using OpenProtocolInterpreter; +using System.Collections.Generic; namespace MIDTesters.Tool { [TestClass] @@ -66,5 +68,47 @@ public void Mid0702ExtraDataRequestByteRevision1() AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0702PackRevision1() + { + string package = "01000702001 00301200012040000000QST50-150CTT01202012040000000SERIALNUMBER0120300201000000011"; + + AssertBuildAndParse(package, new Mid0702() + { + NumberOfToolPIDs = 3, + ToolDataUpload = new List() + { + new VariableDataField() + { + ParameterId = 1200, + Length = 12, + DataType = DataTypeDefinition.String, + Unit = DataUnitType.NoUnit, + StepNumber = 0, + DataValue = "QST50-150CTT" + }, + new VariableDataField() + { + ParameterId = 1202, + Length = 12, + DataType = DataTypeDefinition.String, + Unit = DataUnitType.NoUnit, + StepNumber = 0, + DataValue = "SERIALNUMBER" + }, + new VariableDataField() + { + ParameterId = 1203, + Length = 2, + DataType = DataTypeDefinition.UnsignedInteger, + Unit = DataUnitType.NoUnit, + StepNumber = 0, + DataValue = "11" + } + } + }); + } } } diff --git a/src/MIDTesters.Core/Tool/TestMid0703.cs b/src/MIDTesters.Core/Tool/TestMid0703.cs index 8445ed2d..89914c27 100644 --- a/src/MIDTesters.Core/Tool/TestMid0703.cs +++ b/src/MIDTesters.Core/Tool/TestMid0703.cs @@ -1,6 +1,8 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter.Tool; +using System.Collections.Generic; +using OpenProtocolInterpreter; namespace MIDTesters.Tool { [TestClass] @@ -33,5 +35,48 @@ public void Mid0703ByteRevision1() Assert.AreEqual(40, mid.ToolNumber); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0703PackRevision1() + { + string package = "01050703001 0100400301200012040000000QST50-150CTT01202012040000000SERIALNUMBER0120300201000000011"; + + AssertBuildAndParse(package, new Mid0703() + { + ToolNumber = 40, + NumberOfCalibrationParameters = 3, + CalibrationParameters = new List() + { + new VariableDataField() + { + ParameterId = 1200, + Length = 12, + DataType = DataTypeDefinition.String, + Unit = DataUnitType.NoUnit, + StepNumber = 0, + DataValue = "QST50-150CTT" + }, + new VariableDataField() + { + ParameterId = 1202, + Length = 12, + DataType = DataTypeDefinition.String, + Unit = DataUnitType.NoUnit, + StepNumber = 0, + DataValue = "SERIALNUMBER" + }, + new VariableDataField() + { + ParameterId = 1203, + Length = 2, + DataType = DataTypeDefinition.UnsignedInteger, + Unit = DataUnitType.NoUnit, + StepNumber = 0, + DataValue = "11" + } + } + }); + } } } diff --git a/src/MIDTesters.Core/Tool/TestMid0704.cs b/src/MIDTesters.Core/Tool/TestMid0704.cs index b4cfb653..4adefce8 100644 --- a/src/MIDTesters.Core/Tool/TestMid0704.cs +++ b/src/MIDTesters.Core/Tool/TestMid0704.cs @@ -3,6 +3,7 @@ using OpenProtocolInterpreter.Tool; using System.Collections.Generic; +using OpenProtocolInterpreter; namespace MIDTesters.Tool { [TestClass] @@ -76,7 +77,7 @@ public void Mid0704ExtraDataRequestByteRevision1() } [TestMethod] - [TestCategory("Revision 1"), TestCategory("ASCII")] + [TestCategory("Revision 1"), TestCategory("Pack")] public void Mid0704ExtraDataRequestPackRevision1() { string package = "00480006001 0704001190001002001213001215"; @@ -140,7 +141,7 @@ public void Mid0704ExtraDataSubscriptionByteRevision1() } [TestMethod] - [TestCategory("Revision 1"), TestCategory("ASCII")] + [TestCategory("Revision 1"), TestCategory("Pack")] public void Mid0704ExtraDataSubscriptionPackRevision1() { string package = "00540008001 0704001250001002001213005001215000"; @@ -196,7 +197,7 @@ public void Mid0704ExtraDataUnsubscriptionByteRevision1() } [TestMethod] - [TestCategory("Revision 1"), TestCategory("ASCII")] + [TestCategory("Revision 1"), TestCategory("Pack")] public void Mid0704ExtraDataUnsubscriptionPackRevision1() { string package = "00330009001 0704001040001"; @@ -212,5 +213,39 @@ public void Mid0704ExtraDataUnsubscriptionPackRevision1() Assert.AreEqual("0001", mid.ExtraData); AssertEqualPackages(package, mid); } + + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0704PackRevision1() + { + string package = "00700704001 00201200012040000000QST50-150CTT012150010200000003"; + + AssertBuildAndParse(package, new Mid0704() + { + NumberOfDataFields = 2, + VariableDataFields = new List() + { + new VariableDataField() + { + ParameterId = 1200, + Length = 12, + DataType = DataTypeDefinition.String, + Unit = DataUnitType.NoUnit, + StepNumber = 0, + DataValue = "QST50-150CTT" + }, + new VariableDataField() + { + ParameterId = 1215, + Length = 1, + DataType = DataTypeDefinition.Integer, + Unit = DataUnitType.NoUnit, + StepNumber = 0, + DataValue = "3" + } + } + }); + } } } diff --git a/src/MIDTesters.Core/UserInterface/TestMid0110.cs b/src/MIDTesters.Core/UserInterface/TestMid0110.cs index 765a9c49..e1d5c7c3 100644 --- a/src/MIDTesters.Core/UserInterface/TestMid0110.cs +++ b/src/MIDTesters.Core/UserInterface/TestMid0110.cs @@ -29,5 +29,17 @@ public void Mid0110ByteRevision1() Assert.AreEqual("TEST", mid.UserText); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0110PackRevision1() + { + string package = "00240110001 TEST"; + + AssertBuildAndParse(package, new Mid0110() + { + UserText = "TEST" + }); + } } } diff --git a/src/MIDTesters.Core/UserInterface/TestMid0111.cs b/src/MIDTesters.Core/UserInterface/TestMid0111.cs index 339eed57..8513b685 100644 --- a/src/MIDTesters.Core/UserInterface/TestMid0111.cs +++ b/src/MIDTesters.Core/UserInterface/TestMid0111.cs @@ -40,5 +40,22 @@ public void Mid0111ByteRevision1() Assert.AreEqual("Line 4 Text", mid.Line4.TrimEnd()); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0111PackRevision1() + { + string package = "01370111001 01200502103Header Text 04Line 2 Text 05Line 3 Text 06Line 4 Text "; + + AssertBuildAndParse(package, new Mid0111() + { + TextDuration = 2005, + RemovalCondition = RemovalCondition.Acknowledge, + Line1 = "Header Text", + Line2 = "Line 2 Text", + Line3 = "Line 3 Text", + Line4 = "Line 4 Text" + }); + } } } diff --git a/src/MIDTesters.Core/UserInterface/TestMid0113.cs b/src/MIDTesters.Core/UserInterface/TestMid0113.cs index 121cd847..df976ef8 100644 --- a/src/MIDTesters.Core/UserInterface/TestMid0113.cs +++ b/src/MIDTesters.Core/UserInterface/TestMid0113.cs @@ -29,5 +29,14 @@ public void Mid0113ByteAllRevisions() Assert.AreEqual(typeof(Mid0113), mid.GetType()); AssertEqualPackages(bytes, mid, true); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0113PackRevision1() + { + string package = "00200113 "; + + AssertBuildAndParse(package, new Mid0113(), true); + } } } diff --git a/src/MIDTesters.Core/Vin/TestMid0050.cs b/src/MIDTesters.Core/Vin/TestMid0050.cs index 0b1a70c0..301cef64 100644 --- a/src/MIDTesters.Core/Vin/TestMid0050.cs +++ b/src/MIDTesters.Core/Vin/TestMid0050.cs @@ -29,5 +29,17 @@ public void Mid0050ByteRevision1() Assert.AreEqual("VehicleIdNumber", mid.VinNumber); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0050PackRevision1() + { + string package = "00350050001 VehicleIdNumber"; + + AssertBuildAndParse(package, new Mid0050() + { + VinNumber = "VehicleIdNumber" + }); + } } } diff --git a/src/MIDTesters.Core/Vin/TestMid0051.cs b/src/MIDTesters.Core/Vin/TestMid0051.cs index ea52839b..32b652c9 100644 --- a/src/MIDTesters.Core/Vin/TestMid0051.cs +++ b/src/MIDTesters.Core/Vin/TestMid0051.cs @@ -31,5 +31,14 @@ public void Mid0051ByteAllRevisions() Assert.IsTrue(mid.Header.NoAckFlag); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Pack")] + public void Mid0051PackAllRevisions() + { + string package = "002000510011 "; + + AssertBuildAndParse(package, new Mid0051(1, noAckFlag: true)); + } } } diff --git a/src/MIDTesters.Core/Vin/TestMid0052.cs b/src/MIDTesters.Core/Vin/TestMid0052.cs index 32d8b057..6890bd36 100644 --- a/src/MIDTesters.Core/Vin/TestMid0052.cs +++ b/src/MIDTesters.Core/Vin/TestMid0052.cs @@ -86,5 +86,32 @@ public void Mid0052ByteRevision2() Assert.AreEqual("IdentifierPart4", mid.IdentifierResultPart4.TrimEnd()); AssertEqualPackages(bytes, mid); } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0052PackRevision1() + { + string package = "00470052001 VehicleIdNumberHigherThan25"; + + AssertBuildAndParse(package, new Mid0052(1) + { + VinNumber = "VehicleIdNumberHigherThan25" + }); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid0052PackRevision2() + { + string package = "01280052002 01VehicleIdNumber 02IdentifierPart2 03IdentifierPart3 04IdentifierPart4 "; + + AssertBuildAndParse(package, new Mid0052(2) + { + VinNumber = "VehicleIdNumber", + IdentifierResultPart2 = "IdentifierPart2", + IdentifierResultPart3 = "IdentifierPart3", + IdentifierResultPart4 = "IdentifierPart4" + }); + } } } diff --git a/src/MIDTesters.Core/Vin/TestMid0053.cs b/src/MIDTesters.Core/Vin/TestMid0053.cs index 3f73a199..026726c9 100644 --- a/src/MIDTesters.Core/Vin/TestMid0053.cs +++ b/src/MIDTesters.Core/Vin/TestMid0053.cs @@ -16,5 +16,14 @@ public void Mid0053AllRevisions() AssertEqualPackages(package, mid); } + + [TestMethod] + [TestCategory("Pack")] + public void Mid0053PackAllRevisions() + { + string package = "00200053001 "; + + AssertBuildAndParse(package, new Mid0053(1)); + } } } diff --git a/src/MIDTesters.Core/Vin/TestMid0054.cs b/src/MIDTesters.Core/Vin/TestMid0054.cs index bd525e5e..43e3f526 100644 --- a/src/MIDTesters.Core/Vin/TestMid0054.cs +++ b/src/MIDTesters.Core/Vin/TestMid0054.cs @@ -16,5 +16,14 @@ public void Mid0054AllRevisions() AssertEqualPackages(package, mid, true); } + + [TestMethod] + [TestCategory("Pack")] + public void Mid0054PackAllRevisions() + { + string package = "00200054 "; + + AssertBuildAndParse(package, new Mid0054(1), true); + } } } From f66a1b62146198d2e6b630bc68d0a12511746bd1 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Fri, 7 Aug 2026 01:09:20 -0300 Subject: [PATCH 66/78] Using datafield attributes at mid 1202 --- src/OpenProtocolInterpreter/Result/Mid1202.cs | 138 ++++++++---------- 1 file changed, 60 insertions(+), 78 deletions(-) diff --git a/src/OpenProtocolInterpreter/Result/Mid1202.cs b/src/OpenProtocolInterpreter/Result/Mid1202.cs index 9d8e7cb9..5e909d96 100644 --- a/src/OpenProtocolInterpreter/Result/Mid1202.cs +++ b/src/OpenProtocolInterpreter/Result/Mid1202.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; namespace OpenProtocolInterpreter.Result { @@ -27,37 +28,31 @@ public class Mid1202 : Mid, IResult, IController, IAcknowledgeable, IAc { public const int MID = 1202; - public int TotalNumberOfMessages - { - get => GetField(Header.StandardizedRevision, DataFields.TotalMessages).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(Header.StandardizedRevision, DataFields.TotalMessages).SetValue(OpenProtocolConvert.ToString, value); - } - public int MessageNumber - { - get => GetField(Header.StandardizedRevision, DataFields.MessageNumber).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(Header.StandardizedRevision, DataFields.MessageNumber).SetValue(OpenProtocolConvert.ToString, value); - } - public long ResultDataIdentifier - { - get => GetField(Header.StandardizedRevision, DataFields.ResultDataIdentifier).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(Header.StandardizedRevision, DataFields.ResultDataIdentifier).SetValue(OpenProtocolConvert.ToString, value); - } - public int ObjectId - { - get => GetField(Header.StandardizedRevision, DataFields.ObjectId).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(Header.StandardizedRevision, DataFields.ObjectId).SetValue(OpenProtocolConvert.ToString, value); - } - public string NodeGuid - { - get => GetField(Header.StandardizedRevision, DataFields.NodeGuid).Value; - set => GetField(Header.StandardizedRevision, DataFields.NodeGuid).SetValue(value); - } - public int NumberOfDataFields - { - get => GetField(Header.StandardizedRevision, DataFields.NumberOfDataFields).GetValue(OpenProtocolConvert.ToInt32); - set => GetField(Header.StandardizedRevision, DataFields.NumberOfDataFields).SetValue(OpenProtocolConvert.ToString, value); - } + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 2, field: 1, Index = 20, Size = 3, HasPrefix = false)] + public int TotalNumberOfMessages { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 23, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 2, field: 2, Index = 23, Size = 3, HasPrefix = false)] + public int MessageNumber { get; set; } + + [Int64DataFieldDefinition(revision: 1, field: 3, Index = 26, Size = 10, HasPrefix = false)] + [Int64DataFieldDefinition(revision: 2, field: 3, Index = 26, Size = 10, HasPrefix = false)] + public long ResultDataIdentifier { get; set; } + [Int32DataFieldDefinition(revision: 1, field: 4, Index = 36, Size = 4, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 2, field: 4, Index = 36, Size = 4, HasPrefix = false)] + public int ObjectId { get; set; } + + [StringDataFieldDefinition(revision: 2, field: 5, Index = 40, Size = 36, HasPrefix = false)] + public string NodeGuid { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 5, Index = 40, Size = 3, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 2, field: 6, Index = 76, Size = 3, HasPrefix = false)] + public int NumberOfDataFields { get; set; } + + [VariableDataFieldCollectionDefinition(revision: 1, field: 6, Index = 43, Size = 0, HasPrefix = false)] + [VariableDataFieldCollectionDefinition(revision: 2, field: 7, Index = 79, Size = 0, HasPrefix = false)] public List VariableDataFields { get; set; } public Mid1202() : this(new Header() @@ -72,71 +67,58 @@ public Mid1202(Header header) : base(header) { VariableDataFields = []; } + protected override string BuildHeader() { - Header.Length = 20 + RevisionsByFields[Header.StandardizedRevision].Sum(x => x.TotalSize); + var fields = DataFieldsByRevision(); + Header.Length = Header.DefaultSize + fields.Sum(x => x.TotalSize); return Header.ToString(); } public override string Pack() { - NumberOfDataFields = VariableDataFields.Count; - var revision = Header.StandardizedRevision; - GetField(revision, DataFields.VariableDataFields).SetValue(OpenProtocolConvert.ToString(VariableDataFields)); - return string.Concat(BuildHeader(), base.Pack(revision)); + NumberOfDataFields = VariableDataFields?.Count ?? 0; //Enforce list size even if modified + var field = Header.StandardizedRevision > 1 ? 7 : 6; + GetField(revision: Header.StandardizedRevision, field: field).Size = VariableDataFields?.Sum(x => x.TotalSize) ?? 0; //Enforce size of variable data fields + + var builder = new StringBuilder(); + builder.Append(BuildHeader()); + builder.Append(Pack(DataFieldsByRevision())); + return builder.ToString(); } - public override Mid Parse(ReadOnlySpan package) + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) { - Header = ProcessHeader(package); - var revision = Header.StandardizedRevision; - var variableDataField = GetField(revision, DataFields.VariableDataFields); - variableDataField.Size = Header.Length - variableDataField.Index; - ProcessDataFields(revision, package); - - VariableDataFields = VariableDataField.ParseAll(variableDataField.Value).ToList(); - return this; + switch (dataField.Field) + { + case 6 when dataField is DataField>: + case 7 when dataField is DataField>: + dataField.Size = Header.Length - dataField.Index; + break; + } + + base.ProcessDataField(dataField, package); } - protected override Dictionary> RegisterDatafields() + protected override void ProcessDataFields(ReadOnlySpan package) { - return new Dictionary>() + foreach (var field in DataFieldsByRevision()) + ProcessDataField(field, package); + } + + private IEnumerable DataFieldsByRevision() + { + var previousField = default(DataField); + foreach (var dataField in RevisionsByFields[Header.StandardizedRevision]) { + if (previousField != null && dataField.Index == 0) { - 1, new List() - { - DataField.Number(DataFields.TotalMessages, 20, 3, false), - DataField.Number(DataFields.MessageNumber, 23, 3, false), - DataField.Number(DataFields.ResultDataIdentifier, 26, 10, false), - DataField.Number(DataFields.ObjectId, 36, 4, false), - DataField.Number(DataFields.NumberOfDataFields, 40, 3, false), - DataField.Volatile(DataFields.VariableDataFields, 43, false) //defined at runtime - } - }, - { - 2, new List() - { - DataField.Number(DataFields.TotalMessages, 20, 3, false), - DataField.Number(DataFields.MessageNumber, 23, 3, false), - DataField.Number(DataFields.ResultDataIdentifier, 26, 10, false), - DataField.Number(DataFields.ObjectId, 36, 4, false), - DataField.String(DataFields.NodeGuid, 40, 36, false), - DataField.Number(DataFields.NumberOfDataFields, 76, 3, false), - DataField.Volatile(DataFields.VariableDataFields, 79, false) //defined at runtime - } + dataField.Index = previousField.Index + previousField.TotalSize; } - }; - } - protected enum DataFields - { - TotalMessages, - MessageNumber, - ResultDataIdentifier, - ObjectId, - NodeGuid, - NumberOfDataFields, - VariableDataFields + previousField = dataField; + yield return dataField; + } } } } From 3a5f47e8978fb219b89a221e14d8ee93fbdda008 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Fri, 7 Aug 2026 01:09:33 -0300 Subject: [PATCH 67/78] Added Mid1202 and Mid1203 tests --- src/MIDTesters.Core/Result/TestMid1202.cs | 135 ++++++++++++++++++++++ src/MIDTesters.Core/Result/TestMid1203.cs | 42 +++++++ 2 files changed, 177 insertions(+) create mode 100644 src/MIDTesters.Core/Result/TestMid1202.cs create mode 100644 src/MIDTesters.Core/Result/TestMid1203.cs diff --git a/src/MIDTesters.Core/Result/TestMid1202.cs b/src/MIDTesters.Core/Result/TestMid1202.cs new file mode 100644 index 00000000..26670bcc --- /dev/null +++ b/src/MIDTesters.Core/Result/TestMid1202.cs @@ -0,0 +1,135 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; +using OpenProtocolInterpreter.Result; +using System.Collections.Generic; + +namespace MIDTesters.Result +{ + [TestClass] + [TestCategory("Result")] + public class TestMid1202 : DefaultMidTests + { + private const string Revision1Package = "01221202001 0020010000000153001000301000003010000000005010030190500000002022-08-12:13:33:2201001006040000001TORQUE"; + private const string Revision2Package = "01581202002 002001000000015300101b6d1a1e-4b3f-4b1a-9c2d-7f5e8a9b0c1d00301000003010000000005010030190500000002022-08-12:13:33:2201001006040000001TORQUE"; + private const string NodeGuid = "1b6d1a1e-4b3f-4b1a-9c2d-7f5e8a9b0c1d"; + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("ASCII")] + public void Mid1202Revision1() + { + var mid = _midInterpreter.Parse(Revision1Package); + + AssertParsedMid1202(mid, revision: 1); + AssertEqualPackages(Revision1Package, mid); + } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("ByteArray")] + public void Mid1202ByteRevision1() + { + byte[] bytes = GetAsciiBytes(Revision1Package); + var mid = _midInterpreter.Parse(bytes); + + AssertParsedMid1202(mid, revision: 1); + AssertEqualPackages(bytes, mid); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("ASCII")] + public void Mid1202Revision2() + { + var mid = _midInterpreter.Parse(Revision2Package); + + AssertParsedMid1202(mid, revision: 2); + AssertEqualPackages(Revision2Package, mid); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("ByteArray")] + public void Mid1202ByteRevision2() + { + byte[] bytes = GetAsciiBytes(Revision2Package); + var mid = _midInterpreter.Parse(bytes); + + AssertParsedMid1202(mid, revision: 2); + AssertEqualPackages(bytes, mid); + } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid1202PackRevision1() + { + AssertBuildAndParse(Revision1Package, BuildMid1202(1)); + } + + [TestMethod] + [TestCategory("Revision 2"), TestCategory("Pack")] + public void Mid1202PackRevision2() + { + var mid = BuildMid1202(2); + mid.NodeGuid = NodeGuid; + + AssertBuildAndParse(Revision2Package, mid); + } + + private static Mid1202 BuildMid1202(int revision) + { + return new Mid1202(new Header() { Mid = Mid1202.MID, Revision = revision }) + { + TotalNumberOfMessages = 2, + MessageNumber = 1, + ResultDataIdentifier = 153, + ObjectId = 10, + VariableDataFields = BuildVariableDataFields() + }; + } + + private static List BuildVariableDataFields() + { + return new List() + { + new VariableDataField() { ParameterId = 1000, Length = 3, DataType = DataTypeDefinition.UnsignedInteger, Unit = DataUnitType.NoUnit, StepNumber = 0, DataValue = "005" }, + new VariableDataField() { ParameterId = 1003, Length = 19, DataType = DataTypeDefinition.Timestamp, Unit = DataUnitType.NoUnit, StepNumber = 0, DataValue = "2022-08-12:13:33:22" }, + new VariableDataField() { ParameterId = 1001, Length = 6, DataType = DataTypeDefinition.String, Unit = DataUnitType.NoUnit, StepNumber = 1, DataValue = "TORQUE" } + }; + } + + private static void AssertParsedMid1202(Mid1202 mid, int revision) + { + Assert.AreEqual(2, mid.TotalNumberOfMessages); + Assert.AreEqual(1, mid.MessageNumber); + Assert.AreEqual(153, mid.ResultDataIdentifier); + Assert.AreEqual(10, mid.ObjectId); + + if (revision > 1) + { + Assert.AreEqual(NodeGuid, mid.NodeGuid); + } + + Assert.AreEqual(3, mid.NumberOfDataFields); + Assert.AreEqual(3, mid.VariableDataFields.Count); + + var unsignedInteger = mid.VariableDataFields[0]; + Assert.AreEqual(1000, unsignedInteger.ParameterId); + Assert.AreEqual(3, unsignedInteger.Length); + Assert.AreEqual(DataTypeDefinition.UnsignedInteger, unsignedInteger.DataType); + Assert.AreEqual(DataUnitType.NoUnit, unsignedInteger.Unit); + Assert.AreEqual(0, unsignedInteger.StepNumber); + Assert.AreEqual("005", unsignedInteger.DataValue); + + var timestamp = mid.VariableDataFields[1]; + Assert.AreEqual(1003, timestamp.ParameterId); + Assert.AreEqual(19, timestamp.Length); + Assert.AreEqual(DataTypeDefinition.Timestamp, timestamp.DataType); + Assert.AreEqual(0, timestamp.StepNumber); + Assert.AreEqual("2022-08-12:13:33:22", timestamp.DataValue); + + var text = mid.VariableDataFields[2]; + Assert.AreEqual(1001, text.ParameterId); + Assert.AreEqual(6, text.Length); + Assert.AreEqual(DataTypeDefinition.String, text.DataType); + Assert.AreEqual(1, text.StepNumber); + Assert.AreEqual("TORQUE", text.DataValue); + } + } +} diff --git a/src/MIDTesters.Core/Result/TestMid1203.cs b/src/MIDTesters.Core/Result/TestMid1203.cs new file mode 100644 index 00000000..082ea988 --- /dev/null +++ b/src/MIDTesters.Core/Result/TestMid1203.cs @@ -0,0 +1,42 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter.Result; + +namespace MIDTesters.Result +{ + [TestClass] + [TestCategory("Result")] + public class TestMid1203 : DefaultMidTests + { + private const string Revision1Package = "00201203001 "; + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("ASCII")] + public void Mid1203Revision1() + { + var mid = _midInterpreter.Parse(Revision1Package); + + Assert.AreEqual(typeof(Mid1203), mid.GetType()); + Assert.AreEqual(Mid1203.MID, mid.Header.Mid); + AssertEqualPackages(Revision1Package, mid); + } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("ByteArray")] + public void Mid1203ByteRevision1() + { + byte[] bytes = GetAsciiBytes(Revision1Package); + var mid = _midInterpreter.Parse(bytes); + + Assert.AreEqual(typeof(Mid1203), mid.GetType()); + Assert.AreEqual(Mid1203.MID, mid.Header.Mid); + AssertEqualPackages(bytes, mid); + } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid1203PackRevision1() + { + AssertBuildAndParse(Revision1Package, new Mid1203()); + } + } +} From e9d98523581dc2d1c4f0330fd18d8f5870315065 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Fri, 7 Aug 2026 01:18:17 -0300 Subject: [PATCH 68/78] fixed unix epoch for .net standard --- src/OpenProtocolInterpreter/OpenProtocolConvert.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenProtocolInterpreter/OpenProtocolConvert.cs b/src/OpenProtocolInterpreter/OpenProtocolConvert.cs index 30346614..6003aa19 100644 --- a/src/OpenProtocolInterpreter/OpenProtocolConvert.cs +++ b/src/OpenProtocolInterpreter/OpenProtocolConvert.cs @@ -10,6 +10,7 @@ namespace OpenProtocolInterpreter public class OpenProtocolConvert { private static readonly IFormatProvider _formatProvider = new CultureInfo("en-US"); + private static readonly DateTimeOffset _unixEpoch = DateTimeOffset.FromUnixTimeSeconds(0); public static string ToString(bool value) => value ? "1" : "0"; @@ -70,9 +71,8 @@ public static DateTimeOffset ToUnixDateTime(string value) public static DateTimeOffset ToUnixDateTime(ReadOnlySpan value) { - var convertedValue = DateTimeOffset.UnixEpoch; if (value.IsWhiteSpace()) - return convertedValue; + return _unixEpoch; var unixTimestamp = ToInt64(value); return DateTimeOffset.FromUnixTimeSeconds(unixTimestamp); From 1133c7a72a8f7f72cd25ec573d47afc6bc99065c Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Fri, 7 Aug 2026 01:22:11 -0300 Subject: [PATCH 69/78] #118 exposing a parse method for Header --- src/OpenProtocolInterpreter/Header.cs | 54 +++++++++++++++++++++++++-- src/OpenProtocolInterpreter/Mid.cs | 53 +++++++++----------------- 2 files changed, 68 insertions(+), 39 deletions(-) diff --git a/src/OpenProtocolInterpreter/Header.cs b/src/OpenProtocolInterpreter/Header.cs index c44e020e..77ca6aff 100644 --- a/src/OpenProtocolInterpreter/Header.cs +++ b/src/OpenProtocolInterpreter/Header.cs @@ -1,4 +1,5 @@ -using System.Text; +using System; +using System.Text; namespace OpenProtocolInterpreter { @@ -20,7 +21,7 @@ public sealed class Header public int Mid { get; set; } /// - /// The MID Revision is unique per MID and is used in case different versions are available for the same MID. + /// The MID Revision is unique per MID and is used in case different versions are available for the same MID. /// Using the revision number the integrator can subscribe or ask for different versions of the same MID. /// /// Note: The default MID Revision is 1. There is three different ways to get it, either send three spaces or 000 or 001. @@ -29,7 +30,7 @@ public sealed class Header public int Revision { get; set; } /// - /// The MID Revision is unique per MID and is used in case different versions are available for the same MID. + /// The MID Revision is unique per MID and is used in case different versions are available for the same MID. /// Using the revision number the integrator can subscribe or ask for different versions of the same MID. /// /// Note: Enforces the default MID Revision to 1 when it's either send three spaces or 000 or 001. @@ -98,6 +99,53 @@ public override string ToString() builder.Append(MessageNumber.HasValue ? MessageNumber.ToString() : " "); return builder.ToString(); } + + /// + /// Parses the header from a string representation of the MID message. + /// + /// The MID message as a string. + /// The parsed object. + public static Header Parse(string package) + => Parse(package.AsSpan()); + + /// + /// Parses the header from a read-only span of characters representing the MID message. + /// + /// The MID message as a read-only span of characters. + /// The parsed object. + public static Header Parse(ReadOnlySpan package) + { +#if NETSTANDARD2_0 + static bool IsNotEmptyOrZero(ReadOnlySpan package, out int value) + { + value = 0; + return !package.IsWhiteSpace() && int.TryParse(package.ToString(), out value) && value > 0; + } + static int ParseInt(ReadOnlySpan span) => int.Parse(span.ToString()); + static bool TryParseInt(ReadOnlySpan span, out int value) => int.TryParse(span.ToString(), out value); +#else + static bool IsNotEmptyOrZero(ReadOnlySpan package, out int value) + { + value = 0; + return !package.IsWhiteSpace() && int.TryParse(package, out value) && value > 0; + } + static int ParseInt(ReadOnlySpan span) => int.Parse(span); + static bool TryParseInt(ReadOnlySpan span, out int value) => int.TryParse(span, out value); +#endif + + return new Header + { + Length = ParseInt(package.Slice(0, 4)), + Mid = ParseInt(package.Slice(4, 4)), + Revision = IsNotEmptyOrZero(package.Slice(8, 3), out var revision) ? revision : 1, + NoAckFlag = !package.Slice(11, 1).IsWhiteSpace(), + StationId = TryParseInt(package.Slice(12, 2), out var stationId) ? stationId : 1, + SpindleId = TryParseInt(package.Slice(14, 2), out var spindleId) ? spindleId : 1, + SequenceNumber = IsNotEmptyOrZero(package.Slice(16, 2), out var sequenceNumber) ? sequenceNumber : default(int?), + NumberOfMessages = IsNotEmptyOrZero(package.Slice(18, 1), out var numberOfMessages) ? numberOfMessages : default(int?), + MessageNumber = IsNotEmptyOrZero(package.Slice(19, 1), out var messageNumber) ? messageNumber : default(int?) + }; + } } } diff --git a/src/OpenProtocolInterpreter/Mid.cs b/src/OpenProtocolInterpreter/Mid.cs index c5bac3f2..e20dfd36 100644 --- a/src/OpenProtocolInterpreter/Mid.cs +++ b/src/OpenProtocolInterpreter/Mid.cs @@ -176,15 +176,25 @@ protected virtual Header ProcessHeader(ReadOnlySpan package) Span buffer = stackalloc char[20]; package.CopyTo(buffer); buffer.Slice(package.Length).Fill(' '); - return ParseHeader(buffer); + return Header.Parse(buffer); } - return ParseHeader(package); + return Header.Parse(package); } + /// + /// Parses the MID message from a string representation, filling the and data fields for the current revision. + /// + /// The MID message as a string. + /// The parsed object. public virtual Mid Parse(string package) => Parse(package.AsSpan()); + /// + /// Parses the MID message from a read-only span of characters, filling the and data fields for the current revision. + /// + /// The MID message as a read-only span of characters. + /// The parsed object. public virtual Mid Parse(ReadOnlySpan package) { Header = ProcessHeader(package); @@ -192,6 +202,11 @@ public virtual Mid Parse(ReadOnlySpan package) return this; } + /// + /// Parses the MID message from a byte array representation, filling the and data fields for the current revision. + /// + /// The MID message as a byte array. + /// The parsed object. public virtual Mid Parse(byte[] package) { var pack = ToAscii(package); @@ -312,39 +327,5 @@ protected static Span ToBytes(ReadOnlySpan value) #endif return buffer; } - - private Header ParseHeader(ReadOnlySpan package) - { -#if NETSTANDARD2_0 - static bool IsNotEmptyOrZero(ReadOnlySpan package, out int value) - { - value = 0; - return !package.IsWhiteSpace() && int.TryParse(package.ToString(), out value) && value > 0; - } - static int ParseInt(ReadOnlySpan span) => int.Parse(span.ToString()); - static bool TryParseInt(ReadOnlySpan span, out int value) => int.TryParse(span.ToString(), out value); -#else - static bool IsNotEmptyOrZero(ReadOnlySpan package, out int value) - { - value = 0; - return !package.IsWhiteSpace() && int.TryParse(package, out value) && value > 0; - } - static int ParseInt(ReadOnlySpan span) => int.Parse(span); - static bool TryParseInt(ReadOnlySpan span, out int value) => int.TryParse(span, out value); -#endif - - return new Header - { - Length = ParseInt(package.Slice(0, 4)), - Mid = ParseInt(package.Slice(4, 4)), - Revision = IsNotEmptyOrZero(package.Slice(8, 3), out var revision) ? revision : 1, - NoAckFlag = !package.Slice(11, 1).IsWhiteSpace(), - StationId = TryParseInt(package.Slice(12, 2), out var stationId) ? stationId : 1, - SpindleId = TryParseInt(package.Slice(14, 2), out var spindleId) ? spindleId : 1, - SequenceNumber = IsNotEmptyOrZero(package.Slice(16, 2), out var sequenceNumber) ? sequenceNumber : default(int?), - NumberOfMessages = IsNotEmptyOrZero(package.Slice(18, 1), out var numberOfMessages) ? numberOfMessages : default(int?), - MessageNumber = IsNotEmptyOrZero(package.Slice(19, 1), out var messageNumber) ? messageNumber : default(int?) - }; - } } } From e435631ebaaeab8e03b65e10400bb5f2652a1cf8 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Fri, 7 Aug 2026 02:08:21 -0300 Subject: [PATCH 70/78] Added mid 0067 --- src/MIDTesters.Core/Tightening/TestMid0067.cs | 167 ++++++++++++++++++ .../Tightening/Mid0067.cs | 65 +++++++ .../Tightening/ResultData.cs | 80 +++++++++ .../Tightening/TighteningMessages.cs | 11 ++ 4 files changed, 323 insertions(+) create mode 100644 src/MIDTesters.Core/Tightening/TestMid0067.cs create mode 100644 src/OpenProtocolInterpreter/Tightening/Mid0067.cs create mode 100644 src/OpenProtocolInterpreter/Tightening/ResultData.cs diff --git a/src/MIDTesters.Core/Tightening/TestMid0067.cs b/src/MIDTesters.Core/Tightening/TestMid0067.cs new file mode 100644 index 00000000..8ce79942 --- /dev/null +++ b/src/MIDTesters.Core/Tightening/TestMid0067.cs @@ -0,0 +1,167 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter.Communication; +using OpenProtocolInterpreter.Tightening; +using System; +using System.Collections.Generic; + +using OpenProtocolInterpreter; +namespace MIDTesters.Tightening +{ + [TestClass] + [TestCategory("Tightening")] + public class TestMid0067 : DefaultMidTests + { + [TestMethod] + [TestCategory("Revision 1"), TestCategory("ASCII")] + public void Mid0067Revision1() + { + string package = "00820067001 0200000000012018-04-25:10:23:45100000000022018-04-25:10:24:010"; + var mid = _midInterpreter.Parse(package); + + Assert.AreEqual(typeof(Mid0067), mid.GetType()); + Assert.AreEqual(2, mid.NumberOfResults); + Assert.AreEqual(2, mid.Results.Count); + + Assert.AreEqual(1, mid.Results[0].Index); + Assert.AreEqual(new DateTime(2018, 4, 25, 10, 23, 45), mid.Results[0].StartTime); + Assert.AreEqual(1, mid.Results[0].Status); + + Assert.AreEqual(2, mid.Results[1].Index); + Assert.AreEqual(new DateTime(2018, 4, 25, 10, 24, 1), mid.Results[1].StartTime); + Assert.AreEqual(0, mid.Results[1].Status); + + AssertEqualPackages(package, mid); + } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("ByteArray")] + public void Mid0067ByteRevision1() + { + string package = "00820067001 0200000000012018-04-25:10:23:45100000000022018-04-25:10:24:010"; + byte[] bytes = GetAsciiBytes(package); + var mid = _midInterpreter.Parse(bytes); + + Assert.AreEqual(typeof(Mid0067), mid.GetType()); + Assert.AreEqual(2, mid.NumberOfResults); + Assert.AreEqual(2, mid.Results.Count); + Assert.AreEqual(1, mid.Results[0].Index); + Assert.AreEqual(2, mid.Results[1].Index); + AssertEqualPackages(bytes, mid); + } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("ASCII")] + public void Mid0067EmptyListRevision1() + { + string package = "00220067001 00"; + var mid = _midInterpreter.Parse(package); + + Assert.AreEqual(0, mid.NumberOfResults); + Assert.AreEqual(0, mid.Results.Count); + AssertEqualPackages(package, mid); + } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0067PackRevision1() + { + string package = "00820067001 0200000000012018-04-25:10:23:45100000000022018-04-25:10:24:010"; + + AssertBuildAndParse(package, new Mid0067() + { + Results = new List() + { + new ResultData() + { + Index = 1, + StartTime = new DateTime(2018, 4, 25, 10, 23, 45), + Status = 1 + }, + new ResultData() + { + Index = 2, + StartTime = new DateTime(2018, 4, 25, 10, 24, 1), + Status = 0 + } + } + }); + } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0067PackEnforcesNumberOfResultsRevision1() + { + string package = "00520067001 0100000000012018-04-25:10:23:451"; + var mid = new Mid0067() + { + NumberOfResults = 99, //Overwritten by the actual list size while packing + Results = new List() + { + new ResultData() + { + Index = 1, + StartTime = new DateTime(2018, 4, 25, 10, 23, 45), + Status = 1 + } + } + }; + + Assert.AreEqual(package, mid.Pack()); + Assert.AreEqual(1, mid.NumberOfResults); + } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("ASCII")] + public void Mid0067ExtraDataRequestRevision1() + { + string package = "00420006001 0067001130000000001010"; + var mid = _midInterpreter.Parse(package); + + var extraData = _midInterpreter.ParseExtraData(mid); + + Assert.AreEqual(Mid0006.MID, mid.Header.Mid); + Assert.AreEqual(Mid0067.MID, mid.RequestedMid); + Assert.AreEqual(1, mid.WantedRevision); + Assert.AreEqual(13, mid.ExtraDataLength); + Assert.AreEqual(1, extraData.StartIndex); + Assert.AreEqual(10, extraData.Count); + AssertEqualPackages(package, mid); + } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("ByteArray")] + public void Mid0067ExtraDataRequestByteRevision1() + { + string package = "00420006001 0067001130000000001010"; + byte[] bytes = GetAsciiBytes(package); + var mid = _midInterpreter.Parse(bytes); + + var extraData = _midInterpreter.ParseExtraData(mid); + + Assert.AreEqual(Mid0067.MID, mid.RequestedMid); + Assert.AreEqual(13, mid.ExtraDataLength); + Assert.AreEqual(1, extraData.StartIndex); + Assert.AreEqual(10, extraData.Count); + AssertEqualPackages(bytes, mid); + } + + [TestMethod] + [TestCategory("Revision 1"), TestCategory("Pack")] + public void Mid0067ExtraDataRequestPackRevision1() + { + string package = "00420006001 0067001130000000001010"; + var mid = new Mid0006(); + + mid.SetExtraData(new Mid0067ExtraData() + { + StartIndex = 1, + Count = 10 + }); + + Assert.AreEqual(Mid0067.MID, mid.RequestedMid); + Assert.AreEqual(13, mid.ExtraDataLength); + Assert.AreEqual("0000000001010", mid.ExtraData); + AssertEqualPackages(package, mid); + } + } +} diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0067.cs b/src/OpenProtocolInterpreter/Tightening/Mid0067.cs new file mode 100644 index 00000000..7f14d631 --- /dev/null +++ b/src/OpenProtocolInterpreter/Tightening/Mid0067.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OpenProtocolInterpreter.Tightening +{ + /// + /// Tightening Result List Upload + /// This message contains a list of tightening results stored in the controller. The result list is sorted ascendingly on result index, and contains a brief summary of each result. + /// shall be used for fetching this message + /// For full results data, request upload of + /// + public class Mid0067 : Mid, ITightening, IController + { + public const int MID = 67; + + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 2, HasPrefix = false)] + public int NumberOfResults { get; set; } + + [ResultDataCollectionDefinition(revision: 1, field: 2, Index = 22, Size = 0, HasPrefix = false)] + public List Results { get; set; } = new List(); + + public Mid0067() : this(new Header() + { + Mid = MID, + Revision = DEFAULT_REVISION + }) + { + + } + + public Mid0067(Header header) : base(header) + { + } + + protected override void ProcessDataField(DataField dataField, ReadOnlySpan package) + { + if (dataField.Field == 2) //Results + { + dataField.Size = NumberOfResults * 30; + } + + base.ProcessDataField(dataField, package); + } + + public override string Pack() + { + NumberOfResults = Results?.Count ?? 0; //Enforce list size even if modified + GetField(nameof(Results)).Size = NumberOfResults * 30; + return base.Pack(); + } + } + + public class Mid0067ExtraData : ExtraData, IExtraDataRequest + { + public override int Mid => Mid0067.MID; + + [Int64DataFieldDefinition(revision: 1, field: 1, Index = 0, Size = 10, HasPrefix = false)] + public long StartIndex { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 10, Size = 3, HasPrefix = false)] + public int Count { get; set; } + } +} diff --git a/src/OpenProtocolInterpreter/Tightening/ResultData.cs b/src/OpenProtocolInterpreter/Tightening/ResultData.cs new file mode 100644 index 00000000..c2397f7c --- /dev/null +++ b/src/OpenProtocolInterpreter/Tightening/ResultData.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace OpenProtocolInterpreter.Tightening +{ + public class ResultData + { + public long Index { get; set; } + public DateTime StartTime { get; set; } + public int Status { get; set; } + + public string Pack() + { + return OpenProtocolConvert.ToString('0', 10, PaddingOrientation.LeftPadded, Index) + + OpenProtocolConvert.ToString(StartTime) + + OpenProtocolConvert.ToString('0', 1, PaddingOrientation.LeftPadded, Status); + } + + public static ResultData Parse(string value) + => Parse(value.AsSpan()); + + public static ResultData Parse(ReadOnlySpan value) + { + return new ResultData() + { + Index = OpenProtocolConvert.ToInt64(value.Slice(0, 10)), + StartTime = OpenProtocolConvert.ToDateTime(value.Slice(10, 19)), + Status = OpenProtocolConvert.ToInt32(value.Slice(29, 1)) + }; + } + + public static IEnumerable ParseAll(string value) + => ParseAll(value.AsSpan()); + + public static IEnumerable ParseAll(ReadOnlySpan value) + { + var result = new List(); + const int sectionSize = 30; + for (int i = 0; i < value.Length; i += sectionSize) + result.Add(Parse(value.Slice(i, sectionSize))); + return result; + } + } + + public class ResultDataCollectionDefinitionAttribute : DataFieldDefinitionAttribute + { + public ResultDataCollectionDefinitionAttribute(int revision) : base(revision) + { + + } + public ResultDataCollectionDefinitionAttribute(int field, int revision) : base(field, revision) + { + + } + + internal override DataField Build(object owner, PropertyInfo propertyInfo, int index) + { + return new DataField>(Field, index, Size, HasPrefix) + { + DefaultConverter = Pack, + DefaultParser = Parse + }.Bind(owner, propertyInfo); + } + + private static string Pack(char paddingChar, int size, PaddingOrientation orientation, List value) + { + var builder = new StringBuilder(value.Count * 30); + foreach (var v in value) + builder.Append(v.Pack()); + + return builder.ToString(); + } + + private static List Parse(string value) + => ResultData.ParseAll(value).ToList(); + } +} diff --git a/src/OpenProtocolInterpreter/Tightening/TighteningMessages.cs b/src/OpenProtocolInterpreter/Tightening/TighteningMessages.cs index cf300347..67566bf4 100644 --- a/src/OpenProtocolInterpreter/Tightening/TighteningMessages.cs +++ b/src/OpenProtocolInterpreter/Tightening/TighteningMessages.cs @@ -20,8 +20,19 @@ public TighteningMessages() : base() { Mid0064.MID, new CompiledInstance(typeof(Mid0064)) }, { Mid0065.MID, new CompiledInstance(typeof(Mid0065)) }, { Mid0066.MID, new CompiledInstance(typeof(Mid0066)) }, + { Mid0067.MID, new CompiledInstance(typeof(Mid0067)) }, { Mid0902.MID, new CompiledInstance(typeof(Mid0902)) } }; + + _extraDataTemplates = new Dictionary>>() + { + { + Mid0067.MID, new List>() + { + new CompiledInstance(typeof(Mid0067ExtraData)) + } + } + }; } public TighteningMessages(IEnumerable selectedMids) : this() From 90c746a5abd6705b25d193d53dbd18ad7fd7fd2e Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Fri, 7 Aug 2026 02:16:30 -0300 Subject: [PATCH 71/78] every list initialized on constructor --- .../TestCollectionInitialization.cs | 93 +++++++++++++++++++ .../IOInterface/Mid0215.cs | 4 +- .../Job/Advanced/Mid0140.cs | 2 +- src/OpenProtocolInterpreter/Job/Mid0031.cs | 2 +- src/OpenProtocolInterpreter/Job/Mid0033.cs | 2 +- .../MultiSpindle/Mid0091.cs | 2 +- .../MultiSpindle/Mid0101.cs | 2 +- .../ParameterSet/Mid0011.cs | 2 +- .../ParameterSet/Mid2505.cs | 2 +- .../PowerMACS/Mid0106.cs | 4 +- .../PowerMACS/Mid0107.cs | 6 +- src/OpenProtocolInterpreter/Result/Mid1201.cs | 4 +- src/OpenProtocolInterpreter/Result/Mid1202.cs | 2 +- .../Tightening/Mid0061.cs | 3 +- .../Tightening/Mid0065.cs | 4 +- .../Tightening/Mid0066.cs | 1 - .../Tightening/Mid0902.cs | 2 +- src/OpenProtocolInterpreter/Tool/Mid0701.cs | 2 +- src/OpenProtocolInterpreter/Tool/Mid0702.cs | 2 +- src/OpenProtocolInterpreter/Tool/Mid0703.cs | 2 +- src/OpenProtocolInterpreter/Tool/Mid0704.cs | 7 +- 21 files changed, 122 insertions(+), 28 deletions(-) create mode 100644 src/MIDTesters.Core/TestCollectionInitialization.cs diff --git a/src/MIDTesters.Core/TestCollectionInitialization.cs b/src/MIDTesters.Core/TestCollectionInitialization.cs new file mode 100644 index 00000000..8ef9bad9 --- /dev/null +++ b/src/MIDTesters.Core/TestCollectionInitialization.cs @@ -0,0 +1,93 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace MIDTesters +{ + /// + /// Every and holding a collection must hand it back already + /// initialized, so callers may add items to a freshly built instance and packing never dereferences null. + /// + [TestClass] + [TestCategory("Default")] + public class TestCollectionInitialization + { + [TestMethod] + public void EveryMidInitializesItsCollections() + => AssertCollectionsAreInitialized(typeof(Mid)); + + [TestMethod] + public void EveryExtraDataInitializesItsCollections() + => AssertCollectionsAreInitialized(typeof(ExtraData)); + + private static void AssertCollectionsAreInitialized(Type baseType) + { + var failures = new List(); + var types = baseType.Assembly.GetTypes() + .Where(x => x.IsPublic && x.IsClass && !x.IsAbstract && baseType.IsAssignableFrom(x)) + .OrderBy(x => x.FullName); + + foreach (var type in types) + { + var collections = GetCollectionProperties(type).ToList(); + if (collections.Count == 0) + continue; + + foreach (var constructor in type.GetConstructors()) + { + if (!TryBuildArguments(constructor, out var arguments)) + continue; + + var instance = constructor.Invoke(arguments); + foreach (var property in collections.Where(x => x.GetValue(instance) == null)) + { + failures.Add($"{type.Name}.{property.Name} is null when built by {Describe(constructor)}"); + } + } + } + + Assert.AreEqual(0, failures.Count, + $"{failures.Count} collection(s) are left uninitialized:{Environment.NewLine}{string.Join(Environment.NewLine, failures)}"); + } + + private static IEnumerable GetCollectionProperties(Type type) + { + return type.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(x => x.CanRead && x.CanWrite + && x.PropertyType != typeof(string) + && typeof(IEnumerable).IsAssignableFrom(x.PropertyType)); + } + + /// + /// Mids and extra datas are built either with no arguments, from a or from a + /// revision number. Any other constructor is skipped instead of guessing a value for it. + /// + private static bool TryBuildArguments(ConstructorInfo constructor, out object[] arguments) + { + var parameters = constructor.GetParameters(); + arguments = new object[parameters.Length]; + + for (int i = 0; i < parameters.Length; i++) + { + if (parameters[i].ParameterType == typeof(Header)) + arguments[i] = new Header(); + else if (parameters[i].ParameterType == typeof(int)) + arguments[i] = 1; + else + return false; + } + + return true; + } + + private static string Describe(ConstructorInfo constructor) + { + var parameters = constructor.GetParameters().Select(x => x.ParameterType.Name); + return $"{constructor.DeclaringType.Name}({string.Join(", ", parameters)})"; + } + } +} diff --git a/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs b/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs index c0c7c93b..c2d2765d 100644 --- a/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs +++ b/src/OpenProtocolInterpreter/IOInterface/Mid0215.cs @@ -29,11 +29,11 @@ public class Mid0215 : Mid, IIOInterface, IController [RelayCollectionDefinition(revision: 1, field: 2, Index = 24, Size = 4 * 8)] [RelayCollectionDefinition(revision: 2, field: 3, Index = 28, Size = 4 * 8)] - public List Relays { get; set; } + public List Relays { get; set; } = new List(); [DigitalInputCollectionDefinition(revision: 1, field: 3, Index = 58, Size = 4 * 8)] [DigitalInputCollectionDefinition(revision: 2, field: 5, Index = 0, Size = 4 * 8)] - public List DigitalInputs { get; set; } + public List DigitalInputs { get; set; } = new List(); //At revision 2 number of relays/digital inputs comes before their lists [Int32DataFieldDefinition(revision: 2, field: 2, Index = 24, Size = 2)] diff --git a/src/OpenProtocolInterpreter/Job/Advanced/Mid0140.cs b/src/OpenProtocolInterpreter/Job/Advanced/Mid0140.cs index bf7ba25f..5322ca14 100644 --- a/src/OpenProtocolInterpreter/Job/Advanced/Mid0140.cs +++ b/src/OpenProtocolInterpreter/Job/Advanced/Mid0140.cs @@ -48,7 +48,7 @@ public class Mid0140 : Mid, IAdvancedJob, IIntegrator, IAcceptableCommand [AdvancedJobCollectionDefinition(revision: 3, field: 4, Index = 57, Size = 0)] [AdvancedJobCollectionDefinition(revision: 4, field: 4, Index = 57, Size = 0)] [AdvancedJobCollectionDefinition(revision: 999, field: 4, Index = 57, Size = 0)] - public List JobList { get; set; } + public List JobList { get; set; } = new List(); [Int32DataFieldDefinition(revision: 1, field: 5, Index = 0, Size = 1)] [Int32DataFieldDefinition(revision: 2, field: 5, Index = 0, Size = 1)] diff --git a/src/OpenProtocolInterpreter/Job/Mid0031.cs b/src/OpenProtocolInterpreter/Job/Mid0031.cs index 7e7a6501..ccaed4f4 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0031.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0031.cs @@ -24,7 +24,7 @@ public class Mid0031 : Mid, IJob, IController [Int32CollectionDefinition(revision: 1, field: 2, Index = 22, Size = 2, EachFieldSize = 2, HasPrefix = false)] [Int32CollectionDefinition(revision: 2, field: 2, Index = 24, Size = 4, EachFieldSize = 4, HasPrefix = false)] - public List JobIds { get; set; } + public List JobIds { get; set; } = new List(); public Mid0031() : this(DEFAULT_REVISION) { diff --git a/src/OpenProtocolInterpreter/Job/Mid0033.cs b/src/OpenProtocolInterpreter/Job/Mid0033.cs index 74a0dd62..65aa47a6 100644 --- a/src/OpenProtocolInterpreter/Job/Mid0033.cs +++ b/src/OpenProtocolInterpreter/Job/Mid0033.cs @@ -68,7 +68,7 @@ public class Mid0033 : Mid, IJob, IController [ParameterSetCollectionDefinition(revision: 3, field: 13, Index = 91, Size = 0)] [ParameterSetCollectionDefinition(revision: 4, field: 13, Index = 91, Size = 0)] [ParameterSetCollectionDefinition(revision: 5, field: 13, Index = 91, Size = 0)] - public List ParameterSetList { get; set; } + public List ParameterSetList { get; set; } = new List(); public Mid0033() : this(DEFAULT_REVISION) { diff --git a/src/OpenProtocolInterpreter/MultiSpindle/Mid0091.cs b/src/OpenProtocolInterpreter/MultiSpindle/Mid0091.cs index 3c9be1b6..14b9162a 100644 --- a/src/OpenProtocolInterpreter/MultiSpindle/Mid0091.cs +++ b/src/OpenProtocolInterpreter/MultiSpindle/Mid0091.cs @@ -26,7 +26,7 @@ public class Mid0091 : Mid, IMultiSpindle, IController, IAcknowledgeable SpindlesStatus { get; set; } + public List SpindlesStatus { get; set; } = new List(); public Mid0091() : this(new Header() { diff --git a/src/OpenProtocolInterpreter/MultiSpindle/Mid0101.cs b/src/OpenProtocolInterpreter/MultiSpindle/Mid0101.cs index cdd1a077..293e887f 100644 --- a/src/OpenProtocolInterpreter/MultiSpindle/Mid0101.cs +++ b/src/OpenProtocolInterpreter/MultiSpindle/Mid0101.cs @@ -59,7 +59,7 @@ public class Mid0101 : Mid, IMultiSpindle, IController, IAcknowledgeable SpindlesOrPressesStatus { get; set; } + public List SpindlesOrPressesStatus { get; set; } = new List(); [Int32DataFieldDefinition(revision: 4, field: 19, Index = 0, Size = 3)] public SystemSubType SystemSubType { get; set; } [Int32DataFieldDefinition(revision: 5, field: 20, Index = 0, Size = 5)] diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid0011.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid0011.cs index 70312f97..14f538e1 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid0011.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid0011.cs @@ -23,7 +23,7 @@ public class Mid0011 : Mid, IParameterSet, IController public int TotalParameterSets { get; set; } [Int32CollectionDefinition(revision: 1, field: 2, Index = 23, EachFieldSize = 3, HasPrefix = false)] - public List ParameterSets { get; set; } + public List ParameterSets { get; set; } = new List(); public Mid0011() : this(new Header() { diff --git a/src/OpenProtocolInterpreter/ParameterSet/Mid2505.cs b/src/OpenProtocolInterpreter/ParameterSet/Mid2505.cs index 3c4049ce..6b36504d 100644 --- a/src/OpenProtocolInterpreter/ParameterSet/Mid2505.cs +++ b/src/OpenProtocolInterpreter/ParameterSet/Mid2505.cs @@ -30,7 +30,7 @@ public class Mid2505 : Mid, IParameterSet, IIntegrator, IAcceptableCommand, IDec public int NumberOfParameterDataFields { get; set; } [VariableDataFieldCollectionDefinition(revision: 1, field: 3, Index = 26, HasPrefix = false)] - public List VariableDataFields { get; set; } + public List VariableDataFields { get; set; } = new List(); public Mid2505() : this(DEFAULT_REVISION) { diff --git a/src/OpenProtocolInterpreter/PowerMACS/Mid0106.cs b/src/OpenProtocolInterpreter/PowerMACS/Mid0106.cs index 1867250e..e1bc415b 100644 --- a/src/OpenProtocolInterpreter/PowerMACS/Mid0106.cs +++ b/src/OpenProtocolInterpreter/PowerMACS/Mid0106.cs @@ -70,7 +70,7 @@ public class Mid0106 : Mid, IPowerMACS, IController, IAcknowledgeable public int NumberOfBolts { get; set; } [BoltDataCollectionDefinition(revision: 1, field: 13, Index = 165, HasPrefix = false)] - public List BoltsData { get; set; } + public List BoltsData { get; set; } = new List(); //Total Special values has a weird pattern of being attached to the list of special values instead of being a separate field. //So we do process is in a separate way with special values and set its value and have to do it together with list because each special value @@ -78,7 +78,7 @@ public class Mid0106 : Mid, IPowerMACS, IController, IAcknowledgeable public int TotalSpecialValues { get; set; } [SpecialValueCollectionDefinition(revision: 1, field: 23, Index = 0, Size = 67)] - public List SpecialValues { get; set; } + public List SpecialValues { get; set; } = new List(); [Int32DataFieldDefinition(revision: 4, field: 24, Index = 0, Size = 3)] public SystemSubType SystemSubType { get; set; } diff --git a/src/OpenProtocolInterpreter/PowerMACS/Mid0107.cs b/src/OpenProtocolInterpreter/PowerMACS/Mid0107.cs index b6882497..84bca407 100644 --- a/src/OpenProtocolInterpreter/PowerMACS/Mid0107.cs +++ b/src/OpenProtocolInterpreter/PowerMACS/Mid0107.cs @@ -69,7 +69,7 @@ public class Mid0107 : Mid, IPowerMACS, IController, IAcknowledgeable public int NumberOfBoltResults { get; set; } [BoltResultCollectionDefinition(revision: 1, field: 12, Index = 180, Size = 0, HasPrefix = false)] - public List BoltResults { get; set; } + public List BoltResults { get; set; } = new List(); [Int32DataFieldDefinition(revision: 1, field: 13, Index = 0, Size = 3)] public int NumberOfStepResults { get; set; } @@ -78,7 +78,7 @@ public class Mid0107 : Mid, IPowerMACS, IController, IAcknowledgeable public bool AllStepDataSent { get; set; } [StepResultCollectionDefinition(revision: 1, field: 14, Index = 0, Size = 0, HasPrefix = false)] - public List StepResults { get; set; } + public List StepResults { get; set; } = new List(); //Total Special values has a weird pattern of being attached to the list of special values instead of being a separate field. //So we do process is in a separate way with special values and set its value and have to do it together with list because each special value @@ -86,7 +86,7 @@ public class Mid0107 : Mid, IPowerMACS, IController, IAcknowledgeable public int NumberOfSpecialValues { get; set; } [SpecialValueCollectionDefinition(revision: 1, field: 15, Index = 0, UseStepNumber = true)] - public List SpecialValues { get; set; } + public List SpecialValues { get; set; } = new List(); [Int32DataFieldDefinition(revision: 4, field: 16, Index = 0, Size = 3)] public SystemSubType SystemSubType { get; set; } diff --git a/src/OpenProtocolInterpreter/Result/Mid1201.cs b/src/OpenProtocolInterpreter/Result/Mid1201.cs index 7e4d6b6b..65071295 100644 --- a/src/OpenProtocolInterpreter/Result/Mid1201.cs +++ b/src/OpenProtocolInterpreter/Result/Mid1201.cs @@ -66,7 +66,7 @@ public class Mid1201 : Mid, IResult, IController, IAcknowledgeable, IAc [ObjectDataCollectionDefinition(revision: 1, field: 8, Index = 61, Size = 0, HasPrefix = false)] [ObjectDataCollectionDefinition(revision: 2, field: 9, Index = 65, Size = 0, HasPrefix = false)] [ObjectDataCollectionDefinition(revision: 3, field: 9, Index = 65, Size = 0, HasPrefix = false)] - public List ObjectDataList { get; set; } + public List ObjectDataList { get; set; } = new List(); [Int32DataFieldDefinition(revision: 1, field: 9, Index = 0, Size = 3, HasPrefix = false)] [Int32DataFieldDefinition(revision: 2, field: 10, Index = 0, Size = 3, HasPrefix = false)] @@ -76,7 +76,7 @@ public class Mid1201 : Mid, IResult, IController, IAcknowledgeable, IAc [VariableDataFieldCollectionDefinition(revision: 1, field: 10, Index = 0, Size = 0, HasPrefix = false)] [VariableDataFieldCollectionDefinition(revision: 2, field: 11, Index = 0, Size = 0, HasPrefix = false)] [VariableDataFieldCollectionDefinition(revision: 3, field: 11, Index = 0, Size = 0, HasPrefix = false)] - public List VariableDataFields { get; set; } + public List VariableDataFields { get; set; } = new List(); public Mid1201() : this(DEFAULT_REVISION) { diff --git a/src/OpenProtocolInterpreter/Result/Mid1202.cs b/src/OpenProtocolInterpreter/Result/Mid1202.cs index 5e909d96..6d07cc26 100644 --- a/src/OpenProtocolInterpreter/Result/Mid1202.cs +++ b/src/OpenProtocolInterpreter/Result/Mid1202.cs @@ -53,7 +53,7 @@ public class Mid1202 : Mid, IResult, IController, IAcknowledgeable, IAc [VariableDataFieldCollectionDefinition(revision: 1, field: 6, Index = 43, Size = 0, HasPrefix = false)] [VariableDataFieldCollectionDefinition(revision: 2, field: 7, Index = 79, Size = 0, HasPrefix = false)] - public List VariableDataFields { get; set; } + public List VariableDataFields { get; set; } = new List(); public Mid1202() : this(new Header() { diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0061.cs b/src/OpenProtocolInterpreter/Tightening/Mid0061.cs index aaa04941..30b2ccbf 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0061.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0061.cs @@ -306,7 +306,7 @@ public class Mid0061 : Mid, ITightening, IController, IAcknowledgeable public int NumberOfStageResults { get; set; } [StageResultCollectionDefinition(revision: 998, field: 58, Index = 534, Size = 0)] - public List StageResults { get; set; } + public List StageResults { get; set; } = new List(); public Mid0061() : this(DEFAULT_REVISION) { @@ -315,6 +315,7 @@ public Mid0061() : this(DEFAULT_REVISION) public Mid0061(Header header) : base(header) { + } public Mid0061(int revision) : this(new Header() diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0065.cs b/src/OpenProtocolInterpreter/Tightening/Mid0065.cs index d21c6f56..4f39ab4c 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0065.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0065.cs @@ -222,7 +222,7 @@ public class Mid0065 : Mid, ITightening, IController public int NumberOfStageResults { get; set; } [StageResultCollectionDefinition(revision: 998, field: 39, Index = 348, Size = 0)] - public List StageResults { get; set; } + public List StageResults { get; set; } = new List(); public Mid0065() : this(DEFAULT_REVISION) { @@ -231,7 +231,7 @@ public Mid0065() : this(DEFAULT_REVISION) public Mid0065(Header header) : base(header) { - + StageResults ??= []; } public Mid0065(int revision) : this(new Header() diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0066.cs b/src/OpenProtocolInterpreter/Tightening/Mid0066.cs index 71250f57..d1e4923a 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0066.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0066.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/src/OpenProtocolInterpreter/Tightening/Mid0902.cs b/src/OpenProtocolInterpreter/Tightening/Mid0902.cs index afbbad4c..f7965194 100644 --- a/src/OpenProtocolInterpreter/Tightening/Mid0902.cs +++ b/src/OpenProtocolInterpreter/Tightening/Mid0902.cs @@ -34,7 +34,7 @@ public class Mid0902 : Mid, ITightening, IController public int NumberOfPIDs { get; set; } [VariableDataFieldCollectionDefinition(revision: 1, field: 7, Index = 91, Size = 0, HasPrefix = false)] - public List VariableDataFields { get; set; } + public List VariableDataFields { get; set; } = new List(); public Mid0902() : this(new Header() { diff --git a/src/OpenProtocolInterpreter/Tool/Mid0701.cs b/src/OpenProtocolInterpreter/Tool/Mid0701.cs index 5918130f..030f2836 100644 --- a/src/OpenProtocolInterpreter/Tool/Mid0701.cs +++ b/src/OpenProtocolInterpreter/Tool/Mid0701.cs @@ -21,7 +21,7 @@ public class Mid0701 : Mid, ITool, IController public int TotalTools { get; set; } [ToolDataCollectionDefinition(revision: 1, field: 2, Index = 23, Size = 0, HasPrefix = false)] - public List Tools { get; set; } + public List Tools { get; set; } = new List(); public Mid0701() : this(new Header() { diff --git a/src/OpenProtocolInterpreter/Tool/Mid0702.cs b/src/OpenProtocolInterpreter/Tool/Mid0702.cs index 1cbffd92..2535c76d 100644 --- a/src/OpenProtocolInterpreter/Tool/Mid0702.cs +++ b/src/OpenProtocolInterpreter/Tool/Mid0702.cs @@ -20,7 +20,7 @@ public class Mid0702 : Mid, ITool, IController public int NumberOfToolPIDs { get; set; } [VariableDataFieldCollectionDefinition(revision: 1, field: 2, Index = 23, Size = 0, HasPrefix = false)] - public List ToolDataUpload { get; set; } + public List ToolDataUpload { get; set; } = new List(); public Mid0702() : this(new Header() { diff --git a/src/OpenProtocolInterpreter/Tool/Mid0703.cs b/src/OpenProtocolInterpreter/Tool/Mid0703.cs index 8f9184d7..509303d3 100644 --- a/src/OpenProtocolInterpreter/Tool/Mid0703.cs +++ b/src/OpenProtocolInterpreter/Tool/Mid0703.cs @@ -28,7 +28,7 @@ public class Mid0703 : Mid, ITool, IIntegrator, IAcceptableCommand, IDeclinableC public int NumberOfCalibrationParameters { get; set; } [VariableDataFieldCollectionDefinition(revision: 1, field: 3, Index = 28, Size = 0, HasPrefix = false)] - public List CalibrationParameters { get; set; } + public List CalibrationParameters { get; set; } = new List(); public Mid0703() : this(new Header() { diff --git a/src/OpenProtocolInterpreter/Tool/Mid0704.cs b/src/OpenProtocolInterpreter/Tool/Mid0704.cs index 340fd504..c4a13cd4 100644 --- a/src/OpenProtocolInterpreter/Tool/Mid0704.cs +++ b/src/OpenProtocolInterpreter/Tool/Mid0704.cs @@ -25,7 +25,7 @@ public class Mid0704 : Mid, ITool, IController public int NumberOfDataFields { get; set; } [VariableDataFieldCollectionDefinition(revision: 1, field: 2, Index = 23, Size = 0, HasPrefix = false)] - public List VariableDataFields { get; set; } + public List VariableDataFields { get; set; } = new List(); public Mid0704() : this(new Header() { @@ -38,6 +38,7 @@ public Mid0704() : this(new Header() public Mid0704(Header header) : base(header) { + VariableDataFields ??= []; } public override string Pack() @@ -73,7 +74,7 @@ public class Mid0704ExtraDataRequest : ExtraData, IExtraDataRequest public int TotalRequestedPIDs { get; set; } [Int32CollectionDefinition(revision: 1, field: 3, Index = 7, Size = 0, HasPrefix = false, EachFieldSize = PID_SIZE)] - public List RequestedPIDs { get; set; } + public List RequestedPIDs { get; set; } = new List(); public Mid0704ExtraDataRequest() { @@ -124,7 +125,7 @@ public class Mid0704ExtraDataSubscription : ExtraData, IExtraDataSubscription public int NumberOfPIDs { get; set; } [PIDRestrictionCollectionDefinition(revision: 1, field: 3, Index = 7, Size = 0, HasPrefix = false)] - public List PIDRestrictions { get; set; } + public List PIDRestrictions { get; set; } = new List(); public Mid0704ExtraDataSubscription() { From c10314812985e80f1e2bc603e3093e600534c112 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Fri, 7 Aug 2026 02:16:39 -0300 Subject: [PATCH 72/78] updated README --- README.md | 358 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 250 insertions(+), 108 deletions(-) diff --git a/README.md b/README.md index b881d059..3a3a0d99 100644 --- a/README.md +++ b/README.md @@ -10,16 +10,28 @@ 1. [What is Open Protocol at all?](#what-is-open-protocol-at-all) 2. [What is OpenProtocolInterpreter?](#what-is-openprotocolinterpreter) - 3. [How does it work?](#how-does-it-work) - 4. [Usage examples](#lets-see-some-examples-of-usage) - 5. [Available on package managers](#get-it-on-nuget) - 6. [Advanced section](#advanced-section) + 3. [Get it on NuGet](#get-it-on-nuget) + 4. [How does it work?](#how-does-it-work) + 5. [Usage examples](#lets-see-some-examples-of-usage) + * [Parsing a package](#parsing-a-package) + * [Packing a MID](#packing-a-mid) + * [Replies and acknowledges](#replies-and-acknowledges) + 6. [Anatomy of a MID](#anatomy-of-a-mid) + * [Data field attributes](#data-field-attributes) + * [Available attributes](#available-attributes) + * [Revisions](#revisions) + 7. [Advanced section](#advanced-section) + * [MIDs identifying customization](#mids-identifying-customization) + * [Extra data (MID 0006, 0008 and 0009)](#extra-data-mid-0006-0008-and-0009) + * [MIDs overriding](#mids-overriding) + * [Adding MIDs that are not in documentation](#adding-mids-that-are-not-in-documentation) * [How it was built?](#how-it-was-built) - * [Customization](#mids-identifying-customization) * [Advanced example](#advanced-example) -7. [Tips](#tips) -8. [Contribute to the project](#contribute-to-the-project) -9. [Still unavailable mids](#list-of-still-unavailable-mids) + 8. [Tips](#tips) + 9. [Supported frameworks](#supported-frameworks) +10. [Contribute to the project](#contribute-to-the-project) +11. [Sponsor the project](#sponsor-the-project) +12. [Still unavailable mids](#list-of-still-unavailable-mids) ## What is Open Protocol at all? @@ -36,6 +48,11 @@ OpenProtocolInterpreter is a **library that converts the ugly string** that came **[If you're curious, just take a look at their documentation.](docs/OpenProtocol_Specification.pdf)** +## Get it on [NuGet](https://www.nuget.org/packages/OpenProtocolInterpreter)! +``` +Install-Package OpenProtocolInterpreter +``` + ## How does it work? It's simple, you give us your byte[] or string package and we deliver you an object, simple as that! @@ -48,42 +65,124 @@ It's **MID 5**, so OpenProtocolIntepreter will return a **Mid0005** class for yo ## Let's see some examples of usage -A simple usage: +### Parsing a package + +`MidInterpreter` starts empty: you must tell it which MIDs it should know about before parsing anything. +`UseAllMessages()` is the "give me everything" shortcut — see [MIDs identifying customization](#mids-identifying-customization) to narrow it down. ``` csharp -var interpreter = new MidInterpreter(); -var midPackage = @"00260004001 001802"; +var interpreter = new MidInterpreter().UseAllMessages(); + +var midPackage = "00260004001 001802"; var myMid04 = interpreter.Parse(midPackage); -//MID 0004 is an error mid which contains which MID Failed and its error code -//Int value of the Failed Mid -int myFailedMid = myMid04.FailedMid; -//An enum with Error Code -Error errorCode = myMid04.ErrorCode; + +//MID 0004 is an error mid which contains which MID failed and its error code +int myFailedMid = myMid04.FailedMid; // 18 +Error errorCode = myMid04.ErrorCode; // Error.ParameterSetIdNotPresent +``` + +Everything a MID carries is a plain, settable property, so you can read it straight away. +`byte[]` packages are accepted by the very same overloads: + +``` csharp +byte[] rawPackage = socket.Receive(); +var mid = interpreter.Parse(rawPackage); // returns Mid, use it when you don't know what is coming +var mid04 = interpreter.Parse(rawPackage); ``` +> Use `Parse(package)` when you don't know which MID will arrive and `Parse(package)` when you do. +> The generic overload throws `InvalidCastException` when the package turns out to be another MID. + +### Packing a MID + It can generate an object from a string, but can it make it to the other way?? FOR SURE! + ``` csharp -var jobUploadRequest = new Mid0032(1, 2); //Job id 1, revision 2 +var jobUploadRequest = new Mid0032(revision: 2) { JobId = 1 }; var package = jobUploadRequest.Pack(); //Generated package => 00240032002 0001 + +var bytes = jobUploadRequest.PackBytes(); // same content, ASCII encoded +var terminated = jobUploadRequest.PackWithNul(); // appends the NUL character some controllers expect ``` -## Get it on [NuGet](https://www.nuget.org/packages/OpenProtocolInterpreter)! +The header length is recalculated for you on every `Pack()`, based on the revision in `Header.Revision`. +Every MID exposes a parameterless constructor (revision 1) and a `Mid(Header header)` constructor; the ones that +have more than one documented revision also expose `Mid(int revision)`. + +### Replies and acknowledges + +MIDs advertise their relationships through marker interfaces, so it might help you to remember which MID answers which or even give a shortcut for you: + +``` csharp +Mid0062 ack = new Mid0061(2).GetAcknowledge(); // IAcknowledgeable, keeps the source revision +Mid0033 reply = new Mid0032(2).GetReply(); // IAnswerableBy ``` -Install-Package OpenProtocolInterpreter + +## Anatomy of a MID + +### Data field attributes + +A MID has **each property is decorated with a data field attribute** describing where that value lives inside the package. Which also represents the string index/position of the package accordingly with Open Protocol documentation. E.g.: + +``` csharp +public class Mid0004 : Mid, ICommunication, IController +{ + public const int MID = 4; + + [Int32DataFieldDefinition(revision: 1, field: 1, Index = 20, Size = 4, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 2, field: 1, Index = 20, Size = 4, HasPrefix = false)] + public int FailedMid { get; set; } + + [Int32DataFieldDefinition(revision: 1, field: 2, Index = 24, Size = 2, HasPrefix = false)] + [Int32DataFieldDefinition(revision: 2, field: 2, Index = 24, Size = 3, HasPrefix = false)] + public Error ErrorCode { get; set; } + + public Mid0004() : this(DEFAULT_REVISION) { } + public Mid0004(Header header) : base(header) { } + public Mid0004(int revision) : this(new Header { Mid = MID, Revision = revision }) { } +} ``` +Every attribute accepts: + +| Member | Meaning | +| - | - | +| `revision` | Revision this definition belongs to. | +| `field` | Data field id/sequence/index inside the revision. | +| `Index` | Offset of the value inside the package, counted from the start of the package (the header takes the first 20 chars). | +| `Size` | Length of the value. Fixed automatically by some attributes that has fixed size (booleans, timestamps). | +| `HasPrefix` | `true` when the package carries the two digit field id right before the value. | +| `PaddingChar` | Char used to pad the value to its full size when packing. | +| `PaddingOrientation` | Defines if char filling `RightPadded` or `LeftPadded` | + +Parsing and packing both flow through the same definitions: on `Parse` the slice is read, converted and **written back into the property**; on `Pack` the property value is read, converted and written into the package. + +### Available attributes + +| Attribute | Property type | Notes | +| - | - | - | +| `BooleanDataFieldDefinition` | `bool` | `Size` forced to 1 | +| `StringDataFieldDefinition` | `string` | Defaultly right padded with `' '`| +| `Int32DataFieldDefinition` | `int`, `int`-backed `enum` | left padded with `'0'` | +| `Int64DataFieldDefinition` | `long`, `long`-backed `enum` | left padded with `'0'` | +| `DecimalDataFieldDefinition` | `decimal` | left padded with `'0'` | +| `TruncatedDecimalDataFieldDefinition` | `decimal` | `DecimalPoints` (default 2) implicit decimal places | +| `TimestampDataFieldDefinition` | `DateTime` | `Size` forced to 19, `YYYY-MM-DD:HH:MM:SS` | +| `Int32CollectionDefinition` | `List` | requires `EachFieldSize` | +| `EnumCollectionDefinition` | `List` where `T : Enum` | one char per entry | +| `VariableDataFieldCollectionDefinition` | `List` | the variable data fields block | + +### Revisions + +At parse/pack time only the definitions up to `Header.StandardizedRevision` are used so we can avoid confusion with revision `0` since when zero or blank it represents revision `1`. + ## Advanced Section! Now we will get real! Put one thing in mind, in real world we will always need to build something more complex than the dummies examples we give to you. **With this in mind, this section is for you:** -#### How it was built? - -It used to rely on **Chain Of Responsabilities** design pattern, but since we had some problems with instance references, it changed! -For now, instead of iterating through all Mids of the same category, it relies on a Dictionary, which every category knows which mid it attends, once it found it creates a new Instance via System.Reflection and parse it. - #### MIDs Identifying Customization We have several MIDs inside Open Protocol documentation, but do you really need all of them? @@ -91,29 +190,72 @@ The answer is... **NO!** You will probably need only to use a range of MIDs, with this in mind, we did something to make things faster. You can tell us which MIDs we should considerate! -> *NOTE: You can register only mids you need to call "Parse" method - Here is an example: ``` csharp -string package = "00260004001 001802"; var myCustomInterpreter = new MidInterpreter() - .UseAllMessages(new Type[] - { - typeof(Mid0001), - typeof(Mid0002), - typeof(Mid0003), - typeof(Mid0004), - typeof(Mid0106) - }); + .UseAllMessages(new Type[] + { + typeof(Mid0001), + typeof(Mid0002), + typeof(Mid0003), + typeof(Mid0004), + typeof(Mid0106) + }); + //Will work: -var myMid04 = myCustomInterpreter.Parse(package); -//Won't work, will throw NotImplementedException: -var myMid30 = myCustomInterpreter.Parse(package); -//Won't work, will throw InvalidCastException: -var myMid01 = myCustomInterpreter.Parse(package); +var myMid04 = myCustomInterpreter.Parse("00260004001 001802"); + +//Won't work, will throw NotImplementedException, MID 0030 was never registered: +var unknown = myCustomInterpreter.Parse("00220030001 01"); +``` + +You can also register a whole category at once, and filter it by your role in the communication: + +``` csharp +var interpreter = new MidInterpreter() + .UseCommunicationMessages(InterpreterMode.Integrator) + .UseTighteningMessages(InterpreterMode.Integrator) + .UseAlarmMessages(InterpreterMode.Integrator); +``` + +`InterpreterMode.Integrator` keeps only the MIDs a controller can send you, `InterpreterMode.Controller` keeps +only the ones an integrator can send, and `InterpreterMode.Both` (the default) keeps everything. +There is one `Use...Messages` extension per category, all of them accepting a mode, an `IEnumerable` or an +`IDictionary` (see [MIDs overriding](#mids-overriding)). + +#### Extra data (MID 0006, 0008 and 0009) + +MIDs 0006 (request), 0008 (subscription) and 0009 (unsubscription) carry a free form payload whose layout +depends on the MID being requested/subscribed. Those payloads are modelled by `ExtraData` classes, which use the very same data field attributes as a MID: + +``` csharp +var subscription = new Mid0008(); +subscription.SetExtraData(new Mid1201ExtraDataSubscription +{ + SendAlternatives = 1, + DataIdentifierTimestamp = new DateTime(2026, 8, 7, 13, 45, 0), + SendObjectData = true +}); + +var package = subscription.Pack(); +//Generated package => 00590008001 12010013000000000012026-08-07:13:45:001 ``` -When you don't know which package will come, use ``` Parse ``` overload, not ``` Parse ```. If you want, take a look at the sample on this repository. -> If necessary, there is a new overload where you can define if you're the controller or the integrator, which will automatically handle implemented mids + +`SetExtraData` fills `SubscriptionMid`, `WantedRevision`, `ExtraDataLength` and `ExtraData` for you. +On the receiving side, parse the container MID first and then hand its raw `ExtraData` to the matching class: + +``` csharp +var mid08 = interpreter.Parse(package); +var extraData = (Mid1201ExtraDataSubscription)new Mid1201ExtraDataSubscription(mid08.WantedRevision) + .Parse(mid08.ExtraData); + +int alternatives = extraData.SendAlternatives; +DateTime timestamp = extraData.DataIdentifierTimestamp; +``` + +A MID may declare one `ExtraData` per kind, since request (`IExtraDataRequest`), subscription (`IExtraDataSubscription`) and unsubscription (`IExtraDataUnsubscription`) payloads don't necessarily share the same content. + +> If necessary you can add `ExtraData` manually as plain string. #### MIDs Overriding @@ -123,8 +265,12 @@ so you can customize it and add more properties to handle some conversions. Anyw Here is an example: ``` csharp //This will override Mid 81 with my custom Mid -var _midInterpreter new MidInterpreter().UseAllMessages() - .UseTimeMessages(new Dictionary() { { 81, typeof(OverridedMid0081) } }); +var interpreter = new MidInterpreter() + .UseAllMessages() + .UseTimeMessages(new Dictionary { { Mid0081.MID, typeof(OverridedMid0081) } }); + +var mid = interpreter.Parse("00390081001 2026-08-07:13:45:00"); +//mid is an OverridedMid0081 public class OverridedMid0081 : Mid0081 @@ -139,65 +285,43 @@ public class OverridedMid0081 : Mid0081 { } - - public override string Pack() - { - Time = TestCustomMid.Now; - return base.Pack(); - } } ``` +> Your type must expose a parameterless constructor — that's the one the interpreter compiles and calls. + #### Adding MIDs that are not in documentation Maybe your controller is weird and have unknown MID numbers, MIDs that are not in the documentation and you want to inject into MidInterpreter, there is a way: ``` csharp -var _midInterpreter new MidInterpreter().UseAllMessages() - .UseCustomMessage(new Dictionary() { { 83, typeof(NewMid0083) } }); +var interpreter = new MidInterpreter() + .UseAllMessages() + .UseCustomMessage(new Dictionary { { NewMid0083.MID, typeof(NewMid0083) } }); public class NewMid0083 : Mid { - private readonly IValueConverter _dateConverter; - private const int LAST_REVISION = 1; public const int MID = 83; - public DateTime Time - { - get => GetField(1, (int)DataFields.TIME).GetValue(_dateConverter.Convert); - set => GetField(1, (int)DataFields.TIME).SetValue(_dateConverter.Convert, value); - } - public string TimeZone - { - get => GetField(1, (int)DataFields.TIMEZONE).Value; - set => GetField(1, (int)DataFields.TIMEZONE).SetValue(value); - } + [TimestampDataFieldDefinition(revision: 1, field: 1, Index = 20, HasPrefix = false)] + public DateTime Time { get; set; } - public NewMid0083() : base(MID, LAST_REVISION) - { - _dateConverter = new DateConverter(); - } + [StringDataFieldDefinition(revision: 1, field: 2, Index = 39, Size = 2, HasPrefix = false)] + public string TimeZone { get; set; } - protected override Dictionary> RegisterDatafields() + public NewMid0083() : base(MID, DEFAULT_REVISION) { - return new Dictionary>() - { - { - 1, new List() - { - new DataField((int)DataFields.TIME, 20, 19), - new DataField((int)DataFields.TIMEZONE, 41, 2) - } - } - }; + } - public enum DataFields + public NewMid0083(Header header) : base(header) { - TIME, - TIMEZONE + } } + +//new NewMid0083 { Time = new DateTime(2026, 8, 7, 13, 45, 0), TimeZone = "BR" }.Pack() +// => 00410083001 2026-08-07:13:45:00BR ``` > **NOTE:** Custom messages might not perform as fast as other MIDs because they don't have optimizations for finding it @@ -207,9 +331,9 @@ public class NewMid0083 : Mid Declared a delegate: ``` csharp -protected delegate void ReceivedCommandActionDelegate(ReceivedMIDEventArgs e); +protected delegate void ReceivedCommandActionDelegate(ReceivedMidEventArgs e); ``` -**ReceivedMIDEventArgs class**: +**ReceivedMidEventArgs class**: ``` csharp public class ReceivedMidEventArgs : EventArgs { @@ -243,58 +367,79 @@ protected void OnPackageReceived(string message) var mid = Interpreter.Parse(message); //Get Registered delegate for the MID that was identified - var action = OnReceivedMid.FirstOrDefault(x => x.Key == mid.GetType()); + if (!OnReceivedMid.TryGetValue(mid.GetType(), out var action)) + return; //Stop if there is no delegate registered for the message that arrived - if (action.Equals(default(KeyValuePair))) - return; //Stop if there is no delegate registered for the message that arrived - - action.Value(new ReceivedMidEventArgs() { ReceivedMid = mid }); //Call delegate - } - catch (Exception ex) - { - Console.log(ex.Message); - } + action(new ReceivedMidEventArgs { ReceivedMid = mid }); //Call delegate + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } } ``` This would call the registered delegate which you're sure what mid it is. -For example when a **MID_0061** (last tightening) pop up, the **onTighteningReceived** delegate will be called: +For example when a **MID_0061** (last tightening) pop up, the **OnTighteningReceived** delegate will be called: ``` csharp protected void OnTighteningReceived(ReceivedMidEventArgs e) { try { - Mid0061 tighteningMid = e.ReceivedMID as Mid0061; //Casting to the right mid + var tighteningMid = (Mid0061)e.ReceivedMid; //Casting to the right mid //This method just send the ack from tightening mid BuildAndSendAcknowledge(tighteningMid); - Console.log("TIGHTENING ARRIVED") - } - catch (Exception ex) - { - Console.log(ex.Message); - } + Console.WriteLine("TIGHTENING ARRIVED"); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } } -protected void BuildAndSendAcknowledge(Mid mid) +protected void BuildAndSendAcknowledge(Mid0061 mid) { - TcpClient.GetStream().Write(new Mid0062().Pack()); //Send acknowledge to controller + var ack = mid.GetAcknowledge(); //Mid0062, on the same revision as the received mid + TcpClient.GetStream().Write(ack.PackBytes()); //Send acknowledge to controller } ``` ### Tips -> Instantiate the **MIDIdentifier** class just once and keep working with it! +> Instantiate the **MidInterpreter** class just once and keep working with it! Template lookups are cached per instance. > **Controller Implementation Tip:** Always **TRY** to register used MIDs, not all Tightening Controllers use every available MID. > **Integrator Implementation Tip:** Always **DO** register used MIDs, I'm pretty sure you won't need all of them to your application. +### Contribute to the project + +Bug reports, controllers that behave differently from the specification and new MIDs are all very welcome. +Fork it, add your MID next to its category (with its tests in `src/MIDTesters.Core`) and open a pull request. +The [Still unavailable MIDs](#list-of-still-unavailable-mids) table below is a good place to pick something up. + +### Sponsor the project + +OpenProtocolInterpreter is built and maintained on free time, and the hardware it talks to isn't exactly the +kind of thing you keep on your desk. If this library saved you a few days of *"substringing"* packages — or if +your company ships something on top of it — consider sponsoring its development: + +[![Sponsor on GitHub](https://img.shields.io/badge/Sponsor-GitHub%20Sponsors-ea4aaa?logo=githubsponsors&logoColor=white)](https://github.com/sponsors/rickedb) +[![Buy me a coffee](https://img.shields.io/badge/Buy%20me%20a%20coffee-ffdd00?logo=buymeacoffee&logoColor=black)](https://www.buymeacoffee.com/rickedb) + +* **[GitHub Sponsors](https://github.com/sponsors/rickedb)** — one-off or monthly, and it shows up right here on the repository. +* **[Buy me a coffee](https://www.buymeacoffee.com/rickedb)** — for a quick thank you. + +Sponsoring keeps new MIDs, new specification revisions and bug fixes coming. Not in a position to sponsor? +Starring the repository, reporting a controller that misbehaves or sending a pull request helps just as much. + ### List of still unavailable Mids | MID | Description | Notes | | - | - | - | | 0007 | Reserved | Reserved by Atlas Copco | +| 0025 | Parameter user set download request | | | 0049 | Pairing Status Acknowledge | | | 0700 | Tightening data download status for radio tools | | | 0900 | Result traces curve | | @@ -305,9 +450,6 @@ protected void BuildAndSendAcknowledge(Mid mid) | 1901 | Selector socket control | | | 2100 | Device command | | | 2500 | Program data download | | -| 2501 | Program data upload reply | | -| 2502 | Password request | | -| 2503 | Password response | | | 2600 | Mode ID upload request | | | 2601 | Mode ID upload reply | | | 2602 | Mode data upload request | | From ca535e8b5f03190e5efa47876a8521b64cc22527 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Fri, 7 Aug 2026 09:00:27 -0300 Subject: [PATCH 73/78] #117 Allow setting default encoding for packing --- README.md | 2 +- src/MIDTesters.Core/TestMidEncoding.cs | 124 ++++++++++++++++++ src/OpenProtocolInterpreter/Mid.cs | 64 +++++++-- src/OpenProtocolInterpreter/MidExtensions.cs | 12 +- src/OpenProtocolInterpreter/MidInterpreter.cs | 51 +++++-- .../Tightening/StrategyOptions.cs | 5 +- .../Tightening/TighteningErrorStatus.cs | 9 +- .../_internals/Messages/IMessagesTemplate.cs | 2 + .../_internals/Messages/MessagesTemplate.cs | 16 ++- 9 files changed, 253 insertions(+), 32 deletions(-) create mode 100644 src/MIDTesters.Core/TestMidEncoding.cs diff --git a/README.md b/README.md index 3a3a0d99..009b12e7 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ var jobUploadRequest = new Mid0032(revision: 2) { JobId = 1 }; var package = jobUploadRequest.Pack(); //Generated package => 00240032002 0001 -var bytes = jobUploadRequest.PackBytes(); // same content, ASCII encoded +var bytes = jobUploadRequest.PackBytes(); // same content, encoded with Mid.DefaultEncoding (ASCII) var terminated = jobUploadRequest.PackWithNul(); // appends the NUL character some controllers expect ``` diff --git a/src/MIDTesters.Core/TestMidEncoding.cs b/src/MIDTesters.Core/TestMidEncoding.cs new file mode 100644 index 00000000..3f58fd31 --- /dev/null +++ b/src/MIDTesters.Core/TestMidEncoding.cs @@ -0,0 +1,124 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenProtocolInterpreter; +using OpenProtocolInterpreter.Communication; +using System; +using System.Text; + +namespace MIDTesters +{ + /// + /// is a global setting, so this class must never run alongside + /// the other tests, otherwise they would pack/parse their packages with the encoding set here. + /// + [TestClass] + [TestCategory("Encoding")] + [DoNotParallelize] + public class TestMidEncoding + { + private const string NonAsciiControllerName = "Estação"; + + private readonly MidInterpreter _midInterpreter; + + public TestMidEncoding() + { + _midInterpreter = new MidInterpreter().UseAllMessages(); + } + + [TestCleanup] + public void RestoreDefaultEncoding() => Mid.DefaultEncoding = Encoding.ASCII; + + private static Mid0002 BuildMid() => new(revision: 1) + { + CellId = 1, + ChannelId = 1, + ControllerName = NonAsciiControllerName + }; + + [TestMethod] + public void TestDefaultEncodingIsAscii() + { + Assert.AreEqual(Encoding.ASCII, Mid.DefaultEncoding); + } + + [TestMethod] + public void TestDefaultEncodingDoesNotAcceptNull() + { + Assert.ThrowsException(() => Mid.DefaultEncoding = null); + Assert.AreEqual(Encoding.ASCII, Mid.DefaultEncoding); + } + + [TestMethod] + public void TestPackBytesUsesDefaultEncoding() + { + var mid = BuildMid(); + var package = mid.Pack(); + + Mid.DefaultEncoding = Encoding.Latin1; + + CollectionAssert.AreEqual(Encoding.Latin1.GetBytes(package), mid.PackBytes()); + } + + [TestMethod] + public void TestPackBytesWithNulUsesDefaultEncoding() + { + var mid = BuildMid(); + var package = mid.Pack(); + + Mid.DefaultEncoding = Encoding.Latin1; + + CollectionAssert.AreEqual(Encoding.Latin1.GetBytes(package + '\0'), mid.PackBytesWithNul()); + } + + [TestMethod] + public void TestPackBytesWithGivenEncodingDoesNotChangeTheDefault() + { + var mid = BuildMid(); + var package = mid.Pack(); + + CollectionAssert.AreEqual(Encoding.UTF8.GetBytes(package), mid.PackBytes(Encoding.UTF8)); + CollectionAssert.AreEqual(Encoding.UTF8.GetBytes(package + '\0'), mid.PackBytesWithNul(Encoding.UTF8)); + Assert.AreEqual(Encoding.ASCII, Mid.DefaultEncoding); + CollectionAssert.AreEqual(Encoding.ASCII.GetBytes(package), mid.PackBytes()); + } + + [TestMethod] + public void TestParseUsesDefaultEncoding() + { + Mid.DefaultEncoding = Encoding.Latin1; + + var bytes = BuildMid().PackBytes(); + var parsed = _midInterpreter.Parse(bytes); + + Assert.AreEqual(NonAsciiControllerName, parsed.ControllerName.Trim()); + } + + [TestMethod] + public void TestParseWithGivenEncodingDoesNotChangeTheDefault() + { + var bytes = BuildMid().PackBytes(Encoding.Latin1); + + var parsed = _midInterpreter.Parse(bytes, Encoding.Latin1); + + Assert.AreEqual(NonAsciiControllerName, parsed.ControllerName.Trim()); + Assert.AreEqual(Encoding.ASCII, Mid.DefaultEncoding); + } + + [TestMethod] + public void TestParseFromMidInstanceUsesGivenEncoding() + { + var bytes = BuildMid().PackBytes(Encoding.Latin1); + + var parsed = (Mid0002)new Mid0002(revision: 1).Parse(bytes, Encoding.Latin1); + + Assert.AreEqual(NonAsciiControllerName, parsed.ControllerName.Trim()); + } + + [TestMethod] + public void TestAsciiEncodingReplacesNonAsciiCharacters() + { + var parsed = _midInterpreter.Parse(BuildMid().PackBytes()); + + Assert.AreEqual("Esta??o", parsed.ControllerName.Trim()); + } + } +} diff --git a/src/OpenProtocolInterpreter/Mid.cs b/src/OpenProtocolInterpreter/Mid.cs index e20dfd36..973bdaa2 100644 --- a/src/OpenProtocolInterpreter/Mid.cs +++ b/src/OpenProtocolInterpreter/Mid.cs @@ -13,12 +13,28 @@ namespace OpenProtocolInterpreter public abstract class Mid { private static readonly ConcurrentDictionary _metadataCache = new(); + private static Encoding _defaultEncoding = Encoding.ASCII; private readonly Lazy>> _lazyFields; protected const int DEFAULT_REVISION = 1; - protected internal Dictionary> RevisionsByFields => _lazyFields.Value; + /// + /// Encoding used whenever a package is converted from/to its byte array representation. + /// Defaults to , as stated by the Open Protocol specification. + /// + /// Setting it affects every MID, so it is meant to be set once during + /// startup, before any package is packed or parsed. Field positions of the specification are + /// counted in single byte characters, therefore ASCII compatible single byte encodings + /// (such as Latin1) are the ones able to preserve them. + /// + /// + public static Encoding DefaultEncoding + { + get => _defaultEncoding; + set => _defaultEncoding = value ?? Encoding.ASCII; + } + /// /// Header of the MID message containing standardized fields. /// @@ -83,10 +99,18 @@ public virtual string Pack() /// /// Packs the MID message into a byte array representation, including the header and all data fields for the current revision. /// If the MID has no data fields, only the header will be packed. - /// The byte array is encoded in ASCII. + /// The byte array is encoded with . /// - /// The byte array representing the packed MID message in ASCII encoding. - public virtual byte[] PackBytes() => Encoding.ASCII.GetBytes(Pack()); + /// The byte array representing the packed MID message. + public virtual byte[] PackBytes() => PackBytes(DefaultEncoding); + + /// + /// Packs the MID message into a byte array representation, including the header and all data fields for the current revision. + /// If the MID has no data fields, only the header will be packed. + /// + /// Encoding used to convert the packed message into bytes. + /// The byte array representing the packed MID message in the given encoding. + public virtual byte[] PackBytes(Encoding encoding) => ToBytes(Pack(), encoding); protected virtual string Pack(int revision) { @@ -204,12 +228,21 @@ public virtual Mid Parse(ReadOnlySpan package) /// /// Parses the MID message from a byte array representation, filling the and data fields for the current revision. + /// The byte array is decoded with . /// /// The MID message as a byte array. /// The parsed object. - public virtual Mid Parse(byte[] package) + public virtual Mid Parse(byte[] package) => Parse(package, DefaultEncoding); + + /// + /// Parses the MID message from a byte array representation, filling the and data fields for the current revision. + /// + /// The MID message as a byte array. + /// Encoding used to decode the package. + /// The parsed object. + public virtual Mid Parse(byte[] package, Encoding encoding) { - var pack = ToAscii(package); + var pack = ToText(package, encoding); return Parse(pack); } @@ -314,18 +347,23 @@ protected DataField GetField(string propertyName) protected DataField GetField(int revision, TEnum field) where TEnum : struct, Enum => GetField(revision, field.GetHashCode()); - protected static string ToAscii(byte[] bytes) => Encoding.ASCII.GetString(bytes); - protected static byte[] ToBytes(string value) => Encoding.ASCII.GetBytes(value); + [Obsolete("Packages are no longer necessarily ASCII encoded, use ToText(byte[]) which honors " + nameof(DefaultEncoding) + " instead.")] + protected static string ToAscii(byte[] bytes) => ToText(bytes, DefaultEncoding); + protected static string ToText(byte[] bytes) => ToText(bytes, DefaultEncoding); + protected static string ToText(byte[] bytes, Encoding encoding) => encoding.GetString(bytes); + protected static byte[] ToBytes(string value) => ToBytes(value, DefaultEncoding); + protected static byte[] ToBytes(string value, Encoding encoding) => encoding.GetBytes(value); protected static Span ToBytesSpan(string value) => ToBytes(value.AsSpan()); - protected static Span ToBytes(ReadOnlySpan value) + protected static Span ToBytes(ReadOnlySpan value) => ToBytes(value, DefaultEncoding); + protected static Span ToBytes(ReadOnlySpan value, Encoding encoding) { - var buffer = new byte[value.Length]; #if NETSTANDARD2_0 - Encoding.ASCII.GetBytes(value.ToString(), 0, value.Length, buffer, 0); + return encoding.GetBytes(value.ToString()); #else - Encoding.ASCII.GetBytes(value, buffer); -#endif + var buffer = new byte[encoding.GetByteCount(value)]; + encoding.GetBytes(value, buffer); return buffer; +#endif } } } diff --git a/src/OpenProtocolInterpreter/MidExtensions.cs b/src/OpenProtocolInterpreter/MidExtensions.cs index c939f547..07a19c3b 100644 --- a/src/OpenProtocolInterpreter/MidExtensions.cs +++ b/src/OpenProtocolInterpreter/MidExtensions.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; namespace OpenProtocolInterpreter { @@ -32,13 +33,22 @@ public static string PackWithNul(this Mid mid) /// Mid instance /// Mid's package in bytes with NUL character public static byte[] PackBytesWithNul(this Mid mid) + => PackBytesWithNul(mid, Mid.DefaultEncoding); + + /// + /// then concatenate NUL charactor to it`s end + /// + /// Mid instance + /// Encoding used to convert the packed message into bytes + /// Mid's package in bytes with NUL character + public static byte[] PackBytesWithNul(this Mid mid, Encoding encoding) { if (mid == default) { return default; } - var bytes = mid.PackBytes(); + var bytes = mid.PackBytes(encoding); return bytes.Concat(new byte[] { 0x00 }).ToArray(); } diff --git a/src/OpenProtocolInterpreter/MidInterpreter.cs b/src/OpenProtocolInterpreter/MidInterpreter.cs index 9ab7bc51..8e21a0e9 100644 --- a/src/OpenProtocolInterpreter/MidInterpreter.cs +++ b/src/OpenProtocolInterpreter/MidInterpreter.cs @@ -25,8 +25,16 @@ public MidInterpreter() public static string Pack(Mid mid) => mid.Pack(); + /// + /// Packs a mid into its byte array representation using . + /// public static byte[] PackBytes(Mid mid) => mid.PackBytes(); + /// + /// Packs a mid into its byte array representation using the given . + /// + public static byte[] PackBytes(Mid mid, Encoding encoding) => mid.PackBytes(encoding); + public Mid Parse(string package) { #if NETSTANDARD2_0 @@ -90,15 +98,12 @@ public ExtraData ParseExtraData(TMid mid) where TMid : Mid, IExtraDataCont return extraDataInstance.Parse(mid.ExtraData); } + /// + /// Parses a package decoding it with . + /// public Mid Parse(byte[] package) { -#if NETSTANDARD2_0 - int mid = int.Parse(Encoding.ASCII.GetString(package, 4, 4)); -#else - Span buffer = stackalloc char[4]; - Encoding.ASCII.GetChars(package.AsSpan(4, 4), buffer); - int mid = int.Parse(buffer); -#endif + int mid = ReadMidNumber(package, Mid.DefaultEncoding); var instance = TryParseStandaloneMid(mid); if (instance != default) return instance; @@ -107,6 +112,20 @@ public Mid Parse(byte[] package) return template.ProcessPackage(mid, package); } + /// + /// Parses a package decoding it with the given . + /// + public Mid Parse(byte[] package, Encoding encoding) + { + int mid = ReadMidNumber(package, encoding); + var instance = TryParseStandaloneMid(mid); + if (instance != default) + return instance; + + var template = GetMessageTemplate(mid); + return template.ProcessPackage(mid, package, encoding); + } + public ExpectedMid Parse(string package) where ExpectedMid : Mid { Mid mid = Parse(package); @@ -117,14 +136,30 @@ public ExpectedMid Parse(string package) where ExpectedMid : Mid } public ExpectedMid Parse(byte[] package) where ExpectedMid : Mid + => Parse(Parse(package)); + + public ExpectedMid Parse(byte[] package, Encoding encoding) where ExpectedMid : Mid + => Parse(Parse(package, encoding)); + + private static ExpectedMid Parse(Mid mid) where ExpectedMid : Mid { - Mid mid = Parse(package); if (mid.GetType().Equals(typeof(ExpectedMid))) return (ExpectedMid)mid; throw new InvalidCastException($"Package is Mid {mid.GetType().Name}, cannot be casted to {typeof(ExpectedMid).Name}"); } + private static int ReadMidNumber(byte[] package, Encoding encoding) + { +#if NETSTANDARD2_0 + return int.Parse(encoding.GetString(package, 4, 4)); +#else + Span buffer = stackalloc char[4]; + var written = encoding.GetChars(package.AsSpan(4, 4), buffer); + return int.Parse(buffer.Slice(0, written)); +#endif + } + internal void UseTemplate(IMessagesTemplate template) { var type = template.GetType(); diff --git a/src/OpenProtocolInterpreter/Tightening/StrategyOptions.cs b/src/OpenProtocolInterpreter/Tightening/StrategyOptions.cs index d8a4212d..8e4bd9f3 100644 --- a/src/OpenProtocolInterpreter/Tightening/StrategyOptions.cs +++ b/src/OpenProtocolInterpreter/Tightening/StrategyOptions.cs @@ -1,6 +1,5 @@ using System; using System.Reflection; -using System.Text; namespace OpenProtocolInterpreter.Tightening { @@ -26,7 +25,7 @@ public class StrategyOptions public string Pack() { byte[] bytes = PackBytes(); - return Encoding.ASCII.GetString(bytes); + return Mid.DefaultEncoding.GetString(bytes); } public byte[] PackBytes() @@ -56,7 +55,7 @@ public byte[] PackBytes() }); var asciiInt = BitConverter.ToInt32(bytes, 0).ToString("D5"); - return Encoding.ASCII.GetBytes(asciiInt); + return Mid.DefaultEncoding.GetBytes(asciiInt); } public static StrategyOptions Parse(string value) diff --git a/src/OpenProtocolInterpreter/Tightening/TighteningErrorStatus.cs b/src/OpenProtocolInterpreter/Tightening/TighteningErrorStatus.cs index 53896ec9..5fa4bdbe 100644 --- a/src/OpenProtocolInterpreter/Tightening/TighteningErrorStatus.cs +++ b/src/OpenProtocolInterpreter/Tightening/TighteningErrorStatus.cs @@ -1,6 +1,5 @@ using System; using System.Reflection; -using System.Text; namespace OpenProtocolInterpreter.Tightening { @@ -49,7 +48,7 @@ public class TighteningErrorStatus public string Pack() { byte[] bytes = PackBytes(); - return Encoding.ASCII.GetString(bytes); + return Mid.DefaultEncoding.GetString(bytes); } public byte[] PackBytes() @@ -101,7 +100,7 @@ public byte[] PackBytes() ]); var asciiLong = System.BitConverter.ToInt64(bytes, 0).ToString("D10"); - return Encoding.ASCII.GetBytes(asciiLong); + return Mid.DefaultEncoding.GetBytes(asciiLong); } public static TighteningErrorStatus Parse(string value) @@ -216,7 +215,7 @@ public TighteningErrorStatus2() public string Pack() { byte[] bytes = PackBytes(); - return Encoding.ASCII.GetString(bytes); + return Mid.DefaultEncoding.GetString(bytes); } public byte[] PackBytes() @@ -266,7 +265,7 @@ public byte[] PackBytes() ]; var asciiLong = System.BitConverter.ToInt64(bytes, 0).ToString().PadLeft(10, '0'); - return Encoding.ASCII.GetBytes(asciiLong); + return Mid.DefaultEncoding.GetBytes(asciiLong); } public static TighteningErrorStatus2 Parse(string value) diff --git a/src/OpenProtocolInterpreter/_internals/Messages/IMessagesTemplate.cs b/src/OpenProtocolInterpreter/_internals/Messages/IMessagesTemplate.cs index d16d430e..3f0db7ae 100644 --- a/src/OpenProtocolInterpreter/_internals/Messages/IMessagesTemplate.cs +++ b/src/OpenProtocolInterpreter/_internals/Messages/IMessagesTemplate.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text; namespace OpenProtocolInterpreter.Messages { @@ -11,6 +12,7 @@ internal interface IMessagesTemplate void AddOrUpdateTemplate(IDictionary types); Mid ProcessPackage(int mid, string package); Mid ProcessPackage(int mid, byte[] package); + Mid ProcessPackage(int mid, byte[] package, Encoding encoding); bool IsAssignableTo(int mid); CompiledInstance GetInstance(int mid); CompiledInstance GetExtraDataInstance(int mid, Type kind); diff --git a/src/OpenProtocolInterpreter/_internals/Messages/MessagesTemplate.cs b/src/OpenProtocolInterpreter/_internals/Messages/MessagesTemplate.cs index 1744f2bf..d0e605a1 100644 --- a/src/OpenProtocolInterpreter/_internals/Messages/MessagesTemplate.cs +++ b/src/OpenProtocolInterpreter/_internals/Messages/MessagesTemplate.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; namespace OpenProtocolInterpreter.Messages { @@ -44,7 +45,7 @@ public Mid ProcessPackage(int mid, string package) /// Find out which Mid instance it should instantiate and parse all it's content /// /// Mid number - /// package in bytes + /// package in bytes, encoded with /// instance public Mid ProcessPackage(int mid, byte[] package) { @@ -52,6 +53,19 @@ public Mid ProcessPackage(int mid, byte[] package) return compiledInstance.CompiledConstructor().Parse(package); } + /// + /// Find out which Mid instance it should instantiate and parse all it's content + /// + /// Mid number + /// package in bytes + /// Encoding used to decode the package + /// instance + public Mid ProcessPackage(int mid, byte[] package, Encoding encoding) + { + var compiledInstance = GetInstance(mid); + return compiledInstance.CompiledConstructor().Parse(package, encoding); + } + /// /// Get from the dictionary based on mid number /// From 71010ca994bf1dc896703ab5367e1a6af6da3191 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Fri, 7 Aug 2026 09:06:33 -0300 Subject: [PATCH 74/78] Fixed tests --- src/MIDTesters.Core/TestMidEncoding.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/MIDTesters.Core/TestMidEncoding.cs b/src/MIDTesters.Core/TestMidEncoding.cs index 3f58fd31..b1548fd6 100644 --- a/src/MIDTesters.Core/TestMidEncoding.cs +++ b/src/MIDTesters.Core/TestMidEncoding.cs @@ -1,7 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenProtocolInterpreter; using OpenProtocolInterpreter.Communication; -using System; using System.Text; namespace MIDTesters @@ -41,9 +40,9 @@ public void TestDefaultEncodingIsAscii() } [TestMethod] - public void TestDefaultEncodingDoesNotAcceptNull() + public void TestDefaultEncodingNullFallbackToAscii() { - Assert.ThrowsException(() => Mid.DefaultEncoding = null); + Mid.DefaultEncoding = null; Assert.AreEqual(Encoding.ASCII, Mid.DefaultEncoding); } From e0991cfc8987ffede28bddd75a5e7c138c3155e0 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Fri, 7 Aug 2026 10:26:02 -0300 Subject: [PATCH 75/78] Updated summaries --- src/OpenProtocolInterpreter/Mid.cs | 2 +- src/OpenProtocolInterpreter/MidInterpreter.cs | 94 +++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/src/OpenProtocolInterpreter/Mid.cs b/src/OpenProtocolInterpreter/Mid.cs index 973bdaa2..54e629ae 100644 --- a/src/OpenProtocolInterpreter/Mid.cs +++ b/src/OpenProtocolInterpreter/Mid.cs @@ -347,7 +347,7 @@ protected DataField GetField(string propertyName) protected DataField GetField(int revision, TEnum field) where TEnum : struct, Enum => GetField(revision, field.GetHashCode()); - [Obsolete("Packages are no longer necessarily ASCII encoded, use ToText(byte[]) which honors " + nameof(DefaultEncoding) + " instead.")] + [Obsolete($"Packages are no longer necessarily ASCII encoded, use ToText(byte[]) which honors {nameof(DefaultEncoding)} instead.")] protected static string ToAscii(byte[] bytes) => ToText(bytes, DefaultEncoding); protected static string ToText(byte[] bytes) => ToText(bytes, DefaultEncoding); protected static string ToText(byte[] bytes, Encoding encoding) => encoding.GetString(bytes); diff --git a/src/OpenProtocolInterpreter/MidInterpreter.cs b/src/OpenProtocolInterpreter/MidInterpreter.cs index 8e21a0e9..296ff808 100644 --- a/src/OpenProtocolInterpreter/MidInterpreter.cs +++ b/src/OpenProtocolInterpreter/MidInterpreter.cs @@ -17,24 +17,45 @@ public class MidInterpreter private readonly Dictionary> _messagesTemplates; private readonly SortedDictionary _midTemplates; + /// + /// Creates an interpreter with no message template registered. + /// + /// Templates must be registered before parsing any package + /// + /// public MidInterpreter() { _messagesTemplates = new Dictionary>(); _midTemplates = new SortedDictionary(); } + /// + /// Packs a mid into its string representation. + /// + /// The mid to pack. + /// The string representation of the mid. public static string Pack(Mid mid) => mid.Pack(); /// /// Packs a mid into its byte array representation using . /// + /// The mid to pack. + /// The byte array representation of the mid. public static byte[] PackBytes(Mid mid) => mid.PackBytes(); /// /// Packs a mid into its byte array representation using the given . /// + /// The mid to pack. + /// Encoding used to convert the packed mid into bytes. + /// The byte array representation of the mid. public static byte[] PackBytes(Mid mid, Encoding encoding) => mid.PackBytes(encoding); + /// + /// Parses a package into its corresponding instance. + /// + /// The package to parse. + /// The parsed mid instance. public Mid Parse(string package) { #if NETSTANDARD2_0 @@ -50,6 +71,14 @@ public Mid Parse(string package) return template.ProcessPackage(mid, package); } + /// + /// Parses the extra data of a request into the expected . + /// + /// The expected extra data type of the requested mid. + /// The mid containing the extra data to parse. + /// The parsed extra data. + /// Thrown when the parsed extra data is not a . + /// Thrown when no registered message template handles the requested mid. public TExtraData ParseExtraData(Mid0006 mid) where TExtraData : ExtraData, IExtraDataRequest { var instance = ParseExtraData(mid); @@ -59,6 +88,14 @@ public TExtraData ParseExtraData(Mid0006 mid) where TExtraData : Ext throw new InvalidCastException($"Extra data cannot be casted to {typeof(TExtraData).Name}"); } + /// + /// Parses the extra data of a subscription into the expected . + /// + /// The expected extra data type of the subscribed mid. + /// The mid containing the extra data to parse. + /// The parsed extra data. + /// Thrown when the parsed extra data is not a . + /// Thrown when no registered message template handles the subscribed mid. public TExtraData ParseExtraData(Mid0008 mid) where TExtraData : ExtraData, IExtraDataSubscription { var instance = ParseExtraData(mid); @@ -68,6 +105,14 @@ public TExtraData ParseExtraData(Mid0008 mid) where TExtraData : Ext throw new InvalidCastException($"Extra data cannot be casted to {typeof(TExtraData).Name}"); } + /// + /// Parses the extra data of a unsubscription into the expected . + /// + /// The expected extra data type of the unsubscribed mid. + /// The mid containing the extra data to parse. + /// The parsed extra data. + /// Thrown when the parsed extra data is not a . + /// Thrown when no registered message template handles the unsubscribed mid. public TExtraData ParseExtraData(Mid0009 mid) where TExtraData : ExtraData, IExtraDataUnsubscription { var expectedExtraData = ParseExtraData(mid); @@ -77,6 +122,15 @@ public TExtraData ParseExtraData(Mid0009 mid) where TExtraData : Ext throw new InvalidCastException($"Extra data cannot be casted to {typeof(TExtraData).Name}"); } + /// + /// Parses the extra data of any extra data container mid into the expected . + /// + /// The type of the mid carrying the extra data. + /// The expected extra data type of the contained mid. + /// The mid containing the extra data to parse. + /// The parsed extra data. + /// Thrown when the parsed extra data is not a . + /// Thrown when is not a supported extra data container, or when no registered message template handles the contained mid. public TExtraData ParseExtraData(TMid mid) where TMid : Mid, IExtraDataContainer where TExtraData : ExtraData { @@ -87,6 +141,14 @@ public TExtraData ParseExtraData(TMid mid) where TMid : Mid, I throw new InvalidCastException($"Extra data cannot be casted to {typeof(TExtraData).Name}"); } + /// + /// Parses the extra data of any extra data container mid, such as , and . + /// The extra data instance is resolved from the mid it refers to and parsed with the revision wanted by . + /// + /// The type of the mid carrying the extra data. + /// The mid containing the extra data to parse. + /// The parsed extra data. + /// Thrown when is not a supported extra data container, or when no registered message template handles the contained mid. public ExtraData ParseExtraData(TMid mid) where TMid : Mid, IExtraDataContainer { var expectedMid = GetMidFromExtraDataMid(mid); @@ -101,6 +163,9 @@ public ExtraData ParseExtraData(TMid mid) where TMid : Mid, IExtraDataCont /// /// Parses a package decoding it with . /// + /// The package to parse. + /// The parsed mid instance. + /// Thrown when no registered message template handles the package's mid. public Mid Parse(byte[] package) { int mid = ReadMidNumber(package, Mid.DefaultEncoding); @@ -115,6 +180,10 @@ public Mid Parse(byte[] package) /// /// Parses a package decoding it with the given . /// + /// The package to parse. + /// Encoding used to decode the package. + /// The parsed mid instance. + /// Thrown when no registered message template handles the package's mid. public Mid Parse(byte[] package, Encoding encoding) { int mid = ReadMidNumber(package, encoding); @@ -126,6 +195,14 @@ public Mid Parse(byte[] package, Encoding encoding) return template.ProcessPackage(mid, package, encoding); } + /// + /// Parses a package and returns it as the expected mid type. + /// + /// The mid type the package is expected to be. + /// The package to parse. + /// The parsed mid instance. + /// Thrown when the package is not an . + /// Thrown when no registered message template handles the package's mid. public ExpectedMid Parse(string package) where ExpectedMid : Mid { Mid mid = Parse(package); @@ -135,9 +212,26 @@ public ExpectedMid Parse(string package) where ExpectedMid : Mid throw new InvalidCastException($"Package is Mid {mid.GetType().Name}, cannot be casted to {typeof(ExpectedMid).Name}"); } + /// + /// Parses a package decoding it with and returns it as the expected mid type. + /// + /// The mid type the package is expected to be. + /// The package to parse. + /// The parsed mid instance. + /// Thrown when the package is not an . + /// Thrown when no registered message template handles the package's mid. public ExpectedMid Parse(byte[] package) where ExpectedMid : Mid => Parse(Parse(package)); + /// + /// Parses a package decoding it with the given and returns it as the expected mid type. + /// + /// The mid type the package is expected to be. + /// The package to parse. + /// Encoding used to decode the package. + /// The parsed mid instance. + /// Thrown when the package is not an . + /// Thrown when no registered message template handles the package's mid. public ExpectedMid Parse(byte[] package, Encoding encoding) where ExpectedMid : Mid => Parse(Parse(package, encoding)); From 45842f74ffaf42db45ae0ff0f0784386c38ad248 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 9 Aug 2026 00:24:03 -0300 Subject: [PATCH 76/78] Added benchmarkDotNet --- OpenProtocolInterpreter.slnx | 1 + src/MIDTesters.Core/MIDsTests.cs | 97 ----------------- src/MIDTesters.Core/Program.cs | 4 - .../BenchmarkConfig.cs | 28 +++++ .../InterpreterModeBenchmarks.cs | 58 ++++++++++ .../OpenProtocolInterpreter.Benchmarks.csproj | 28 +++++ .../Packages.cs | 102 ++++++++++++++++++ .../PackingBenchmarks.cs | 57 ++++++++++ .../ParsingBenchmarks.cs | 60 +++++++++++ .../Program.cs | 19 ++++ .../README.md | 59 ++++++++++ .../RegistrationBenchmarks.cs | 55 ++++++++++ 12 files changed, 467 insertions(+), 101 deletions(-) delete mode 100644 src/MIDTesters.Core/MIDsTests.cs delete mode 100644 src/MIDTesters.Core/Program.cs create mode 100644 src/OpenProtocolInterpreter.Benchmarks/BenchmarkConfig.cs create mode 100644 src/OpenProtocolInterpreter.Benchmarks/InterpreterModeBenchmarks.cs create mode 100644 src/OpenProtocolInterpreter.Benchmarks/OpenProtocolInterpreter.Benchmarks.csproj create mode 100644 src/OpenProtocolInterpreter.Benchmarks/Packages.cs create mode 100644 src/OpenProtocolInterpreter.Benchmarks/PackingBenchmarks.cs create mode 100644 src/OpenProtocolInterpreter.Benchmarks/ParsingBenchmarks.cs create mode 100644 src/OpenProtocolInterpreter.Benchmarks/Program.cs create mode 100644 src/OpenProtocolInterpreter.Benchmarks/README.md create mode 100644 src/OpenProtocolInterpreter.Benchmarks/RegistrationBenchmarks.cs diff --git a/OpenProtocolInterpreter.slnx b/OpenProtocolInterpreter.slnx index ec628101..1171772d 100644 --- a/OpenProtocolInterpreter.slnx +++ b/OpenProtocolInterpreter.slnx @@ -1,4 +1,5 @@ + diff --git a/src/MIDTesters.Core/MIDsTests.cs b/src/MIDTesters.Core/MIDsTests.cs deleted file mode 100644 index cd724e1b..00000000 --- a/src/MIDTesters.Core/MIDsTests.cs +++ /dev/null @@ -1,97 +0,0 @@ -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Running; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using OpenProtocolInterpreter; -using OpenProtocolInterpreter.Communication; -using OpenProtocolInterpreter.MotorTuning; -using OpenProtocolInterpreter.Tightening; -using System; - -namespace MIDTesters -{ - [MemoryDiagnoser] - public class MIDsTests - { - private const string Mid61Raw = - "02310061001 010001020103airbag7 04KPOL3456JKLO897 " + - "05000600307000008000009010011112000840130014001400120015000739160000017099991800000" + - "1900000202001-06-02:09:54:09212001-05-29:12:34:3322123345675 "; - - private MidInterpreter _customMidInterpreter = null!; - private MidInterpreter _allMidInterpreter = null!; - - [GlobalSetup] - public void Setup() - { - _customMidInterpreter = new MidInterpreter() - .UseAllMessages(new Type[] - { - typeof(Mid0001), - typeof(Mid0002), - typeof(Mid0003), - typeof(Mid0004), - typeof(Mid0061), - typeof(Mid0500), - typeof(Mid0501), - typeof(Mid0502), - typeof(Mid0503), - typeof(Mid0504) - }); - - _allMidInterpreter = new MidInterpreter().UseAllMessages(); - } - - //[Benchmark(Description = "Build MidInterpreter (custom MIDs)")] - public MidInterpreter BuildCustomMidInterpreter() => - new MidInterpreter().UseAllMessages(new Type[] - { - typeof(Mid0001), - typeof(Mid0002), - typeof(Mid0003), - typeof(Mid0004), - typeof(Mid0061), - typeof(Mid0500), - typeof(Mid0501), - typeof(Mid0502), - typeof(Mid0503), - typeof(Mid0504) - }); - - //[Benchmark(Description = "Build MidInterpreter (all MIDs)")] - public MidInterpreter BuildAllMidInterpreter() => - new MidInterpreter().UseAllMessages(); - - //[Benchmark(Description = "Parse Mid0061 (custom MIDs)")] - public Mid0061 ParseMid61CustomInterpreter() => - _customMidInterpreter.Parse(Mid61Raw); - - //[Benchmark(Description = "Parse Mid0061 (all MIDs)", Baseline = true)] - public Mid0061 ParseMid61AllInterpreter() => - _allMidInterpreter.Parse(Mid61Raw); - - [Benchmark(Description = "Mid performance")] - public Mid MidPerformance() - { - var value = "02240002007 010001020103Airbag1 04ACT05OpenProtocolVersion06Version 19.0.0.0 07Version 01.0.0.0 08RBUType 09Serial 100021100212013114429496729515Station Or Cell Name 16A171"; - var mid = _allMidInterpreter.Parse(value); - Assert.AreEqual(1, mid.CellId); - Assert.AreEqual(1, mid.ChannelId); - Assert.AreEqual("Airbag1", mid.ControllerName.TrimEnd()); - Assert.AreEqual("ACT", mid.SupplierCode); - Assert.AreEqual("OpenProtocolVersion", mid.OpenProtocolVersion); - Assert.AreEqual("Version 19.0.0.0", mid.ControllerSoftwareVersion.TrimEnd()); - Assert.AreEqual("Version 01.0.0.0", mid.ToolSoftwareVersion.TrimEnd()); - Assert.AreEqual("RBUType", mid.RBUType.TrimEnd()); - Assert.AreEqual("Serial", mid.ControllerSerialNumber.TrimEnd()); - Assert.AreEqual(SystemType.PowerMacs4000, mid.SystemType); - Assert.AreEqual(SystemSubType.SystemRunningPresses, mid.SystemSubType); - Assert.IsFalse(mid.SequenceNumberSupport); - Assert.IsTrue(mid.LinkingHandlingSupport); - Assert.AreEqual(4294967295L, mid.StationCellId); - Assert.AreEqual("Station Or Cell Name", mid.StationCellName.TrimEnd()); - Assert.AreEqual("A", mid.ClientId); - Assert.IsTrue(mid.OptionalKeepAlive); - return mid; - } - } -} diff --git a/src/MIDTesters.Core/Program.cs b/src/MIDTesters.Core/Program.cs deleted file mode 100644 index a4caf239..00000000 --- a/src/MIDTesters.Core/Program.cs +++ /dev/null @@ -1,4 +0,0 @@ -using BenchmarkDotNet.Running; -using MIDTesters; - -BenchmarkRunner.Run(); diff --git a/src/OpenProtocolInterpreter.Benchmarks/BenchmarkConfig.cs b/src/OpenProtocolInterpreter.Benchmarks/BenchmarkConfig.cs new file mode 100644 index 00000000..e660303d --- /dev/null +++ b/src/OpenProtocolInterpreter.Benchmarks/BenchmarkConfig.cs @@ -0,0 +1,28 @@ +namespace OpenProtocolInterpreter.Benchmarks +{ + using BenchmarkDotNet.Columns; + using BenchmarkDotNet.Configs; + using BenchmarkDotNet.Diagnosers; + using BenchmarkDotNet.Order; + using BenchmarkDotNet.Reports; + using Job = BenchmarkDotNet.Jobs.Job; + + internal static class BenchmarkConfig + { + public static IConfig Create() => Create(Job.Default); + + public static IConfig CreateQuick() => Create(Job.ShortRun); + + private static IConfig Create(Job job) + { + return ManualConfig.Create(DefaultConfig.Instance) + .AddJob(job) + .AddDiagnoser(MemoryDiagnoser.Default) + .AddColumn(RankColumn.Arabic) + .WithOrderer(new DefaultOrderer(SummaryOrderPolicy.Declared, MethodOrderPolicy.Declared)) + .WithSummaryStyle(SummaryStyle.Default + .WithRatioStyle(RatioStyle.Trend) + .WithMaxParameterColumnWidth(30)); + } + } +} diff --git a/src/OpenProtocolInterpreter.Benchmarks/InterpreterModeBenchmarks.cs b/src/OpenProtocolInterpreter.Benchmarks/InterpreterModeBenchmarks.cs new file mode 100644 index 00000000..7f23230a --- /dev/null +++ b/src/OpenProtocolInterpreter.Benchmarks/InterpreterModeBenchmarks.cs @@ -0,0 +1,58 @@ +using BenchmarkDotNet.Attributes; + +namespace OpenProtocolInterpreter.Benchmarks +{ + public class InterpreterModeBenchmarks + { + private MidInterpreter _interpreter; + private string _representative; + private string[] _incoming; + private byte[][] _incomingBytes; + + [Params(InterpreterMode.Both, InterpreterMode.Controller, InterpreterMode.Integrator)] + public InterpreterMode Mode { get; set; } + + [GlobalSetup] + public void Setup() + { + _representative = Packages.RepresentativeFor(Mode); + _incoming = Packages.IncomingFor(Mode); + _incomingBytes = Packages.ToBytes(_incoming); + + // Fail here rather than reporting a number for a mode that was silently rejecting its packages. + _interpreter = new MidInterpreter().UseAllMessages(Mode); + _interpreter.Parse(_representative); + foreach (var package in _incoming) + _interpreter.Parse(package); + } + + [Benchmark(Description = "Register all messages")] + public MidInterpreter Register() => new MidInterpreter().UseAllMessages(Mode); + + [Benchmark(Description = "Cold start (register + first parse)")] + public Mid ColdStart() => new MidInterpreter().UseAllMessages(Mode).Parse(_representative); + + [Benchmark(Description = "Parse (warm)")] + public Mid Parse() => _interpreter.Parse(_representative); + + [Benchmark(Description = "Parse session (warm, 4 packages)")] + public Mid ParseSession() + { + Mid last = null; + for (int i = 0; i < _incoming.Length; i++) + last = _interpreter.Parse(_incoming[i]); + + return last; + } + + [Benchmark(Description = "Parse session from bytes (warm, 4 packages)")] + public Mid ParseSessionBytes() + { + Mid last = null; + for (int i = 0; i < _incomingBytes.Length; i++) + last = _interpreter.Parse(_incomingBytes[i]); + + return last; + } + } +} diff --git a/src/OpenProtocolInterpreter.Benchmarks/OpenProtocolInterpreter.Benchmarks.csproj b/src/OpenProtocolInterpreter.Benchmarks/OpenProtocolInterpreter.Benchmarks.csproj new file mode 100644 index 00000000..3624c292 --- /dev/null +++ b/src/OpenProtocolInterpreter.Benchmarks/OpenProtocolInterpreter.Benchmarks.csproj @@ -0,0 +1,28 @@ + + + + Exe + net10.0 + latest + OpenProtocolInterpreter.Benchmarks + OpenProtocolInterpreter.Benchmarks + false + + + false + false + + + + + + + + + + + diff --git a/src/OpenProtocolInterpreter.Benchmarks/Packages.cs b/src/OpenProtocolInterpreter.Benchmarks/Packages.cs new file mode 100644 index 00000000..c4f58043 --- /dev/null +++ b/src/OpenProtocolInterpreter.Benchmarks/Packages.cs @@ -0,0 +1,102 @@ +using System.Text; + +namespace OpenProtocolInterpreter.Benchmarks +{ + /// + /// Open Protocol packages used across the benchmarks, taken from the test suite so every benchmark + /// measures a package that is known to parse. + /// + /// A mid travels in a single direction: mids marked with are sent by the + /// controller and consumed by an integrator, mids marked with travel the + /// other way. That is what filters on, so the packages are grouped by + /// direction here. + /// + /// + internal static class Packages + { + #region Controller originated (parsed by an integrator) + + public const string Mid0002Rev3 = "01250002003 010001020103Airbag1 04ACT05OpenProtocolVersion06Version 19.0.0.0 07Version 01.0.0.0 "; + public const string Mid0015Rev2 = "01410015002 0100202Airbag parameter 032017-06-02:09:54:0904205040600510107010009080050050900001109999911003601200123413001006"; + public const string Mid0035Rev3 = "00790035003 010001020030040008050003062001-12-01:20:12:4507120080100912"; + public const string Mid0061Rev1 = "02310061001 010001020103airbag7 04KPOL3456JKLO897 050006003070000080000090100111120008401300140014001200150007391600000170999918000001900000202001-06-02:09:54:09212001-05-29:12:34:33221230000345675"; + public const string Mid0061Rev11 = "06770061011 010000020003RA ST6.2 ETV100 04 05000006001070208000670900001000001101221301401501611711811912000000081942100340022004600230040002400050525000202600420270000028000002900100300085031000043200033150340003500000036999900370000003800000039000000400000004100001848874200000430000044 C0761275452020-06-25:01:04:39462020-06-24:10:48:5347Test Parameter Set 482490550Identifier result part 2 51Identifier result part 3 52Identifier result part 4 53E124540015005500000000425600100005709999005800100059260652143610052326200100630005064001506500001660000167168-00206900100700012071003291720010057300011074009000750001207600001"; + public const string Mid0071Rev3 = "01090071003 01E1021021031042017-12-01:20:12:4505106Alarm Text "; + public const string Mid0101Rev1 = "02100101001 010202BM3GA02111900601 030304003050001060001071080006800900092010000800110000012000151300000142019-11-14:14:08:05152019-11-25:11:22:41160091317118010111000809100000020111000809100000"; + + #endregion + + #region Integrator originated (parsed by a controller) + + /// Communication start, revision 7. 23 bytes. + public const string Mid0001Rev7 = "00230001007 011"; + + /// Communication stop, revision 1. 20 bytes, header only. + public const string Mid0003Rev1 = "00200003001 "; + + /// Vehicle id number download request. 35 bytes. + public const string Mid0050Rev1 = "00350050001 VehicleIdNumber"; + + /// Last tightening result data subscribe, all revisions. 20 bytes, header only. + public const string Mid0060Rev998 = "00200060998 "; + + /// Old tightening result upload request, revision 1. 30 bytes. + public const string Mid0064Rev1 = "00300064001 0123456789"; + + #endregion + + public static readonly string[] ControllerOriginated = + [ + Mid0002Rev3, + Mid0035Rev3, + Mid0061Rev1, + Mid0071Rev3 + ]; + + public static readonly string[] IntegratorOriginated = + [ + Mid0001Rev7, + Mid0003Rev1, + Mid0060Rev998, + Mid0064Rev1 + ]; + + public static readonly string[] BothDirections = + [ + Mid0001Rev7, + Mid0002Rev3, + Mid0060Rev998, + Mid0061Rev1 + ]; + + public static string[] IncomingFor(InterpreterMode mode) + { + return mode switch + { + InterpreterMode.Controller => IntegratorOriginated, + InterpreterMode.Integrator => ControllerOriginated, + _ => BothDirections + }; + } + + /// + /// Returns the single package used as the representative message of the given : + /// the tightening result for anyone reading a controller, the subscription that asks for it otherwise. + /// + public static string RepresentativeFor(InterpreterMode mode) + { + return mode == InterpreterMode.Controller ? Mid0060Rev998 : Mid0061Rev1; + } + + public static byte[] ToBytes(string package) => Encoding.ASCII.GetBytes(package); + + public static byte[][] ToBytes(string[] packages) + { + var bytes = new byte[packages.Length][]; + for (int i = 0; i < packages.Length; i++) + bytes[i] = ToBytes(packages[i]); + + return bytes; + } + } +} diff --git a/src/OpenProtocolInterpreter.Benchmarks/PackingBenchmarks.cs b/src/OpenProtocolInterpreter.Benchmarks/PackingBenchmarks.cs new file mode 100644 index 00000000..f7b824f8 --- /dev/null +++ b/src/OpenProtocolInterpreter.Benchmarks/PackingBenchmarks.cs @@ -0,0 +1,57 @@ +using BenchmarkDotNet.Attributes; +using OpenProtocolInterpreter.Communication; +using OpenProtocolInterpreter.KeepAlive; +using OpenProtocolInterpreter.Tightening; +using System.Text; + +namespace OpenProtocolInterpreter.Benchmarks +{ + /// + /// Measures the other half of the round trip: turning a mid back into the package that goes on the wire. + /// + /// Packing is what a driver does on every message it sends, and is + /// plus an encode, so the pair shows what the encode step costs on top. + /// + /// + public class PackingBenchmarks + { + private static readonly Encoding NonDefaultEncoding = Encoding.UTF8; + + private Mid9999 _keepAlive; + private Mid0001 _communicationStart; + private Mid0061 _tighteningResult; + private Mid0061 _wideTighteningResult; + + [GlobalSetup] + public void Setup() + { + var interpreter = new MidInterpreter().UseAllMessages(); + + _keepAlive = new Mid9999(); + _communicationStart = interpreter.Parse(Packages.Mid0001Rev7); + _tighteningResult = interpreter.Parse(Packages.Mid0061Rev1); + _wideTighteningResult = interpreter.Parse(Packages.Mid0061Rev11); + } + + [Benchmark(Description = "Pack Mid9999 (header only)")] + public string PackKeepAlive() => _keepAlive.Pack(); + + [Benchmark(Description = "Pack Mid0001 (23B)")] + public string PackCommunicationStart() => _communicationStart.Pack(); + + [Benchmark(Description = "Pack Mid0061 rev 1 (231B)", Baseline = true)] + public string PackTighteningResult() => _tighteningResult.Pack(); + + [Benchmark(Description = "Pack Mid0061 rev 11 (677B)")] + public string PackWideTighteningResult() => _wideTighteningResult.Pack(); + + [Benchmark(Description = "PackBytes Mid0061 rev 1 (default encoding)")] + public byte[] PackBytesTighteningResult() => _tighteningResult.PackBytes(); + + [Benchmark(Description = "PackBytes Mid0061 rev 1 (explicit encoding)")] + public byte[] PackBytesTighteningResultWithEncoding() => _tighteningResult.PackBytes(NonDefaultEncoding); + + [Benchmark(Description = "PackBytes Mid0061 rev 11 (default encoding)")] + public byte[] PackBytesWideTighteningResult() => _wideTighteningResult.PackBytes(); + } +} diff --git a/src/OpenProtocolInterpreter.Benchmarks/ParsingBenchmarks.cs b/src/OpenProtocolInterpreter.Benchmarks/ParsingBenchmarks.cs new file mode 100644 index 00000000..b69cdf02 --- /dev/null +++ b/src/OpenProtocolInterpreter.Benchmarks/ParsingBenchmarks.cs @@ -0,0 +1,60 @@ +using BenchmarkDotNet.Attributes; +using System.Collections.Generic; +using System.Text; + +namespace OpenProtocolInterpreter.Benchmarks +{ + public class ParsingBenchmarks + { + private static readonly Encoding NonDefaultEncoding = Encoding.UTF8; + + private MidInterpreter _interpreter; + private byte[] _bytes; + + [ParamsSource(nameof(PackageCases))] + public PackageCase Package { get; set; } + + public static IEnumerable PackageCases() + { + yield return new PackageCase("Mid0060 header", Packages.Mid0060Rev998); + yield return new PackageCase("Mid0035 r3", Packages.Mid0035Rev3); + yield return new PackageCase("Mid0061 r1", Packages.Mid0061Rev1); + yield return new PackageCase("Mid0101 r1 list", Packages.Mid0101Rev1); + yield return new PackageCase("Mid0061 r11", Packages.Mid0061Rev11); + } + + [GlobalSetup] + public void Setup() + { + _interpreter = new MidInterpreter().UseAllMessages(); + _bytes = Packages.ToBytes(Package.Value); + + // Materialize the templates so the first measured iteration is not the one paying for them. + _interpreter.Parse(Package.Value); + } + + [Benchmark(Description = "Parse(string)", Baseline = true)] + public Mid ParseString() => _interpreter.Parse(Package.Value); + + [Benchmark(Description = "Parse(byte[])")] + public Mid ParseBytes() => _interpreter.Parse(_bytes); + + [Benchmark(Description = "Parse(byte[], encoding)")] + public Mid ParseBytesWithEncoding() => _interpreter.Parse(_bytes, NonDefaultEncoding); + + public readonly struct PackageCase + { + public PackageCase(string name, string value) + { + Name = name; + Value = value; + } + + public string Name { get; } + + public string Value { get; } + + public override string ToString() => $"{Name} ({Value.Length}B)"; + } + } +} diff --git a/src/OpenProtocolInterpreter.Benchmarks/Program.cs b/src/OpenProtocolInterpreter.Benchmarks/Program.cs new file mode 100644 index 00000000..82ffa828 --- /dev/null +++ b/src/OpenProtocolInterpreter.Benchmarks/Program.cs @@ -0,0 +1,19 @@ +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Running; +using OpenProtocolInterpreter.Benchmarks; +using System; +using System.Linq; +using System.Reflection; + +var quick = args.Contains("--quick", StringComparer.OrdinalIgnoreCase); +var remaining = args.Where(x => !string.Equals(x, "--quick", StringComparison.OrdinalIgnoreCase)).ToList(); +IConfig config = quick ? BenchmarkConfig.CreateQuick() : BenchmarkConfig.Create(); + +string[] selectionArguments = ["--filter", "-f", "--list", "--help", "-h", "--version", "--info"]; +if (!remaining.Any(x => selectionArguments.Contains(x, StringComparer.OrdinalIgnoreCase))) +{ + remaining.Add("--filter"); + remaining.Add("*"); +} + +BenchmarkSwitcher.FromAssembly(Assembly.GetExecutingAssembly()).Run(remaining.ToArray(), config); diff --git a/src/OpenProtocolInterpreter.Benchmarks/README.md b/src/OpenProtocolInterpreter.Benchmarks/README.md new file mode 100644 index 00000000..a28f6d1c --- /dev/null +++ b/src/OpenProtocolInterpreter.Benchmarks/README.md @@ -0,0 +1,59 @@ +# OpenProtocolInterpreter.Benchmarks + +[BenchmarkDotNet](https://benchmarkdotnet.org/) suite for the library. It is a plain console app, kept apart +from `MIDTesters.Core` on purpose: a benchmark needs an optimized, non instrumented, single process host, +which is the opposite of what a test host gives you. + +## Running + +```bash +# everything, full precision (several minutes) +dotnet run -c Release --project src/OpenProtocolInterpreter.Benchmarks + +# one class +dotnet run -c Release --project src/OpenProtocolInterpreter.Benchmarks -- --filter '*InterpreterModeBenchmarks*' + +# short job, for checking the suite still runs after a change +dotnet run -c Release --project src/OpenProtocolInterpreter.Benchmarks -- --quick + +# list what is available +dotnet run -c Release --project src/OpenProtocolInterpreter.Benchmarks -- --list flat +``` + +`-c Release` is not optional; BenchmarkDotNet refuses to run a debug build. Everything other than `--quick` +is passed straight to BenchmarkDotNet, so its whole command line is available. + +Reports land in `BenchmarkDotNet.Artifacts/results` next to the binaries, including a GitHub flavoured +markdown table that can be pasted into an issue or a pull request. + +## What is measured + +| Class | Question it answers | +|---|---| +| `InterpreterModeBenchmarks` | What do `InterpreterMode.Both`, `Controller` and `Integrator` cost? | +| `RegistrationBenchmarks` | Is it worth registering only the mids I use instead of `UseAllMessages()`? | +| `ParsingBenchmarks` | What does a parse cost per package width, and what does the `byte[]` overload add? | +| `PackingBenchmarks` | What does building the outgoing package cost, and what does `PackBytes` add over `Pack`? | + +### Reading the interpreter mode results + +Only `Register all messages` is a like for like comparison across the three modes: it is the same call with +a different argument. The parse rows cannot be, because a mid travels in one direction only, so a +`Controller` interpreter physically never receives the packages an `Integrator` one does. Each mode is +therefore benchmarked against the traffic it actually sees (`Packages.IncomingFor`), and each row should be +read on its own. + +What the three columns do show is *where* a mode costs anything. Registration only wires up 24 lazy +templates and is mode independent. The mode is paid for on the first parse, when a template is materialized +and `Controller`/`Integrator` additionally filter out the mids of the opposite direction, work `Both` skips. + +## Adding a benchmark + +Sample packages live in `Packages.cs` and are copied from the test suite, so anything benchmarked is known +to parse. Add new ones there rather than inlining a raw string, and keep them grouped by direction +(`IController` mids are sent by the controller, `IIntegrator` mids by the integrator) so the mode benchmarks +keep working. + +Benchmark bodies must not assert. An assertion measures the assertion; correctness belongs in +`MIDTesters.Core`. Where a benchmark depends on a package parsing, check it once in `[GlobalSetup]` so a +broken sample fails the run instead of quietly reporting the cost of an exception. diff --git a/src/OpenProtocolInterpreter.Benchmarks/RegistrationBenchmarks.cs b/src/OpenProtocolInterpreter.Benchmarks/RegistrationBenchmarks.cs new file mode 100644 index 00000000..283b0a20 --- /dev/null +++ b/src/OpenProtocolInterpreter.Benchmarks/RegistrationBenchmarks.cs @@ -0,0 +1,55 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Configs; +using OpenProtocolInterpreter.Communication; +using OpenProtocolInterpreter.MotorTuning; +using OpenProtocolInterpreter.Tightening; +using System; + +namespace OpenProtocolInterpreter.Benchmarks +{ + [CategoriesColumn] + [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] + public class RegistrationBenchmarks + { + private const string Registering = "Registering"; + private const string ColdStart = "Cold start"; + + private static readonly Type[] CuratedMids = + [ + typeof(Mid0001), + typeof(Mid0002), + typeof(Mid0003), + typeof(Mid0004), + typeof(Mid0061), + typeof(Mid0500), + typeof(Mid0501), + typeof(Mid0502), + typeof(Mid0503), + typeof(Mid0504) + ]; + + [BenchmarkCategory(Registering)] + [Benchmark(Description = "UseAllMessages()", Baseline = true)] + public MidInterpreter RegisterAll() => new MidInterpreter().UseAllMessages(); + + [BenchmarkCategory(Registering)] + [Benchmark(Description = "UseAllMessages(Type[]) - 10 mids")] + public MidInterpreter RegisterCurated() => new MidInterpreter().UseAllMessages(CuratedMids); + + [BenchmarkCategory(Registering)] + [Benchmark(Description = "UseTighteningMessages() only")] + public MidInterpreter RegisterSingleFamily() => new MidInterpreter().UseTighteningMessages(); + + [BenchmarkCategory(ColdStart)] + [Benchmark(Description = "UseAllMessages() + first parse", Baseline = true)] + public Mid ColdStartAll() => new MidInterpreter().UseAllMessages().Parse(Packages.Mid0061Rev1); + + [BenchmarkCategory(ColdStart)] + [Benchmark(Description = "UseAllMessages(Type[]) + first parse")] + public Mid ColdStartCurated() => new MidInterpreter().UseAllMessages(CuratedMids).Parse(Packages.Mid0061Rev1); + + [BenchmarkCategory(ColdStart)] + [Benchmark(Description = "UseTighteningMessages() + first parse")] + public Mid ColdStartSingleFamily() => new MidInterpreter().UseTighteningMessages().Parse(Packages.Mid0061Rev1); + } +} From 79b8e3be876aa3d14d9b6642b8151fe05e92be71 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 9 Aug 2026 00:24:44 -0300 Subject: [PATCH 77/78] Removed benchmarkdotnet from test project --- .github/workflows/dotnetcore.yml | 8 ++++---- src/MIDTesters.Core/MIDTesters.Core.csproj | 1 - src/MIDTesters.Core/TestCollectionInitialization.cs | 3 +-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index bfe6fb00..707186f9 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -1,6 +1,6 @@ name: dotnetcore -on: +on: pull_request: types: [opened, synchronize] push: @@ -15,7 +15,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@master with: - dotnet-version: | + dotnet-version: | 3.1.x 6.0.x 8.0.x @@ -29,7 +29,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@master with: - dotnet-version: | + dotnet-version: | 3.1.x 6.0.x 8.0.x @@ -37,4 +37,4 @@ jobs: - name: Build and run .net core tests run: | dotnet build src/MIDTesters.Core/MIDTesters.Core.csproj - dotnet test src/MIDTesters.Core/MIDTesters.Core.csproj \ No newline at end of file + dotnet test src/MIDTesters.Core/MIDTesters.Core.csproj diff --git a/src/MIDTesters.Core/MIDTesters.Core.csproj b/src/MIDTesters.Core/MIDTesters.Core.csproj index 293c01c7..f985dc5a 100644 --- a/src/MIDTesters.Core/MIDTesters.Core.csproj +++ b/src/MIDTesters.Core/MIDTesters.Core.csproj @@ -7,7 +7,6 @@ - diff --git a/src/MIDTesters.Core/TestCollectionInitialization.cs b/src/MIDTesters.Core/TestCollectionInitialization.cs index 8ef9bad9..d3b6fc65 100644 --- a/src/MIDTesters.Core/TestCollectionInitialization.cs +++ b/src/MIDTesters.Core/TestCollectionInitialization.cs @@ -50,8 +50,7 @@ private static void AssertCollectionsAreInitialized(Type baseType) } } - Assert.AreEqual(0, failures.Count, - $"{failures.Count} collection(s) are left uninitialized:{Environment.NewLine}{string.Join(Environment.NewLine, failures)}"); + Assert.AreEqual(0, failures.Count, $"{failures.Count} collection(s) are left uninitialized:{Environment.NewLine}{string.Join(Environment.NewLine, failures)}"); } private static IEnumerable GetCollectionProperties(Type type) From 837647e4d228094a0acf5a9f82e54262cbadb978 Mon Sep 17 00:00:00 2001 From: Henrique Dal Bello Date: Sun, 9 Aug 2026 00:26:08 -0300 Subject: [PATCH 78/78] updated version to 6.2.0 --- .github/workflows/release.yml | 6 +++--- src/OpenProtocolInterpreter/OpenProtocolInterpreter.csproj | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d2d31a46..f2d3b578 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@master with: - dotnet-version: | + dotnet-version: | 3.1.x 6.0.x 8.0.x @@ -27,7 +27,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@master with: - dotnet-version: | + dotnet-version: | 3.1.x 6.0.x 8.0.x @@ -48,4 +48,4 @@ jobs: - name: Publish NuGet run: | dotnet build src/OpenProtocolInterpreter/OpenProtocolInterpreter.csproj --configuration release - nuget push src/OpenProtocolInterpreter/bin/release/OpenProtocolInterpreter.6.1.2.nupkg -Source https://api.nuget.org/v3/index.json -SkipDuplicate \ No newline at end of file + nuget push src/OpenProtocolInterpreter/bin/release/OpenProtocolInterpreter.6.2.0.nupkg -Source https://api.nuget.org/v3/index.json -SkipDuplicate diff --git a/src/OpenProtocolInterpreter/OpenProtocolInterpreter.csproj b/src/OpenProtocolInterpreter/OpenProtocolInterpreter.csproj index 156de07e..ec52e964 100644 --- a/src/OpenProtocolInterpreter/OpenProtocolInterpreter.csproj +++ b/src/OpenProtocolInterpreter/OpenProtocolInterpreter.csproj @@ -3,7 +3,7 @@ netstandard2.0;net6.0;net8.0;net10.0 latest - 6.1.2 + 6.2.0 Converts the ugly package that came from Open Protocol to an object Copyright © 2026 Henrique Dal Bello Batista @@ -13,7 +13,7 @@ MIT CSharp Open Protocol Atlas Copco true - See release notes at: https://github.com/Rickedb/OpenProtocolInterpreter/releases/tag/6.1.0 + See release notes at: https://github.com/Rickedb/OpenProtocolInterpreter/releases/tag/6.2.0 icon.png

- - Buy me a coffee - - - - - - - - - MIT - -

&47`E@)e$r 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 z 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