From 8ab456bd85933913c22db926e33194b67e1d2b3d Mon Sep 17 00:00:00 2001 From: SeppPenner Date: Tue, 20 Feb 2024 08:36:23 +0100 Subject: [PATCH] Added Sparkplug spec version to differentiate between state messages (Maybe later, more things need to be checked). Fixes https://github.com/SeppPenner/SparkplugNet/issues/68 partially --> Issue 3 done. --- ...SparkplugMessageGeneratorTestVersion22.cs} | 27 +- .../SparkplugMessageGeneratorTestVersion30.cs | 483 ++++++++++++++++++ .../Application/SparkplugApplicationBase.cs | 18 +- .../SparkplugSpecificationVersion.cs | 17 + .../Messages/SparkplugMessageGenerator.cs | 51 +- .../Core/Node/SparkplugNodeBase.Device.cs | 18 +- .../Core/Node/SparkplugNodeBase.cs | 24 +- src/SparkplugNet/Core/SparkplugBase.cs | 18 +- src/SparkplugNet/GlobalUsings.cs | 1 + src/SparkplugNet/VersionA/Data/KuraMetric.cs | 12 + .../VersionA/SparkplugApplication.cs | 4 +- src/SparkplugNet/VersionA/SparkplugNode.cs | 4 +- src/SparkplugNet/VersionB/Data/Metric.cs | 66 ++- src/SparkplugNet/VersionB/PayloadConverter.cs | 1 + .../VersionB/SparkplugApplication.cs | 4 +- src/SparkplugNet/VersionB/SparkplugNode.cs | 4 +- 16 files changed, 647 insertions(+), 105 deletions(-) rename src/SparkplugNet.Tests/{SparkplugMessageGeneratorTest.cs => SparkplugMessageGeneratorTestVersion22.cs} (94%) create mode 100644 src/SparkplugNet.Tests/SparkplugMessageGeneratorTestVersion30.cs create mode 100644 src/SparkplugNet/Core/Enumerations/SparkplugSpecificationVersion.cs diff --git a/src/SparkplugNet.Tests/SparkplugMessageGeneratorTest.cs b/src/SparkplugNet.Tests/SparkplugMessageGeneratorTestVersion22.cs similarity index 94% rename from src/SparkplugNet.Tests/SparkplugMessageGeneratorTest.cs rename to src/SparkplugNet.Tests/SparkplugMessageGeneratorTestVersion22.cs index 0517017..3eb613f 100644 --- a/src/SparkplugNet.Tests/SparkplugMessageGeneratorTest.cs +++ b/src/SparkplugNet.Tests/SparkplugMessageGeneratorTestVersion22.cs @@ -1,19 +1,19 @@ // -------------------------------------------------------------------------------------------------------------------- -// +// // The project is licensed under the MIT license. // // -// A class to test the class. +// A class to test the class with specification version 2.2. // // -------------------------------------------------------------------------------------------------------------------- namespace SparkplugNet.Tests; /// -/// A class to test the class. +/// A class to test the class with specification version 2.2. /// [TestClass] -public class SparkplugMessageGeneratorTest +public class SparkplugMessageGeneratorTestVersion22 { /// /// The metrics for namespace A. @@ -64,7 +64,8 @@ public class SparkplugMessageGeneratorTest /// /// The message generator. /// - private readonly SparkplugMessageGenerator messageGenerator = new(new LoggerConfiguration().WriteTo.Console().CreateLogger()); + private readonly SparkplugMessageGenerator messageGenerator = new(new LoggerConfiguration().WriteTo.Console().CreateLogger(), + SparkplugSpecificationVersion.Version22); /// /// Tests the Sparkplug message generator with a message with a version A namespace and a online state. @@ -72,7 +73,7 @@ public class SparkplugMessageGeneratorTest [TestMethod] public void TestStateMessageNamespaceAOnline() { - var message = SparkplugMessageGenerator.GetSparkplugStateMessage(SparkplugNamespace.VersionA, "scada1", true); + var message = this.messageGenerator.GetSparkplugStateMessage(SparkplugNamespace.VersionA, "scada1", true); Assert.AreEqual("STATE/scada1", message.Topic); Assert.AreEqual("ONLINE", message.ConvertPayloadToString()); @@ -84,7 +85,7 @@ public void TestStateMessageNamespaceAOnline() [TestMethod] public void TestStateMessageNamespaceAOffline() { - var message = SparkplugMessageGenerator.GetSparkplugStateMessage(SparkplugNamespace.VersionA, "scada1", false); + var message = this.messageGenerator.GetSparkplugStateMessage(SparkplugNamespace.VersionA, "scada1", false); Assert.AreEqual("STATE/scada1", message.Topic); Assert.AreEqual("OFFLINE", message.ConvertPayloadToString()); @@ -96,7 +97,7 @@ public void TestStateMessageNamespaceAOffline() [TestMethod] public void TestStateMessageNamespaceBOnline() { - var message = SparkplugMessageGenerator.GetSparkplugStateMessage(SparkplugNamespace.VersionB, "scada1", true); + var message = this.messageGenerator.GetSparkplugStateMessage(SparkplugNamespace.VersionB, "scada1", true); Assert.AreEqual("STATE/scada1", message.Topic); Assert.AreEqual("ONLINE", message.ConvertPayloadToString()); @@ -108,7 +109,7 @@ public void TestStateMessageNamespaceBOnline() [TestMethod] public void TestStateMessageNamespaceBOffline() { - var message = SparkplugMessageGenerator.GetSparkplugStateMessage(SparkplugNamespace.VersionB, "scada1", false); + var message = this.messageGenerator.GetSparkplugStateMessage(SparkplugNamespace.VersionB, "scada1", false); Assert.AreEqual("STATE/scada1", message.Topic); Assert.AreEqual("OFFLINE", message.ConvertPayloadToString()); @@ -389,7 +390,7 @@ public void TestNodeDataMessageNamespaceB() public void TestDeviceCommandMessageNamespaceA() { var dateTime = DateTimeOffset.Now; - var message = SparkplugMessageGenerator.GetSparkPlugDeviceCommandMessage(SparkplugNamespace.VersionA, "group1", "edge1", "device1", this.metricsA, 0, 1, dateTime); + var message = this.messageGenerator.GetSparkPlugDeviceCommandMessage(SparkplugNamespace.VersionA, "group1", "edge1", "device1", this.metricsA, 0, 1, dateTime); var payloadVersionA = PayloadHelper.Deserialize(message.Payload); Assert.AreEqual("spAv1.0/group1/DCMD/edge1/device1", message.Topic); @@ -413,7 +414,7 @@ public void TestDeviceCommandMessageNamespaceA() public void TestDeviceCommandMessageNamespaceB() { var dateTime = DateTimeOffset.UtcNow; - var message = SparkplugMessageGenerator.GetSparkPlugDeviceCommandMessage(SparkplugNamespace.VersionB, "group1", "edge1", "device1", this.metricsB, 0, 1, dateTime); + var message = this.messageGenerator.GetSparkPlugDeviceCommandMessage(SparkplugNamespace.VersionB, "group1", "edge1", "device1", this.metricsB, 0, 1, dateTime); var payloadVersionB = PayloadHelper.Deserialize(message.Payload); Assert.AreEqual("spBv1.0/group1/DCMD/edge1/device1", message.Topic); @@ -437,7 +438,7 @@ public void TestDeviceCommandMessageNamespaceB() public void TestNodeCommandMessageNamespaceA() { var dateTime = DateTimeOffset.Now; - var message = SparkplugMessageGenerator.GetSparkPlugNodeCommandMessage(SparkplugNamespace.VersionA, "group1", "edge1", this.metricsA, 0, 1, dateTime); + var message = this.messageGenerator.GetSparkPlugNodeCommandMessage(SparkplugNamespace.VersionA, "group1", "edge1", this.metricsA, 0, 1, dateTime); var payloadVersionA = PayloadHelper.Deserialize(message.Payload); Assert.AreEqual("spAv1.0/group1/NCMD/edge1", message.Topic); @@ -461,7 +462,7 @@ public void TestNodeCommandMessageNamespaceA() public void TestNodeCommandMessageNamespaceB() { var dateTime = DateTimeOffset.UtcNow; - var message = SparkplugMessageGenerator.GetSparkPlugNodeCommandMessage(SparkplugNamespace.VersionB, "group1", "edge1", this.metricsB, 0, 1, dateTime); + var message = this.messageGenerator.GetSparkPlugNodeCommandMessage(SparkplugNamespace.VersionB, "group1", "edge1", this.metricsB, 0, 1, dateTime); var payloadVersionB = PayloadHelper.Deserialize(message.Payload); Assert.AreEqual("spBv1.0/group1/NCMD/edge1", message.Topic); diff --git a/src/SparkplugNet.Tests/SparkplugMessageGeneratorTestVersion30.cs b/src/SparkplugNet.Tests/SparkplugMessageGeneratorTestVersion30.cs new file mode 100644 index 0000000..c10b35f --- /dev/null +++ b/src/SparkplugNet.Tests/SparkplugMessageGeneratorTestVersion30.cs @@ -0,0 +1,483 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// The project is licensed under the MIT license. +// +// +// A class to test the class with specification version 3.0. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace SparkplugNet.Tests; + +/// +/// A class to test the class with specification version 3.0. +/// +[TestClass] +public class SparkplugMessageGeneratorTestVersion30 +{ + /// + /// The metrics for namespace A. + /// + private readonly List metricsA = new() + { + new VersionA.Data.KuraMetric + { + Name = "Test", + BooleanValue = true, + DataType = VersionA.Data.DataType.Boolean + } + }; + + /// + /// The metrics for namespace B. + /// + private readonly List metricsB = new() + { + new VersionB.Data.Metric + { + Name = "Test", + ValueCase = (uint)VersionB.Data.DataType.Int32, + IntValue = 20 + } + }; + + /// + /// The SEQ metric for namespace A. + /// + private readonly VersionA.Data.KuraMetric seqMetricA = new() + { + Name = Constants.SessionNumberMetricName, + LongValue = 1, + DataType = VersionA.Data.DataType.Int64 + }; + + /// + /// The SEQ metric for namespace B. + /// + private readonly VersionB.Data.Metric seqMetricB = new() + { + Name = Constants.SessionNumberMetricName, + LongValue = 1, + ValueCase = (uint)VersionB.Data.DataType.Int64 + }; + + /// + /// The message generator. + /// + private readonly SparkplugMessageGenerator messageGenerator = new(new LoggerConfiguration().WriteTo.Console().CreateLogger(), + SparkplugSpecificationVersion.Version30); + + /// + /// Tests the Sparkplug message generator with a message with a version A namespace and a online state. + /// + [TestMethod] + public void TestStateMessageNamespaceAOnline() + { + var message = this.messageGenerator.GetSparkplugStateMessage(SparkplugNamespace.VersionA, "scada1", true); + + Assert.AreEqual("STATE/scada1", message.Topic); + Assert.AreEqual("ONLINE", message.ConvertPayloadToString()); + } + + /// + /// Tests the Sparkplug message generator with a message with a version A namespace and a offline state. + /// + [TestMethod] + public void TestStateMessageNamespaceAOffline() + { + var message = this.messageGenerator.GetSparkplugStateMessage(SparkplugNamespace.VersionA, "scada1", false); + + Assert.AreEqual("STATE/scada1", message.Topic); + Assert.AreEqual("OFFLINE", message.ConvertPayloadToString()); + } + + /// + /// Tests the Sparkplug message generator with a message with a version B namespace and a online state. + /// + [TestMethod] + public void TestStateMessageNamespaceBOnline() + { + // Todo: Fix this! + var message = this.messageGenerator.GetSparkplugStateMessage(SparkplugNamespace.VersionB, "scada1", true); + + Assert.AreEqual("STATE/scada1", message.Topic); + Assert.AreEqual("ONLINE", message.ConvertPayloadToString()); + } + + /// + /// Tests the Sparkplug message generator with a message with a version B namespace and a offline state. + /// + [TestMethod] + public void TestStateMessageNamespaceBOffline() + { + // Todo: Fix this! + var message = this.messageGenerator.GetSparkplugStateMessage(SparkplugNamespace.VersionB, "scada1", false); + + Assert.AreEqual("STATE/scada1", message.Topic); + Assert.AreEqual("OFFLINE", message.ConvertPayloadToString()); + } + + /// + /// Tests the Sparkplug message generator with a device birth message with a version A namespace. + /// + [TestMethod] + public void TestDeviceBirthMessageNamespaceA() + { + var dateTime = DateTimeOffset.Now; + var message = this.messageGenerator.GetSparkPlugDeviceBirthMessage(SparkplugNamespace.VersionA, "group1", "edge1", "device1", this.metricsA, 0, 1, dateTime); + var payloadVersionA = PayloadHelper.Deserialize(message.Payload); + + Assert.AreEqual("spAv1.0/group1/DBIRTH/edge1/device1", message.Topic); + Assert.IsNotNull(payloadVersionA); + Assert.AreEqual(dateTime.ToUnixTimeMilliseconds(), payloadVersionA.Timestamp); + Assert.AreEqual(2, payloadVersionA.Metrics.Count); + + Assert.AreEqual(this.metricsA.First().Name, payloadVersionA.Metrics.ElementAt(0).Name); + Assert.AreEqual(this.metricsA.First().BooleanValue, payloadVersionA.Metrics.ElementAt(0).BooleanValue); + Assert.AreEqual(this.metricsA.First().DataType, VersionA.PayloadConverter.ConvertVersionADataType(payloadVersionA.Metrics.ElementAt(0).DataType)); + + Assert.AreEqual(this.seqMetricA.Name, payloadVersionA.Metrics.ElementAt(1).Name); + Assert.AreEqual(this.seqMetricA.LongValue, payloadVersionA.Metrics.ElementAt(1).LongValue); + Assert.AreEqual(this.seqMetricA.DataType, VersionA.PayloadConverter.ConvertVersionADataType(payloadVersionA.Metrics.ElementAt(1).DataType)); + } + + /// + /// Tests the Sparkplug message generator with a device birth message with a version B namespace. + /// + [TestMethod] + public void TestDeviceBirthMessageNamespaceB() + { + var dateTime = DateTimeOffset.UtcNow; + var message = this.messageGenerator.GetSparkPlugDeviceBirthMessage(SparkplugNamespace.VersionB, "group1", "edge1", "device1", this.metricsB, 0, 1, dateTime); + var payloadVersionB = PayloadHelper.Deserialize(message.Payload); + + Assert.AreEqual("spBv1.0/group1/DBIRTH/edge1/device1", message.Topic); + Assert.IsNotNull(payloadVersionB); + Assert.AreEqual((ulong)dateTime.ToUnixTimeMilliseconds(), payloadVersionB.Timestamp); + Assert.AreEqual(2, payloadVersionB.Metrics.Count); + + Assert.AreEqual(this.metricsB.First().Name, payloadVersionB.Metrics.ElementAt(0).Name); + Assert.AreEqual(this.metricsB.First().IntValue, payloadVersionB.Metrics.ElementAt(0).IntValue); + Assert.AreEqual(this.metricsB.First().ValueCase, payloadVersionB.Metrics.ElementAt(0).DataType); + + Assert.AreEqual(this.seqMetricB.Name, payloadVersionB.Metrics.ElementAt(1).Name); + Assert.AreEqual(this.seqMetricB.LongValue, payloadVersionB.Metrics.ElementAt(1).LongValue); + Assert.AreEqual(this.seqMetricB.ValueCase, payloadVersionB.Metrics.ElementAt(1).DataType); + } + + /// + /// Tests the Sparkplug message generator with a node birth message with a version A namespace. + /// + [TestMethod] + public void TestNodeBirthMessageNamespaceA() + { + var dateTime = DateTimeOffset.Now; + var message = this.messageGenerator.GetSparkPlugNodeBirthMessage(SparkplugNamespace.VersionA, "group1", "edge1", this.metricsA, 0, 1, dateTime); + var payloadVersionA = PayloadHelper.Deserialize(message.Payload); + + Assert.AreEqual("spAv1.0/group1/NBIRTH/edge1", message.Topic); + Assert.IsNotNull(payloadVersionA); + Assert.AreEqual(dateTime.ToUnixTimeMilliseconds(), payloadVersionA.Timestamp); + Assert.AreEqual(2, payloadVersionA.Metrics.Count); + + Assert.AreEqual(this.metricsA.First().Name, payloadVersionA.Metrics.ElementAt(0).Name); + Assert.AreEqual(this.metricsA.First().BooleanValue, payloadVersionA.Metrics.ElementAt(0).BooleanValue); + Assert.AreEqual(this.metricsA.First().DataType, VersionA.PayloadConverter.ConvertVersionADataType(payloadVersionA.Metrics.ElementAt(0).DataType)); + + Assert.AreEqual(this.seqMetricA.Name, payloadVersionA.Metrics.ElementAt(1).Name); + Assert.AreEqual(this.seqMetricA.LongValue, payloadVersionA.Metrics.ElementAt(1).LongValue); + Assert.AreEqual(this.seqMetricA.DataType, VersionA.PayloadConverter.ConvertVersionADataType(payloadVersionA.Metrics.ElementAt(1).DataType)); + } + + /// + /// Tests the Sparkplug message generator with a node birth message with a version B namespace. + /// + [TestMethod] + public void TestNodeBirthMessageNamespaceB() + { + var dateTime = DateTimeOffset.UtcNow; + var message = this.messageGenerator.GetSparkPlugNodeBirthMessage(SparkplugNamespace.VersionB, "group1", "edge1", this.metricsB, 0, 1, dateTime); + var payloadVersionB = PayloadHelper.Deserialize(message.Payload); + + Assert.AreEqual("spBv1.0/group1/NBIRTH/edge1", message.Topic); + Assert.IsNotNull(payloadVersionB); + Assert.AreEqual((ulong)dateTime.ToUnixTimeMilliseconds(), payloadVersionB.Timestamp); + Assert.AreEqual(2, payloadVersionB.Metrics.Count); + + Assert.AreEqual(this.metricsB.First().Name, payloadVersionB.Metrics.ElementAt(0).Name); + Assert.AreEqual(this.metricsB.First().IntValue, payloadVersionB.Metrics.ElementAt(0).IntValue); + Assert.AreEqual(this.metricsB.First().ValueCase, payloadVersionB.Metrics.ElementAt(0).DataType); + + Assert.AreEqual(this.seqMetricB.Name, payloadVersionB.Metrics.ElementAt(1).Name); + Assert.AreEqual(this.seqMetricB.LongValue, payloadVersionB.Metrics.ElementAt(1).LongValue); + Assert.AreEqual(this.seqMetricB.ValueCase, payloadVersionB.Metrics.ElementAt(1).DataType); + } + + /// + /// Tests the Sparkplug message generator with a device death message with a version A namespace. + /// + [TestMethod] + public void TestDeviceDeathMessageNamespaceA() + { + var dateTime = DateTimeOffset.Now; + var message = this.messageGenerator.GetSparkPlugDeviceDeathMessage(SparkplugNamespace.VersionA, "group1", "edge1", "device1", 0, 1, dateTime); + var payloadVersionA = PayloadHelper.Deserialize(message.Payload); + + Assert.AreEqual("spAv1.0/group1/DDEATH/edge1/device1", message.Topic); + Assert.IsNotNull(payloadVersionA); + Assert.AreEqual(dateTime.ToUnixTimeMilliseconds(), payloadVersionA.Timestamp); + Assert.AreEqual(1, payloadVersionA.Metrics.Count); + + Assert.AreEqual(this.seqMetricA.Name, payloadVersionA.Metrics.ElementAt(0).Name); + Assert.AreEqual(this.seqMetricA.LongValue, payloadVersionA.Metrics.ElementAt(0).LongValue); + Assert.AreEqual(this.seqMetricA.DataType, VersionA.PayloadConverter.ConvertVersionADataType(payloadVersionA.Metrics.ElementAt(0).DataType)); + } + + /// + /// Tests the Sparkplug message generator with a device death message with a version B namespace. + /// + [TestMethod] + public void TestDeviceDeathMessageNamespaceB() + { + var dateTime = DateTimeOffset.UtcNow; + var message = this.messageGenerator.GetSparkPlugDeviceDeathMessage(SparkplugNamespace.VersionB, "group1", "edge1", "device1", 0, 1, dateTime); + var payloadVersionB = PayloadHelper.Deserialize(message.Payload); + + Assert.AreEqual("spBv1.0/group1/DDEATH/edge1/device1", message.Topic); + Assert.IsNotNull(payloadVersionB); + Assert.AreEqual((ulong)dateTime.ToUnixTimeMilliseconds(), payloadVersionB.Timestamp); + Assert.AreEqual(1, payloadVersionB.Metrics.Count); + + Assert.AreEqual(this.seqMetricB.Name, payloadVersionB.Metrics.ElementAt(0).Name); + Assert.AreEqual(this.seqMetricB.LongValue, payloadVersionB.Metrics.ElementAt(0).LongValue); + Assert.AreEqual(this.seqMetricB.ValueCase, payloadVersionB.Metrics.ElementAt(0).DataType); + } + + /// + /// Tests the Sparkplug message generator with a node death message with a version A namespace. + /// + [TestMethod] + public void TestNodeDeathMessageNamespaceA() + { + var message = this.messageGenerator.GetSparkPlugNodeDeathMessage(SparkplugNamespace.VersionA, "group1", "edge1", 1); + var payloadVersionA = PayloadHelper.Deserialize(message.Payload); + + Assert.AreEqual("spAv1.0/group1/NDEATH/edge1", message.Topic); + Assert.IsNotNull(payloadVersionA); + Assert.AreEqual(1, payloadVersionA.Metrics.Count); + + Assert.AreEqual(this.seqMetricA.Name, payloadVersionA.Metrics.ElementAt(0).Name); + Assert.AreEqual(this.seqMetricA.LongValue, payloadVersionA.Metrics.ElementAt(0).LongValue); + Assert.AreEqual(this.seqMetricA.DataType, VersionA.PayloadConverter.ConvertVersionADataType(payloadVersionA.Metrics.ElementAt(0).DataType)); + } + + /// + /// Tests the Sparkplug message generator with a node death message with a version B namespace. + /// + [TestMethod] + public void TestNodeDeathMessageNamespaceB() + { + var message = this.messageGenerator.GetSparkPlugNodeDeathMessage(SparkplugNamespace.VersionB, "group1", "edge1", 1); + var payloadVersionB = PayloadHelper.Deserialize(message.Payload); + + Assert.AreEqual("spBv1.0/group1/NDEATH/edge1", message.Topic); + Assert.IsNotNull(payloadVersionB); + Assert.AreEqual(1, payloadVersionB.Metrics.Count); + + Assert.AreEqual(this.seqMetricB.Name, payloadVersionB.Metrics.ElementAt(0).Name); + Assert.AreEqual(this.seqMetricB.LongValue, payloadVersionB.Metrics.ElementAt(0).LongValue); + Assert.AreEqual(this.seqMetricB.ValueCase, payloadVersionB.Metrics.ElementAt(0).DataType); + } + + /// + /// Tests the Sparkplug message generator with a device data message with a version A namespace. + /// + [TestMethod] + public void TestDeviceDataMessageNamespaceA() + { + var dateTime = DateTimeOffset.Now; + var message = this.messageGenerator.GetSparkPlugDeviceDataMessage(SparkplugNamespace.VersionA, "group1", "edge1", "device1", this.metricsA, 0, 1, dateTime); + var payloadVersionA = PayloadHelper.Deserialize(message.Payload); + + Assert.AreEqual("spAv1.0/group1/DDATA/edge1/device1", message.Topic); + Assert.IsNotNull(payloadVersionA); + Assert.AreEqual(dateTime.ToUnixTimeMilliseconds(), payloadVersionA.Timestamp); + Assert.AreEqual(2, payloadVersionA.Metrics.Count); + + Assert.AreEqual(this.metricsA.First().Name, payloadVersionA.Metrics.ElementAt(0).Name); + Assert.AreEqual(this.metricsA.First().BooleanValue, payloadVersionA.Metrics.ElementAt(0).BooleanValue); + Assert.AreEqual(this.metricsA.First().DataType, VersionA.PayloadConverter.ConvertVersionADataType(payloadVersionA.Metrics.ElementAt(0).DataType)); + + Assert.AreEqual(this.seqMetricA.Name, payloadVersionA.Metrics.ElementAt(1).Name); + Assert.AreEqual(this.seqMetricA.LongValue, payloadVersionA.Metrics.ElementAt(1).LongValue); + Assert.AreEqual(this.seqMetricA.DataType, VersionA.PayloadConverter.ConvertVersionADataType(payloadVersionA.Metrics.ElementAt(1).DataType)); + } + + /// + /// Tests the Sparkplug message generator with a device data message with a version B namespace. + /// + [TestMethod] + public void TestDeviceDataMessageNamespaceB() + { + var dateTime = DateTimeOffset.UtcNow; + var message = this.messageGenerator.GetSparkPlugDeviceDataMessage(SparkplugNamespace.VersionB, "group1", "edge1", "device1", this.metricsB, 0, 1, dateTime); + var payloadVersionB = PayloadHelper.Deserialize(message.Payload); + + Assert.AreEqual("spBv1.0/group1/DDATA/edge1/device1", message.Topic); + Assert.IsNotNull(payloadVersionB); + Assert.AreEqual((ulong)dateTime.ToUnixTimeMilliseconds(), payloadVersionB.Timestamp); + Assert.AreEqual(2, payloadVersionB.Metrics.Count); + + Assert.AreEqual(this.metricsB.First().Name, payloadVersionB.Metrics.ElementAt(0).Name); + Assert.AreEqual(this.metricsB.First().IntValue, payloadVersionB.Metrics.ElementAt(0).IntValue); + Assert.AreEqual(this.metricsB.First().ValueCase, payloadVersionB.Metrics.ElementAt(0).DataType); + + Assert.AreEqual(this.seqMetricB.Name, payloadVersionB.Metrics.ElementAt(1).Name); + Assert.AreEqual(this.seqMetricB.LongValue, payloadVersionB.Metrics.ElementAt(1).LongValue); + Assert.AreEqual(this.seqMetricB.ValueCase, payloadVersionB.Metrics.ElementAt(1).DataType); + } + + /// + /// Tests the Sparkplug message generator with a node data message with a version A namespace. + /// + [TestMethod] + public void TestNodeDataMessageNamespaceA() + { + var dateTime = DateTimeOffset.Now; + var message = this.messageGenerator.GetSparkPlugNodeDataMessage(SparkplugNamespace.VersionA, "group1", "edge1", this.metricsA, 0, 1, dateTime); + var payloadVersionA = PayloadHelper.Deserialize(message.Payload); + + Assert.AreEqual("spAv1.0/group1/NDATA/edge1", message.Topic); + Assert.IsNotNull(payloadVersionA); + Assert.AreEqual(dateTime.ToUnixTimeMilliseconds(), payloadVersionA.Timestamp); + Assert.AreEqual(2, payloadVersionA.Metrics.Count); + + Assert.AreEqual(this.metricsA.First().Name, payloadVersionA.Metrics.ElementAt(0).Name); + Assert.AreEqual(this.metricsA.First().BooleanValue, payloadVersionA.Metrics.ElementAt(0).BooleanValue); + Assert.AreEqual(this.metricsA.First().DataType, VersionA.PayloadConverter.ConvertVersionADataType(payloadVersionA.Metrics.ElementAt(0).DataType)); + + Assert.AreEqual(this.seqMetricA.Name, payloadVersionA.Metrics.ElementAt(1).Name); + Assert.AreEqual(this.seqMetricA.LongValue, payloadVersionA.Metrics.ElementAt(1).LongValue); + Assert.AreEqual(this.seqMetricA.DataType, VersionA.PayloadConverter.ConvertVersionADataType(payloadVersionA.Metrics.ElementAt(1).DataType)); + } + + /// + /// Tests the Sparkplug message generator with a node data message with a version B namespace. + /// + [TestMethod] + public void TestNodeDataMessageNamespaceB() + { + var dateTime = DateTimeOffset.UtcNow; + var message = this.messageGenerator.GetSparkPlugNodeDataMessage(SparkplugNamespace.VersionB, "group1", "edge1", this.metricsB, 0, 1, dateTime); + var payloadVersionB = PayloadHelper.Deserialize(message.Payload); + + Assert.AreEqual("spBv1.0/group1/NDATA/edge1", message.Topic); + Assert.IsNotNull(payloadVersionB); + Assert.AreEqual((ulong)dateTime.ToUnixTimeMilliseconds(), payloadVersionB.Timestamp); + Assert.AreEqual(2, payloadVersionB.Metrics.Count); + + Assert.AreEqual(this.metricsB.First().Name, payloadVersionB.Metrics.ElementAt(0).Name); + Assert.AreEqual(this.metricsB.First().IntValue, payloadVersionB.Metrics.ElementAt(0).IntValue); + Assert.AreEqual(this.metricsB.First().ValueCase, payloadVersionB.Metrics.ElementAt(0).DataType); + + Assert.AreEqual(this.seqMetricB.Name, payloadVersionB.Metrics.ElementAt(1).Name); + Assert.AreEqual(this.seqMetricB.LongValue, payloadVersionB.Metrics.ElementAt(1).LongValue); + Assert.AreEqual(this.seqMetricB.ValueCase, payloadVersionB.Metrics.ElementAt(1).DataType); + } + + /// + /// Tests the Sparkplug message generator with a device command message with a version A namespace. + /// + [TestMethod] + public void TestDeviceCommandMessageNamespaceA() + { + var dateTime = DateTimeOffset.Now; + var message = this.messageGenerator.GetSparkPlugDeviceCommandMessage(SparkplugNamespace.VersionA, "group1", "edge1", "device1", this.metricsA, 0, 1, dateTime); + var payloadVersionA = PayloadHelper.Deserialize(message.Payload); + + Assert.AreEqual("spAv1.0/group1/DCMD/edge1/device1", message.Topic); + Assert.IsNotNull(payloadVersionA); + Assert.AreEqual(dateTime.ToUnixTimeMilliseconds(), payloadVersionA.Timestamp); + Assert.AreEqual(2, payloadVersionA.Metrics.Count); + + Assert.AreEqual(this.metricsA.First().Name, payloadVersionA.Metrics.ElementAt(0).Name); + Assert.AreEqual(this.metricsA.First().BooleanValue, payloadVersionA.Metrics.ElementAt(0).BooleanValue); + Assert.AreEqual(this.metricsA.First().DataType, VersionA.PayloadConverter.ConvertVersionADataType(payloadVersionA.Metrics.ElementAt(0).DataType)); + + Assert.AreEqual(this.seqMetricA.Name, payloadVersionA.Metrics.ElementAt(1).Name); + Assert.AreEqual(this.seqMetricA.LongValue, payloadVersionA.Metrics.ElementAt(1).LongValue); + Assert.AreEqual(this.seqMetricA.DataType, VersionA.PayloadConverter.ConvertVersionADataType(payloadVersionA.Metrics.ElementAt(1).DataType)); + } + + /// + /// Tests the Sparkplug message generator with a device command message with a version B namespace. + /// + [TestMethod] + public void TestDeviceCommandMessageNamespaceB() + { + var dateTime = DateTimeOffset.UtcNow; + var message = this.messageGenerator.GetSparkPlugDeviceCommandMessage(SparkplugNamespace.VersionB, "group1", "edge1", "device1", this.metricsB, 0, 1, dateTime); + var payloadVersionB = PayloadHelper.Deserialize(message.Payload); + + Assert.AreEqual("spBv1.0/group1/DCMD/edge1/device1", message.Topic); + Assert.IsNotNull(payloadVersionB); + Assert.AreEqual((ulong)dateTime.ToUnixTimeMilliseconds(), payloadVersionB.Timestamp); + Assert.AreEqual(2, payloadVersionB.Metrics.Count); + + Assert.AreEqual(this.metricsB.First().Name, payloadVersionB.Metrics.ElementAt(0).Name); + Assert.AreEqual(this.metricsB.First().IntValue, payloadVersionB.Metrics.ElementAt(0).IntValue); + Assert.AreEqual(this.metricsB.First().ValueCase, payloadVersionB.Metrics.ElementAt(0).DataType); + + Assert.AreEqual(this.seqMetricB.Name, payloadVersionB.Metrics.ElementAt(1).Name); + Assert.AreEqual(this.seqMetricB.LongValue, payloadVersionB.Metrics.ElementAt(1).LongValue); + Assert.AreEqual(this.seqMetricB.ValueCase, payloadVersionB.Metrics.ElementAt(1).DataType); + } + + /// + /// Tests the Sparkplug message generator with a node command message with a version A namespace. + /// + [TestMethod] + public void TestNodeCommandMessageNamespaceA() + { + var dateTime = DateTimeOffset.Now; + var message = this.messageGenerator.GetSparkPlugNodeCommandMessage(SparkplugNamespace.VersionA, "group1", "edge1", this.metricsA, 0, 1, dateTime); + var payloadVersionA = PayloadHelper.Deserialize(message.Payload); + + Assert.AreEqual("spAv1.0/group1/NCMD/edge1", message.Topic); + Assert.IsNotNull(payloadVersionA); + Assert.AreEqual(dateTime.ToUnixTimeMilliseconds(), payloadVersionA.Timestamp); + Assert.AreEqual(2, payloadVersionA.Metrics.Count); + + Assert.AreEqual(this.metricsA.First().Name, payloadVersionA.Metrics.ElementAt(0).Name); + Assert.AreEqual(this.metricsA.First().BooleanValue, payloadVersionA.Metrics.ElementAt(0).BooleanValue); + Assert.AreEqual(this.metricsA.First().DataType, VersionA.PayloadConverter.ConvertVersionADataType(payloadVersionA.Metrics.ElementAt(0).DataType)); + + Assert.AreEqual(this.seqMetricA.Name, payloadVersionA.Metrics.ElementAt(1).Name); + Assert.AreEqual(this.seqMetricA.LongValue, payloadVersionA.Metrics.ElementAt(1).LongValue); + Assert.AreEqual(this.seqMetricA.DataType, VersionA.PayloadConverter.ConvertVersionADataType(payloadVersionA.Metrics.ElementAt(1).DataType)); + } + + /// + /// Tests the Sparkplug message generator with a node command message with a version B namespace. + /// + [TestMethod] + public void TestNodeCommandMessageNamespaceB() + { + var dateTime = DateTimeOffset.UtcNow; + var message = this.messageGenerator.GetSparkPlugNodeCommandMessage(SparkplugNamespace.VersionB, "group1", "edge1", this.metricsB, 0, 1, dateTime); + var payloadVersionB = PayloadHelper.Deserialize(message.Payload); + + Assert.AreEqual("spBv1.0/group1/NCMD/edge1", message.Topic); + Assert.IsNotNull(payloadVersionB); + Assert.AreEqual((ulong)dateTime.ToUnixTimeMilliseconds(), payloadVersionB.Timestamp); + Assert.AreEqual(2, payloadVersionB.Metrics.Count); + + Assert.AreEqual(this.metricsB.First().Name, payloadVersionB.Metrics.ElementAt(0).Name); + Assert.AreEqual(this.metricsB.First().IntValue, payloadVersionB.Metrics.ElementAt(0).IntValue); + Assert.AreEqual(this.metricsB.First().ValueCase, payloadVersionB.Metrics.ElementAt(0).DataType); + + Assert.AreEqual(this.seqMetricB.Name, payloadVersionB.Metrics.ElementAt(1).Name); + Assert.AreEqual(this.seqMetricB.LongValue, payloadVersionB.Metrics.ElementAt(1).LongValue); + Assert.AreEqual(this.seqMetricB.ValueCase, payloadVersionB.Metrics.ElementAt(1).DataType); + } +} diff --git a/src/SparkplugNet/Core/Application/SparkplugApplicationBase.cs b/src/SparkplugNet/Core/Application/SparkplugApplicationBase.cs index e952ecc..af16dd1 100644 --- a/src/SparkplugNet/Core/Application/SparkplugApplicationBase.cs +++ b/src/SparkplugNet/Core/Application/SparkplugApplicationBase.cs @@ -98,7 +98,7 @@ public async Task Start(SparkplugApplicationOptions applicationOptions) public async Task Stop() { this.IsRunning = false; - await this.Client.DisconnectAsync(); + await this.client.DisconnectAsync(); } /// @@ -119,7 +119,7 @@ public async Task PublishNodeCommand(IEnumerable metrics, string groupIdentif throw new ArgumentNullException(nameof(this.Options), "The options aren't set properly."); } - if (!this.Client.IsConnected) + if (!this.client.IsConnected) { throw new Exception("The MQTT client is not connected, please try again."); } @@ -156,7 +156,7 @@ public async Task PublishDeviceCommand(IEnumerable metrics, string groupIdent throw new ArgumentNullException(nameof(this.Options), "The options aren't set properly."); } - if (!this.Client.IsConnected) + if (!this.client.IsConnected) { throw new Exception("The MQTT client is not connected, please try again."); } @@ -276,8 +276,8 @@ protected virtual async Task OnClientDisconnectedAsync(MqttClientDisconnectedEve /// Thrown if the options are null. private void AddEventHandler() { - this.Client.DisconnectedAsync += this.OnClientDisconnectedAsync; - this.Client.ConnectedAsync += this.OnClientConnectedAsync; + this.client.DisconnectedAsync += this.OnClientDisconnectedAsync; + this.client.ConnectedAsync += this.OnClientConnectedAsync; } /// @@ -286,7 +286,7 @@ private void AddEventHandler() /// Thrown if the namespace is out of range. private void AddMessageReceivedHandler() { - this.Client.ApplicationMessageReceivedAsync += this.OnApplicationMessageReceived; + this.client.ApplicationMessageReceivedAsync += this.OnApplicationMessageReceived; } /// @@ -428,7 +428,7 @@ private async Task ConnectInternal() } this.ClientOptions = builder.Build(); - await this.Client.ConnectAsync(this.ClientOptions, this.Options.CancellationToken.Value); + await this.client.ConnectAsync(this.ClientOptions, this.Options.CancellationToken.Value); } catch (Exception ex) @@ -464,7 +464,7 @@ private async Task PublishInternal() // Publish message. this.Options.CancellationToken ??= SystemCancellationToken.None; - await this.Client.PublishAsync(onlineMessage, this.Options.CancellationToken.Value); + await this.client.PublishAsync(onlineMessage, this.Options.CancellationToken.Value); } } @@ -475,7 +475,7 @@ private async Task PublishInternal() private async Task SubscribeInternal() { var topic = SparkplugTopicGenerator.GetWildcardNamespaceSubscribeTopic(this.NameSpace); - await this.Client.SubscribeAsync(topic, (MqttQualityOfServiceLevel)SparkplugQualityOfServiceLevel.AtLeastOnce); + await this.client.SubscribeAsync(topic, (MqttQualityOfServiceLevel)SparkplugQualityOfServiceLevel.AtLeastOnce); } /// diff --git a/src/SparkplugNet/Core/Enumerations/SparkplugSpecificationVersion.cs b/src/SparkplugNet/Core/Enumerations/SparkplugSpecificationVersion.cs new file mode 100644 index 0000000..ca6c4c3 --- /dev/null +++ b/src/SparkplugNet/Core/Enumerations/SparkplugSpecificationVersion.cs @@ -0,0 +1,17 @@ +namespace SparkplugNet.Core.Enumerations; + +/// +/// The Sparkplug specification version. +/// +public enum SparkplugSpecificationVersion +{ + /// + /// Version 2.2 of the Sparkplug specification. + /// + Version22 = 0, + + /// + /// Version 3.0 of the Sparkplug specification. + /// + Version30 = 1 +} diff --git a/src/SparkplugNet/Core/Messages/SparkplugMessageGenerator.cs b/src/SparkplugNet/Core/Messages/SparkplugMessageGenerator.cs index 9aaab48..8701d9f 100644 --- a/src/SparkplugNet/Core/Messages/SparkplugMessageGenerator.cs +++ b/src/SparkplugNet/Core/Messages/SparkplugMessageGenerator.cs @@ -19,14 +19,20 @@ internal class SparkplugMessageGenerator /// private readonly ILogger? logger; + /// + /// The Sparkplug specification version. + /// + private readonly SparkplugSpecificationVersion sparkplugSpecificationVersion; + /// /// Initializes a new instance of the class. /// /// The logger. - // Todo: Add spec version here! - public SparkplugMessageGenerator(ILogger? logger) + /// The Sparkplug specification version. + public SparkplugMessageGenerator(ILogger? logger, SparkplugSpecificationVersion sparkplugSpecificationVersion) { this.logger = logger; + this.sparkplugSpecificationVersion = sparkplugSpecificationVersion; } /// @@ -38,7 +44,7 @@ public SparkplugMessageGenerator(ILogger? logger) /// Thrown if the SCADA host identifier is invalid. /// Thrown if the namespace is out of range. /// A new STATE . - public static MqttApplicationMessage GetSparkplugStateMessage( + public MqttApplicationMessage GetSparkplugStateMessage( SparkplugNamespace nameSpace, string scadaHostIdentifier, bool online) @@ -50,8 +56,8 @@ public static MqttApplicationMessage GetSparkplugStateMessage( return nameSpace switch { - SparkplugNamespace.VersionA => GetSparkplugStateMessageA(scadaHostIdentifier, online), - SparkplugNamespace.VersionB => GetSparkplugStateMessageB(scadaHostIdentifier, online), + SparkplugNamespace.VersionA => this.GetSparkplugStateMessageA(scadaHostIdentifier, online), + SparkplugNamespace.VersionB => this.GetSparkplugStateMessageB(scadaHostIdentifier, online), _ => throw new ArgumentOutOfRangeException(nameof(nameSpace)) }; } @@ -415,7 +421,7 @@ public MqttApplicationMessage GetSparkPlugDeviceDataMessage( /// Thrown if the group identifier or the edge node identifier is invalid. /// Thrown if the namespace is out of range. /// A new NCMD . - public static MqttApplicationMessage GetSparkPlugNodeCommandMessage( + public MqttApplicationMessage GetSparkPlugNodeCommandMessage( SparkplugNamespace nameSpace, string groupIdentifier, string edgeNodeIdentifier, @@ -474,7 +480,7 @@ public static MqttApplicationMessage GetSparkPlugNodeCommandMessage( /// Thrown if the group identifier or the edge node identifier or the device identifier is invalid. /// Thrown if the namespace is out of range. /// A new DCMD . - public static MqttApplicationMessage GetSparkPlugDeviceCommandMessage( + public MqttApplicationMessage GetSparkPlugDeviceCommandMessage( SparkplugNamespace nameSpace, string groupIdentifier, string edgeNodeIdentifier, @@ -537,12 +543,7 @@ public static MqttApplicationMessage GetSparkPlugDeviceCommandMessage( // Add a BDSEQ metric. return metrics.Concat(new VersionAData.KuraMetric[] { - new VersionAData.KuraMetric - { - Name = Constants.SessionNumberMetricName, - LongValue = sessionSequenceNumber, - DataType = VersionAData.DataType.Int64 - } + new VersionAData.KuraMetric(Constants.SessionNumberMetricName, VersionADataTypeEnum.Int64, sessionSequenceNumber) }); } @@ -557,12 +558,7 @@ private static IEnumerable AddSessionNumberToMetrics( // Add a BDSEQ metric. return metrics.Concat(new Metric[] { - new Metric - { - Name = Constants.SessionNumberMetricName, - LongValue = (ulong)sessionSequenceNumber, - ValueCase = (uint)DataType.Int64 - } + new VersionBData.Metric(Constants.SessionNumberMetricName, VersionBDataTypeEnum.Int64, sessionSequenceNumber) }); } @@ -572,7 +568,7 @@ private static IEnumerable AddSessionNumberToMetrics( /// The SCADA host identifier. /// A value indicating whether the message sender is online or not. /// A new STATE . - private static MqttApplicationMessage GetSparkplugStateMessageA(string scadaHostIdentifier, bool online) + private MqttApplicationMessage GetSparkplugStateMessageA(string scadaHostIdentifier, bool online) { return new MqttApplicationMessageBuilder() .WithTopic(SparkplugTopicGenerator.GetSparkplugStateMessageTopic(scadaHostIdentifier)) @@ -585,9 +581,20 @@ private static MqttApplicationMessage GetSparkplugStateMessageA(string scadaHost /// The SCADA host identifier. /// A value indicating whether the message sender is online or not. /// A new STATE . - private static MqttApplicationMessage GetSparkplugStateMessageB(string scadaHostIdentifier, bool online) + private MqttApplicationMessage GetSparkplugStateMessageB(string scadaHostIdentifier, bool online) { - var stateString = GetSparkplugStateMessage(online); + var stateString = string.Empty; + + switch (this.sparkplugSpecificationVersion) + { + case SparkplugSpecificationVersion.Version22: + stateString = online ? "ONLINE" : "OFFLINE"; + break; + case SparkplugSpecificationVersion.Version30: + stateString = GetSparkplugStateMessage(online); + break; + } + return new MqttApplicationMessageBuilder() .WithTopic(SparkplugTopicGenerator.GetSparkplugStateMessageTopic(scadaHostIdentifier)) .WithPayload(stateString) diff --git a/src/SparkplugNet/Core/Node/SparkplugNodeBase.Device.cs b/src/SparkplugNet/Core/Node/SparkplugNodeBase.Device.cs index dcbb4f7..acfdfaa 100644 --- a/src/SparkplugNet/Core/Node/SparkplugNodeBase.Device.cs +++ b/src/SparkplugNet/Core/Node/SparkplugNodeBase.Device.cs @@ -36,13 +36,13 @@ public async Task PublishDeviceBirthMessage(List kno throw new ArgumentNullException(nameof(this.Options), "The options aren't set properly."); } - if (!this.Client.IsConnected) + if (!this.client.IsConnected) { throw new Exception("The MQTT client is not connected, please try again."); } // Get the device birth message. - var deviceBirthMessage = this.MessageGenerator.GetSparkPlugDeviceBirthMessage( + var deviceBirthMessage = this.messageGenerator.GetSparkPlugDeviceBirthMessage( this.NameSpace, this.Options.GroupIdentifier, this.Options.EdgeNodeIdentifier, @@ -63,7 +63,7 @@ public async Task PublishDeviceBirthMessage(List kno // Publish the message. this.Options.CancellationToken ??= SystemCancellationToken.None; - return await this.Client.PublishAsync(deviceBirthMessage, this.Options.CancellationToken.Value); + return await this.client.PublishAsync(deviceBirthMessage, this.Options.CancellationToken.Value); } /// @@ -82,7 +82,7 @@ public Task PublishDeviceData(IEnumerable metrics, s throw new ArgumentNullException(nameof(this.Options), "The options aren't set properly."); } - if (!this.Client.IsConnected) + if (!this.client.IsConnected) { throw new Exception("The MQTT client is not connected, please try again."); } @@ -109,7 +109,7 @@ public async Task PublishDeviceDeathMessage(string devi throw new ArgumentNullException(nameof(this.Options), "The options aren't set properly."); } - if (!this.Client.IsConnected) + if (!this.client.IsConnected) { throw new Exception("The MQTT client is not connected, please try again."); } @@ -120,7 +120,7 @@ public async Task PublishDeviceDeathMessage(string devi } // Get the device death message. - var deviceDeathMessage = this.MessageGenerator.GetSparkPlugDeviceDeathMessage( + var deviceDeathMessage = this.messageGenerator.GetSparkPlugDeviceDeathMessage( this.NameSpace, this.Options.GroupIdentifier, this.Options.EdgeNodeIdentifier, @@ -138,7 +138,7 @@ public async Task PublishDeviceDeathMessage(string devi this.KnownDevices.TryRemove(deviceIdentifier, out _); // Publish the message. this.Options.CancellationToken ??= SystemCancellationToken.None; - return await this.Client.PublishAsync(deviceDeathMessage, this.Options.CancellationToken.Value); + return await this.client.PublishAsync(deviceDeathMessage, this.Options.CancellationToken.Value); } /// @@ -167,7 +167,7 @@ protected virtual async Task PublishMessageForDevice(IE } // Get the data message. - var dataMessage = this.MessageGenerator.GetSparkPlugDeviceDataMessage( + var dataMessage = this.messageGenerator.GetSparkPlugDeviceDataMessage( this.NameSpace, this.Options.GroupIdentifier, this.Options.EdgeNodeIdentifier, @@ -181,6 +181,6 @@ protected virtual async Task PublishMessageForDevice(IE this.IncrementLastSequenceNumber(); // Publish the message. - return await this.Client.PublishAsync(dataMessage); + return await this.client.PublishAsync(dataMessage); } } diff --git a/src/SparkplugNet/Core/Node/SparkplugNodeBase.cs b/src/SparkplugNet/Core/Node/SparkplugNodeBase.cs index 40a5973..3395b75 100644 --- a/src/SparkplugNet/Core/Node/SparkplugNodeBase.cs +++ b/src/SparkplugNet/Core/Node/SparkplugNodeBase.cs @@ -89,7 +89,7 @@ public async Task Start(SparkplugNodeOptions nodeOptions, KnownMetricStorage? kn public async Task Stop() { this.IsRunning = false; - await this.Client.DisconnectAsync(); + await this.client.DisconnectAsync(); } /// @@ -107,7 +107,7 @@ public async Task PublishMetrics(IEnumerable metrics throw new ArgumentNullException(nameof(this.Options), "The options aren't set properly."); } - if (!this.Client.IsConnected) + if (!this.client.IsConnected) { throw new Exception("The MQTT client is not connected, please try again."); } @@ -158,8 +158,8 @@ protected virtual async Task OnClientConnectedAsync(MqttClientConnectedEventArgs /// Thrown if the options are null. private void AddEventHandler() { - this.Client.DisconnectedAsync += this.OnClientDisconnected; - this.Client.ConnectedAsync += this.OnClientConnectedAsync; + this.client.DisconnectedAsync += this.OnClientDisconnected; + this.client.ConnectedAsync += this.OnClientConnectedAsync; } /// @@ -208,7 +208,7 @@ private async Task OnClientDisconnected(MqttClientDisconnectedEventArgs args) /// Thrown if the namespace is out of range. private void AddMessageReceivedHandler() { - this.Client.ApplicationMessageReceivedAsync += this.OnApplicationMessageReceived; + this.client.ApplicationMessageReceivedAsync += this.OnApplicationMessageReceived; } /// @@ -255,7 +255,7 @@ private async Task ConnectInternal() this.ResetLastSequenceNumber(); // Get the will message. - var willMessage = this.MessageGenerator.GetSparkPlugNodeDeathMessage( + var willMessage = this.messageGenerator.GetSparkPlugNodeDeathMessage( this.NameSpace, this.Options.GroupIdentifier, this.Options.EdgeNodeIdentifier, @@ -344,7 +344,7 @@ private async Task ConnectInternal() // Debug output. this.Logger?.Debug("CONNECT Message: {@ClientOptions}.", this.ClientOptions); - await this.Client.ConnectAsync(this.ClientOptions, this.Options.CancellationToken.Value); + await this.client.ConnectAsync(this.ClientOptions, this.Options.CancellationToken.Value); } /// @@ -360,7 +360,7 @@ private async Task PublishInternal() } // Get the online message. - var onlineMessage = this.MessageGenerator.GetSparkPlugNodeBirthMessage( + var onlineMessage = this.messageGenerator.GetSparkPlugNodeBirthMessage( this.NameSpace, this.Options.GroupIdentifier, this.Options.EdgeNodeIdentifier, @@ -379,7 +379,7 @@ private async Task PublishInternal() this.IncrementLastSequenceNumber(); // Publish the message. - await this.Client.PublishAsync(onlineMessage, this.Options.CancellationToken.Value); + await this.client.PublishAsync(onlineMessage, this.Options.CancellationToken.Value); if (this.Options.PublishKnownDeviceMetricsOnReconnect) { @@ -403,12 +403,12 @@ private async Task SubscribeInternal() } var nodeCommandSubscribeTopic = SparkplugTopicGenerator.GetNodeCommandSubscribeTopic(this.NameSpace, this.Options.GroupIdentifier, this.Options.EdgeNodeIdentifier); - await this.Client.SubscribeAsync(nodeCommandSubscribeTopic, (MqttQualityOfServiceLevel)SparkplugQualityOfServiceLevel.AtLeastOnce); + await this.client.SubscribeAsync(nodeCommandSubscribeTopic, (MqttQualityOfServiceLevel)SparkplugQualityOfServiceLevel.AtLeastOnce); var deviceCommandSubscribeTopic = SparkplugTopicGenerator.GetWildcardDeviceCommandSubscribeTopic(this.NameSpace, this.Options.GroupIdentifier, this.Options.EdgeNodeIdentifier); - await this.Client.SubscribeAsync(deviceCommandSubscribeTopic, (MqttQualityOfServiceLevel)SparkplugQualityOfServiceLevel.AtLeastOnce); + await this.client.SubscribeAsync(deviceCommandSubscribeTopic, (MqttQualityOfServiceLevel)SparkplugQualityOfServiceLevel.AtLeastOnce); var stateSubscribeTopic = SparkplugTopicGenerator.GetStateSubscribeTopic(this.Options.ScadaHostIdentifier); - await this.Client.SubscribeAsync(stateSubscribeTopic, (MqttQualityOfServiceLevel)SparkplugQualityOfServiceLevel.AtLeastOnce); + await this.client.SubscribeAsync(stateSubscribeTopic, (MqttQualityOfServiceLevel)SparkplugQualityOfServiceLevel.AtLeastOnce); } } diff --git a/src/SparkplugNet/Core/SparkplugBase.cs b/src/SparkplugNet/Core/SparkplugBase.cs index 7500e46..3424b68 100644 --- a/src/SparkplugNet/Core/SparkplugBase.cs +++ b/src/SparkplugNet/Core/SparkplugBase.cs @@ -20,12 +20,12 @@ namespace SparkplugNet.Core; /// /// The message generator. /// - internal readonly SparkplugMessageGenerator MessageGenerator; + private readonly SparkplugMessageGenerator messageGenerator; /// /// The MQTT client. /// - internal readonly IMqttClient Client; + private readonly IMqttClient client; /// /// The known metrics. @@ -37,10 +37,11 @@ namespace SparkplugNet.Core; /// Initializes a new instance of the class. /// /// The metric names. + /// The Sparkplug specification version. /// The logger. /// - public SparkplugBase(IEnumerable knownMetrics, ILogger? logger = null) - : this(new KnownMetricStorage(knownMetrics), logger) + public SparkplugBase(IEnumerable knownMetrics, SparkplugSpecificationVersion sparkplugSpecificationVersion, ILogger? logger = null) + : this(new KnownMetricStorage(knownMetrics), sparkplugSpecificationVersion, logger) { } @@ -49,9 +50,10 @@ public SparkplugBase(IEnumerable knownMetrics, ILogger? logger = null) /// Initializes a new instance of the class. /// /// The known metrics storage. + /// The Sparkplug specification version. /// The logger. /// - public SparkplugBase(KnownMetricStorage knownMetricsStorage, ILogger? logger = null) + public SparkplugBase(KnownMetricStorage knownMetricsStorage, SparkplugSpecificationVersion sparkplugSpecificationVersion, ILogger? logger = null) { this.knownMetrics = knownMetricsStorage; @@ -64,10 +66,10 @@ public SparkplugBase(KnownMetricStorage knownMetricsStorage, ILogger? logger = n this.NameSpace = SparkplugNamespace.VersionB; } - this.Client = new MqttFactory().CreateMqttClient(); + this.client = new MqttFactory().CreateMqttClient(); this.Logger = logger; - this.MessageGenerator = new SparkplugMessageGenerator(logger); + this.messageGenerator = new SparkplugMessageGenerator(logger, sparkplugSpecificationVersion); } /// @@ -98,7 +100,7 @@ public SparkplugBase(KnownMetricStorage knownMetricsStorage, ILogger? logger = n /// /// Gets a value indicating whether this instance is connected. /// - public bool IsConnected => this.Client.IsConnected; + public bool IsConnected => this.client.IsConnected; /// /// Gets or sets a value indicating whether this instance is running. diff --git a/src/SparkplugNet/GlobalUsings.cs b/src/SparkplugNet/GlobalUsings.cs index f8ea27d..19a65fd 100644 --- a/src/SparkplugNet/GlobalUsings.cs +++ b/src/SparkplugNet/GlobalUsings.cs @@ -33,6 +33,7 @@ global using VersionBData = SparkplugNet.VersionB.Data; global using VersionBProtoBuf = SparkplugNet.VersionB.ProtoBuf; +global using VersionADataTypeEnum = SparkplugNet.VersionA.Data.DataType; global using VersionBDataTypeEnum = SparkplugNet.VersionB.Data.DataType; global using SystemCancellationToken = System.Threading.CancellationToken; #pragma warning restore IDE0065 // Die using-Anweisung wurde falsch platziert. \ No newline at end of file diff --git a/src/SparkplugNet/VersionA/Data/KuraMetric.cs b/src/SparkplugNet/VersionA/Data/KuraMetric.cs index 5063d91..14f0988 100644 --- a/src/SparkplugNet/VersionA/Data/KuraMetric.cs +++ b/src/SparkplugNet/VersionA/Data/KuraMetric.cs @@ -33,6 +33,18 @@ public KuraMetric(DataType dataType, object value) this.SetValue(dataType, value); } + /// + /// Initializes a new instance of the class. + /// + /// The name. + /// The data type. + /// The value. + public KuraMetric(string name, DataType dataType, object value) + { + this.Name = name; + this.SetValue(dataType, value); + } + /// /// Gets or sets the name. /// diff --git a/src/SparkplugNet/VersionA/SparkplugApplication.cs b/src/SparkplugNet/VersionA/SparkplugApplication.cs index 60be6b0..42a80fd 100644 --- a/src/SparkplugNet/VersionA/SparkplugApplication.cs +++ b/src/SparkplugNet/VersionA/SparkplugApplication.cs @@ -77,7 +77,7 @@ protected override async Task PublishNodeCommandMessage(IEnumerable @@ -121,7 +121,7 @@ protected override async Task PublishDeviceCommandMessage(IEnumerable diff --git a/src/SparkplugNet/VersionA/SparkplugNode.cs b/src/SparkplugNet/VersionA/SparkplugNode.cs index 11a5293..20c20ca 100644 --- a/src/SparkplugNet/VersionA/SparkplugNode.cs +++ b/src/SparkplugNet/VersionA/SparkplugNode.cs @@ -62,7 +62,7 @@ protected override async Task PublishMessage(IEnumerabl } // Get the data message. - var dataMessage = this.MessageGenerator.GetSparkPlugNodeDataMessage( + var dataMessage = this.messageGenerator.GetSparkPlugNodeDataMessage( this.NameSpace, this.Options.GroupIdentifier, this.Options.EdgeNodeIdentifier, @@ -78,7 +78,7 @@ protected override async Task PublishMessage(IEnumerabl this.IncrementLastSequenceNumber(); // Publish the message. - return await this.Client.PublishAsync(dataMessage); + return await this.client.PublishAsync(dataMessage); } /// diff --git a/src/SparkplugNet/VersionB/Data/Metric.cs b/src/SparkplugNet/VersionB/Data/Metric.cs index 578854f..8460f3e 100644 --- a/src/SparkplugNet/VersionB/Data/Metric.cs +++ b/src/SparkplugNet/VersionB/Data/Metric.cs @@ -14,11 +14,52 @@ namespace SparkplugNet.VersionB.Data; /// public class Metric : ValueBaseVersionB, IMetric { + /// + /// Initializes a new instance of the class. + /// + public Metric() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The data type. + /// The value. + /// The timestamp. + public Metric(VersionBDataTypeEnum dataType, object value, DateTimeOffset? timestamp = null) + { + this.SetValue(dataType, value); + + if (timestamp is not null) + { + this.Timestamp = (ulong)timestamp.Value.ToUnixTimeMilliseconds(); + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The name. + /// The data type. + /// The value. + /// The timestamp. + public Metric(string name, VersionBDataTypeEnum dataType, object value, DateTimeOffset? timestamp = null) + { + this.Name = name; + this.SetValue(dataType, value); + + if (timestamp is not null) + { + this.Timestamp = (ulong)timestamp.Value.ToUnixTimeMilliseconds(); + } + } + /// /// Gets or sets the name. /// [DefaultValue("")] - public virtual string Name { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; /// /// Gets or sets the alias. @@ -75,29 +116,6 @@ public class Metric : ValueBaseVersionB, IMetric /// public MetricValueExtension? ExtensionValue { get; set; } - /// - /// Initializes a new instance of the class. - /// - public Metric() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The data type. - /// The value. - /// The timestamp. - public Metric(VersionBDataTypeEnum dataType, object value, DateTimeOffset? timestamp = null) - { - this.SetValue(dataType, value); - - if (timestamp is not null) - { - this.Timestamp = (ulong)timestamp.Value.ToUnixTimeMilliseconds(); - } - } - /// /// Sets the value. /// diff --git a/src/SparkplugNet/VersionB/PayloadConverter.cs b/src/SparkplugNet/VersionB/PayloadConverter.cs index 87edea8..8fbd21c 100644 --- a/src/SparkplugNet/VersionB/PayloadConverter.cs +++ b/src/SparkplugNet/VersionB/PayloadConverter.cs @@ -68,6 +68,7 @@ public static Metric ConvertVersionBMetric(VersionBProtoBuf.ProtoBufPayload.Metr Timestamp = protoMetric.Timestamp }; + // Todo: Adjust set value method switch (metric.DataType) { case VersionBDataTypeEnum.Int8: diff --git a/src/SparkplugNet/VersionB/SparkplugApplication.cs b/src/SparkplugNet/VersionB/SparkplugApplication.cs index a8abb32..69367f1 100644 --- a/src/SparkplugNet/VersionB/SparkplugApplication.cs +++ b/src/SparkplugNet/VersionB/SparkplugApplication.cs @@ -71,7 +71,7 @@ protected override async Task PublishNodeCommandMessage(IEnumerable @@ -111,7 +111,7 @@ protected override async Task PublishDeviceCommandMessage(IEnumerable diff --git a/src/SparkplugNet/VersionB/SparkplugNode.cs b/src/SparkplugNet/VersionB/SparkplugNode.cs index 304344c..57582c7 100644 --- a/src/SparkplugNet/VersionB/SparkplugNode.cs +++ b/src/SparkplugNet/VersionB/SparkplugNode.cs @@ -58,7 +58,7 @@ protected override async Task PublishMessage(IEnumerabl } // Get the data message. - var dataMessage = this.MessageGenerator.GetSparkPlugNodeDataMessage( + var dataMessage = this.messageGenerator.GetSparkPlugNodeDataMessage( this.NameSpace, this.Options.GroupIdentifier, this.Options.EdgeNodeIdentifier, @@ -74,7 +74,7 @@ protected override async Task PublishMessage(IEnumerabl this.IncrementLastSequenceNumber(); // Publish the message. - return await this.Client.PublishAsync(dataMessage); + return await this.client.PublishAsync(dataMessage); } ///