How to implement an Industrial IoT gateway app using OPC UA and MQTT in just 100 lines of code
Copyright Microsoft Corporation

How to implement an Industrial IoT gateway app using OPC UA and MQTT in just 100 lines of code

I keep hearing from folks wanting to implement their own Industrial IoT gateway application that they struggle to implement this while using open standards OPC UA and MQTT.

The situation isn't improved with the vast amount of misinformation out there claiming that this is all very complicated and tedious and there is no good documentation and no tutorial and you'd be much better off purchasing their solution (but of course!) and and and...

Although I always try to convince folks to use one of the free and open-source solutions out there to achieve this, like Microsoft OPC Publisher, some folks still want to do this themselves. So I've decided to write the Minimum Viable Product (MVP) for this down in this article.

I will use C# and a .NetCore console application to show how this is done, but similar results can be achieved with other programming languages and other development frameworks. .NetCore has the added benefit of being cross-platform, including Docker container support.

In terms of external libraries we need to use, we need:

  1. An OPC UA stack. I will use the reference stack from the OPC Foundation which is available open-source on GitHub and also conveniently available as a pre-built NuGet package.
  2. An MQTT client. I will use the M2Mqtt NuGet for .NetCore, but there are many, many MQTT clients out there.
  3. Access to an OPC UA server to read data from (of course!).
  4. Access to a cloud-based MQTT broker to send data to, I will use Azure IoT Hub (big surprise there! :-))

So let's get right into the code. After creating your .NetCore console app in Visual Studio, you need to add the two M2MqttDotnetCore and OPCFoundation.NetStandard.Opc.Ua NuGet packages mentioned above to the project. Then, we add an OPC UA client configuration XML file to the project, you can find one here. We name the file Mqtt.Publisher.Config.xml and change the ApplicationName name in the file to MQTTPublisher.

Now we will configure the OPC UA client via the config xml file and add a basic OPC UA certificate validator:

// create OPC UA client app
ApplicationInstance app = new ApplicationInstance
{
    ApplicationName = "MQTTPublisher",
    ApplicationType = ApplicationType.Client,
    ConfigSectionName = "Mqtt.Publisher"
};


app.LoadApplicationConfiguration(false).GetAwaiter().GetResult();


app.CheckApplicationInstanceCertificate(false, 0).GetAwaiter().GetResult();


// create OPC UA cert validator
app.ApplicationConfiguration.CertificateValidator = new CertificateValidator();
            app.ApplicationConfiguration.CertificateValidator.CertificateValidation += new CertificateValidationEventHandler(OPCUAServerCertificateValidationCallback);

Next, we will configure the MQTT client (using an encrypted, TLSv1.2 transport):

// create MQTT client
string brokerName = "";
string clientName = "";
string sharedKey = "";
string userName = brokerName + "/" + clientName + "/?api-version=2018-06-30";

MqttClient mqttClient = new MqttClient(brokerName, 8883, true, MqttSslProtocols.TLSv1_2, MQTTBrokerCertificateValidationCallback, null);

Of course you need to provide your own credentials here. For Azure IoT Hub, the name is <hubname>.azure-devices.net and the client name is the name of the IoT Hub device you will have to create for this app through the Azure portal. Finally, the shared key is the primary key of the device which is generated for you when you create the device.

The two certificate validators referenced in the code can simply look like this (but of course you can add additional verification to them):

private static void OPCUAServerCertificateValidationCallback(CertificateValidator validator, CertificateValidationEventArgs e)
{
    // always trust the OPC UA server certificate
    if (e.Error.StatusCode == StatusCodes.BadCertificateUntrusted)
    {
         e.Accept = true;
    }
}


private static bool MQTTBrokerCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
    // always trust the MQTT broker certificate
    return true;
}

Next, we need to generate the password that the MQTT broker expects. In the case of Azure IoT hub, this is a SAS token, which is generated like so:

// create SAS token
TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);
int week = 60 * 60 * 24 * 7;
string expiry = Convert.ToString((int)sinceEpoch.TotalSeconds + week);
string stringToSign = HttpUtility.UrlEncode(brokerName + "/devices/" + clientName) + "\n" + expiry;
HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String(sharedKey));
string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));

string password = "SharedAccessSignature sr=" + HttpUtility.UrlEncode(brokerName + "/devices/" + clientName) + "&sig=" + HttpUtility.UrlEncode(signature) + "&se=" + expiry;

Now we can connect to the MQTT broker using username and password:

// connect to MQTT broker
byte returnCode = mqttClient.Connect(clientName, userName, password);
if (returnCode != MqttMsgConnack.CONN_ACCEPTED)
{
    Console.WriteLine("Connection to MQTT broker failed with " + returnCode.ToString() + "!");

   return;
}

