Cloud-Anbindung mit OPC UA und MQTT

IoT-Gateway-App in 100 Codezeilen

Oft heißt es, Gateways zwischen IIoT-Geräten und Clouds wie Azure seien mit OPC UA und MQTT schwer umzusetzen, es fehle an Dokumentation und Tutorials. Erich Barnstedt von Microsoft hält mit einem selbst geschriebenen Programm entgegen: Die Minimallösung einer Gateway-App zwischen IIoT und Cloud kommt auf rund 100 Codezeilen – und passt samt Howto auf zwei Heftseiten.

Erich Barnstedt ist Chief Architect, Standards & Consortia, Azure IoT bei Microsoft. (Bild: Microsoft GmbH)
Erich Barnstedt ist Chief Architect, Standards & Consortia, Azure IoT bei Microsoft. (Bild: Microsoft GmbH)

Erich Barnstedt setzt für seine Minimallösung auf C# und eine .NetCore-Konsolenanwendung. Andere Programmiersprachen und Frameworks führen zu ähnlichen Ergebnissen. .NetCore hat den Vorteil, dass es plattformübergreifend ist und Docker-Container unterstützt. Der Microsoft-Chief-Architect nutzt zudem folgenden Quellen:

  • • Als OPC-UA-Stack verwendet er den Referenz-Stack der OPC Foundation, der als Open-Source auf GitHub verfügbar ist und als vorgefertigtes NuGet-Paket erhältlich ist.
  • • Als MQTT-Client dient M2Mqtt NuGet für .NetCore, wobei nichts gegen andere MQTT-Clients spreche, so Barnstedt.
  • • Zudem benötigt er einen Zugang zu einem OPC-UA-Server, um Daten auszulesen, und zu einem cloud-basierten MQTT-Broker, zu dem die Daten gesendet werden. Dazu nutzt er den Azure IoT Hub von Microsoft.

Zuerst wird ein neuer .NetCore-Konsolenanwendungs-Projekt in Visual Studio erstellt. Danach gilt es, die beiden NuGet-Pakete M2MqttDotnetCore und OPCFoundation.NetStandard.Opc.UA zum Projekt hinzuzufügen. Es folgt eine OPC-UA-Client-Konfigurations-XML-Datei, die ebenfalls auf Github herunterzuladen ist und unter dem Namen Mqtt.Publisher.Config.xml verwendet werden kann. Der ApplicationName in der Datei wird in MQTTPublisher geändert. Als Nächstes kommt die Konfiguration des OPC-UA-Clients über die config-xml-Datei. Zudem wird ein einfacher OPC-UA-Zertifikatsvalidator hinzugefügt:

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

Dann ist die Konfiguration des MQTT-Clients dran – unter Verwendung eines verschlüsselten TLSv1.2-Transports:

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

Für die Integration von Azure IoT Hub müssen Anmeldedaten eingegeben werden. Standardmäßig lautet der Name azure-devices.net. Der Clientname ist der Name des IoT-Hub-Geräts, das für die App über das Azure-Portal erstellt werden muss. Der Schlüssel schließlich ist der Primärschlüssel des Geräts, der beim Erstellen des Geräts generiert wird. Die beiden Zertifikats-Validierer, auf die im Code verwiesen wird, können so aussehen – und durch zusätzliche, hier nicht aufgeführte Überprüfungen ergänzt werden:

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

Nächster Schritt: Generierung eines Passworts, das der MQTT-Broker erwartet. Im Fall von Azure IoT Hub ist dies ein SAS-Token, das wie folgt generiert wird:

// 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;

Jetzt lässt sich über Usernamen und Passwort eine Verbindung zum MQTT-Broker herstellen:

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

Es folgt die Verbindung zum Test-OPC-UA-Server und zum Start einer neuen 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;
}

Im vorangegangenen Codeblock wird eine Verbindung zu einem OPC-UA-Server hergestellt, der lokal auf Port 61210 läuft. Der OPC-UA-Stack wählt außerdem einen Endpunkt auf dem OPC-UA-Server aus. Jetzt ist das Programm bereit, OPC-UA-PubSub-Daten an die Cloud zu senden. Dafür verwendet der Code eine JSON-Kodierung, die ohne Konvertierung von Cloud-Analyse- und Datenbanksoftware genutzt werden kann. Um sicherzustellen, dass die JSON-Daten mit der OPC-UA-Spezifikation übereinstimmen, nutzt der Code den JSON-Encoder der OPC Foundation, der im Referenz-Stack integriert ist:

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

Das Verschicken von Daten in die Cloud wird anhand der OPC UA Variable ‚Current Time‘ vom OPC-UA-Server realisiert. Dafür gibt es zwar eine effizientere Möglichkeit: über das Erstellen einer OPC-UA-Subscription und das Versenden von Daten nur dann, wenn sie sich geändert haben. Für die Demonstrationszwecke im Rahmen dies Projekts reicht die beschriebene Variante aber aus. Wenn die App Daten eine Minute lang gesendet hat, schließt sie automatisch die Verbindungen:

session.Close();
session.Dispose();
mqttClient.Disconnect();

Ziel erreicht: Die letzte geschweifte Klammer trägt die Zeilennummer 124, aber das ließe sich durch das Entfernen von Leerzeilen und Kommentaren noch reduzieren. Die gut 100 Zeilen zeigen, wie einfach es ist, OPC UA sowie MQTT in eine industrielle IoT-Gateway-App zu integrieren und so die Vorteile zu nutzen, die sich aus der Verwendung internationaler Standards für digitale Transformationsprojekte ergeben – ohne sich an einen Anbieter zu binden, nicht einmal an Microsoft.