Finally, we need to connect to our test OPC UA server and create a session:

// find endpoint on a local OPC UA server
EndpointDescription endpointDescription = CoreClientUtils.SelectEndpoint("opc.tcp://localhost:61210", false);
EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(app.ApplicationConfiguration);
ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);


// Create OPC UA session
Session session = Session.Create(app.ApplicationConfiguration, endpoint, false, false, app.ApplicationConfiguration.ApplicationName, 30 * 60 * 1000, new UserIdentity(), null).GetAwaiter().GetResult();
if (!session.Connected)
{
    Console.WriteLine("Connection to OPC UA server failed!");
    return;
}

As you can see, we are connecting to an OPC UA server running locally on port 61210 and we are letting the OPC UA stack pick an endpoint on the OPC UA server for us.

OK, now we're ready to send OPC UA PubSub data to the cloud. We use a JSON encoding as it can be directly consumed by cloud analytics and database software without first needing conversion. To make sure our JSON payload is OPC UA spec-compliant, we use the JSON encoder from the OPC Foundation built into their reference stack:

// send data for a minute, every second
for (int i = 0; i < 60; i++)
{
    int publishingInterval = 1000;


    // read a variable node from the OPC UA server (for example the current time)
    DataValue serverTime = session.ReadValue(Variables.Server_ServerStatus_CurrentTime);
    VariableNode node = (VariableNode)session.ReadNode(Variables.Server_ServerStatus_CurrentTime);


    // OPC UA PubSub JSON-encode data read
    JsonEncoder encoder = new JsonEncoder(session.MessageContext, true);
    encoder.WriteString("MessageId", i.ToString());
    encoder.WriteString("MessageType", "ua-data");
    encoder.WriteString("PublisherId", app.ApplicationName);
    encoder.PushArray("Messages");
    encoder.PushStructure("");
    encoder.WriteString("DataSetWriterId", endpointDescription.Server.ApplicationUri + ":" + publishingInterval.ToString());
    encoder.PushStructure("Payload");
    encoder.WriteDataValue(node.DisplayName.ToString(), serverTime);
    encoder.PopStructure();
    encoder.PopStructure();
    encoder.PopArray();
    string payload = encoder.CloseAndReturnText();


    // send to MQTT broker
    string topic = "devices/" + clientName + "/messages/events/";
    ushort result = mqttClient.Publish(topic, Encoding.UTF8.GetBytes(payload), MqttMsgBase.QOS_LEVEL_AT_LEAST_ONCE, false);

 
   Task.Delay(publishingInterval).GetAwaiter().GetResult();

}

As you can see, we publish data by simply reading the "Current Time" variable node from the OPC UA server and send it to the cloud every second. Of course there is a more efficient way of doing this by creating an OPC UA subscription and only sending the data when it has changed, but for demonstration purposes this will do.

After sending data for a minute, the app automatically closes the connections and exists:

session.Close();
session.Dispose();

mqttClient.Disconnect();

Well, there you have it! The last curly bracket for the app reads line number 124 in my editor, but as you can see there is quite some whitespace, basic error handling and code comments in the file for readability, so we're pretty close to 100 lines! The full source code to the project can be found here.

I hope you agree that this post demonstrated how easy it is to use both OPC UA and MQTT for your industrial IoT gateway application and reap the benefits of leveraging two international standards for your digital transformation project and avoiding vendor lock-in in the process!

Erich Barnstedt

Senior Director & Architect, Industrial Standards, Corporate Standards Group at Microsoft

3 年

I just added Docker container support plus the full source code to GitHub here: https://github.com/barnstee/MQTTPublisherMVP

This is great and shows how easy it is to bring OPC-UA data to an MQTT infrastructure for decoupled bridging of the OT/IT gap. Next step to bring this further would be the use of Sparkplug for bi-directions data integration with standards compliant (3.1.1 or 5) MQTT infrastructures.?

Alberto Gorni

Senior IoT Specialist Solutions Architect @ AWS | ex Microsoft | ex General Electric | ex Siemens

3 年

This is cool... BTW i try to suggest as well the use of the Publisher module and IoT Edge as long as remove the complexity of all the boilerplate code you need to put around for having a production ready solution.... and .. BTW... it s free

Christos Lithoxopoulos

Innovate with ecosystems in manufacturing, retail and logistics @SYNTAX

3 年

Thanks for sharing. As easy as 1,2,3 ?? André Hoettgen ?

Lukasz Paciorkowski

Digitizing Biotech, Delivering Meaningful Innovation | Board Member @ A4BEE | CTO @ Modica

3 年

Thanks for sharing! Maciej Jarkowski Rafa? Cie?lak

回复

要查看或添加评论,请登录

Erich Barnstedt的更多文章

社区洞察

其他会员也浏览了