/// <summary> /// Create module, throws if configuration is bad /// </summary> public void Create(Broker broker, byte[] configuration) { Trace("Opc.Ua.Publisher.Module: Creating..."); m_broker = broker; string configString = Encoding.UTF8.GetString(configuration); // Deserialize from configuration string ModuleConfiguration moduleConfiguration = null; try { moduleConfiguration = JsonConvert.DeserializeObject <ModuleConfiguration>(configString); } catch (Exception ex) { Trace("Opc.Ua.Publisher.Module: Module config string " + configString + " could not be deserialized: " + ex.Message); throw; } m_configuration = moduleConfiguration.Configuration; m_configuration.CertificateValidator.CertificateValidation += new CertificateValidationEventHandler(CertificateValidator_CertificateValidation); // update log configuration, if available if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GW_LOGP"))) { m_configuration.TraceConfiguration.OutputFilePath = Environment.GetEnvironmentVariable("_GW_LOGP"); m_configuration.TraceConfiguration.ApplySettings(); } // get a list of persisted endpoint URLs and create a session for each. try { // check if we have an env variable specifying the published nodes path, otherwise use current directory string publishedNodesFilePath = Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + "publishednodes.json"; if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GW_PNFP"))) { publishedNodesFilePath = Environment.GetEnvironmentVariable("_GW_PNFP"); } Trace("Opc.Ua.Publisher.Module: Attemping to load nodes file from: " + publishedNodesFilePath); m_nodesLookups = JsonConvert.DeserializeObject <PublishedNodesCollection>(File.ReadAllText(publishedNodesFilePath)); Trace("Opc.Ua.Publisher.Module: Loaded " + m_nodesLookups.Count.ToString() + " nodes."); } catch (Exception ex) { Trace("Opc.Ua.Publisher.Module: Nodes file loading failed with: " + ex.Message); } foreach (NodeLookup nodeLookup in m_nodesLookups) { if (!m_endpointUrls.Contains(nodeLookup.EndPointURL)) { m_endpointUrls.Add(nodeLookup.EndPointURL); } } // start the server try { Trace("Opc.Ua.Publisher.Module: Starting server on endpoint " + m_configuration.ServerConfiguration.BaseAddresses[0].ToString() + "..."); m_server.Start(m_configuration); Trace("Opc.Ua.Publisher.Module: Server started."); } catch (Exception ex) { Trace("Opc.Ua.Publisher.Module: Starting server failed with: " + ex.Message); } // check if we have an environment variable to register ourselves with IoT Hub if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_HUB_CS"))) { string ownerConnectionString = Environment.GetEnvironmentVariable("_HUB_CS"); if ((m_configuration != null) && (!string.IsNullOrEmpty(m_configuration.ApplicationName))) { Trace("Attemping to register ourselves with IoT Hub using owner connection string: " + ownerConnectionString); string deviceConnectionString = IoTHubRegistration.RegisterDeviceWithIoTHub(m_configuration.ApplicationName, ownerConnectionString); if (!string.IsNullOrEmpty(deviceConnectionString)) { SecureIoTHubToken.Write(m_configuration.ApplicationName, deviceConnectionString); } else { Trace("Could not register ourselves with IoT Hub using owner connection string: " + ownerConnectionString); } } } // try to configure our publisher component TryConfigurePublisherAsync().Wait(); // connect to servers Trace("Opc.Ua.Publisher.Module: Attemping to connect to servers..."); try { List <Task> connectionAttempts = new List <Task>(); foreach (Uri endpointUrl in m_endpointUrls) { Trace("Opc.Ua.Publisher.Module: Connecting to server: " + endpointUrl); connectionAttempts.Add(EndpointConnect(endpointUrl)); } // Wait for all sessions to be connected Task.WaitAll(connectionAttempts.ToArray()); } catch (Exception ex) { Trace("Opc.Ua.Publisher.Module: Exception: " + ex.ToString() + "\r\n" + ex.InnerException != null ? ex.InnerException.ToString() : null); } Trace("Opc.Ua.Publisher.Module: Created."); }
public static void Main(string[] args) { try { if ((args.Length == 0) || string.IsNullOrEmpty(args[0]) || args[0].Equals("localhost", StringComparison.OrdinalIgnoreCase)) { m_applicationName = Utils.GetHostName(); } else { m_applicationName = args[0]; } Trace("Publisher is starting up..."); ModuleConfiguration moduleConfiguration = new ModuleConfiguration(m_applicationName); m_configuration = moduleConfiguration.Configuration; m_configuration.CertificateValidator.CertificateValidation += new CertificateValidationEventHandler(CertificateValidator_CertificateValidation); // start our server interface try { Trace("Starting server on endpoint " + m_configuration.ServerConfiguration.BaseAddresses[0].ToString() + "..."); m_server.Start(m_configuration); Trace("Server started."); } catch (Exception ex) { Trace("Starting server failed with: " + ex.Message); } // check if we also received an owner connection string string ownerConnectionString = string.Empty; if ((args.Length > 1) && !string.IsNullOrEmpty(args[1])) { ownerConnectionString = args[1]; } else { Trace("IoT Hub owner connection string not passed as argument."); // check if we have an environment variable to register ourselves with IoT Hub if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_HUB_CS"))) { ownerConnectionString = Environment.GetEnvironmentVariable("_HUB_CS"); } } // register ourselves with IoT Hub if (ownerConnectionString != string.Empty) { Trace("Attemping to register ourselves with IoT Hub using owner connection string: " + ownerConnectionString); RegistryManager manager = RegistryManager.CreateFromConnectionString(ownerConnectionString); // remove any existing device Device existingDevice = manager.GetDeviceAsync(m_applicationName).Result; if (existingDevice != null) { manager.RemoveDeviceAsync(m_applicationName).Wait(); } Device newDevice = manager.AddDeviceAsync(new Device(m_applicationName)).Result; if (newDevice != null) { string hostname = ownerConnectionString.Substring(0, ownerConnectionString.IndexOf(";")); string deviceConnectionString = hostname + ";DeviceId=" + m_applicationName + ";SharedAccessKey=" + newDevice.Authentication.SymmetricKey.PrimaryKey; SecureIoTHubToken.Write(m_applicationName, deviceConnectionString); } else { Trace("Could not register ourselves with IoT Hub using owner connection string: " + ownerConnectionString); } } else { Trace("IoT Hub owner connection string not found, registration with IoT Hub abandoned."); } // try to read connection string from secure store and open IoTHub client Trace("Attemping to read connection string from secure store with certificate name: " + m_applicationName); string connectionString = SecureIoTHubToken.Read(m_applicationName); if (!string.IsNullOrEmpty(connectionString)) { Trace("Attemping to configure publisher with connection string: " + connectionString); m_deviceClient = DeviceClient.CreateFromConnectionString(connectionString, Microsoft.Azure.Devices.Client.TransportType.Mqtt); m_deviceClient.RetryPolicy = RetryPolicyType.Exponential_Backoff_With_Jitter; m_deviceClient.OpenAsync().Wait(); } else { Trace("Device connection string not found in secure store."); } // get a list of persisted endpoint URLs and create a session for each. try { // check if we have an env variable specifying the published nodes path, otherwise use current directory string publishedNodesFilePath = Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + "publishednodes.json"; if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GW_PNFP"))) { publishedNodesFilePath = Environment.GetEnvironmentVariable("_GW_PNFP"); } Trace("Attemping to load nodes file from: " + publishedNodesFilePath); m_nodesLookups = JsonConvert.DeserializeObject <PublishedNodesCollection>(File.ReadAllText(publishedNodesFilePath)); Trace("Loaded " + m_nodesLookups.Count.ToString() + " nodes."); } catch (Exception ex) { Trace("Nodes file loading failed with: " + ex.Message); } foreach (NodeLookup nodeLookup in m_nodesLookups) { if (!m_endpointUrls.Contains(nodeLookup.EndPointURL)) { m_endpointUrls.Add(nodeLookup.EndPointURL); } } // connect to the other servers Trace("Attemping to connect to servers..."); try { List <Task> connectionAttempts = new List <Task>(); foreach (Uri endpointUrl in m_endpointUrls) { Trace("Connecting to server: " + endpointUrl); connectionAttempts.Add(EndpointConnect(endpointUrl)); } // Wait for all sessions to be connected Task.WaitAll(connectionAttempts.ToArray()); } catch (Exception ex) { Trace("Exception: " + ex.ToString() + "\r\n" + ex.InnerException != null ? ex.InnerException.ToString() : null); } // subscribe to preconfigured nodes Trace("Attemping to subscribe to published nodes..."); if (m_nodesLookups != null) { foreach (NodeLookup nodeLookup in m_nodesLookups) { try { CreateMonitoredItem(nodeLookup); } catch (Exception ex) { Trace("Unexpected error publishing node: " + ex.Message + "\r\nIgnoring node: " + nodeLookup.EndPointURL.AbsoluteUri + ", " + nodeLookup.NodeID.ToString()); } } } Task dequeueAndSendTask = null; var tokenSource = new CancellationTokenSource(); var token = tokenSource.Token; Trace("Creating task to send OPC UA messages in batches to IoT Hub..."); try { dequeueAndSendTask = Task.Run(() => DeQueueMessagesAsync(token), token); } catch (Exception ex) { Trace("Exception: " + ex.ToString()); } Trace("Publisher is running. Press enter to quit."); Console.ReadLine(); foreach (Session session in m_sessions) { session.Close(); } //Send cancellation token and wait for last IoT Hub message to be sent. try { tokenSource.Cancel(); dequeueAndSendTask.Wait(); } catch (Exception ex) { Trace("Exception: " + ex.ToString()); } if (m_deviceClient != null) { m_deviceClient.CloseAsync().Wait(); } } catch (Exception e) { Trace(e, "Unhandled exception in Publisher, exiting!"); } }
public static void Main(string[] args) { var opcTraceInitialized = false; try { var shouldShowHelp = false; // command line options configuration Mono.Options.OptionSet options = new Mono.Options.OptionSet { // Publishing configuration options { "pf|publishfile=", $"the filename to configure the nodes to publish.\nDefault: '{NodesToPublishAbsFilenameDefault}'", (string p) => NodesToPublishAbsFilename = p }, { "sd|shopfloordomain=", $"the domain of the shopfloor. if specified this domain is appended (delimited by a ':' to the 'ApplicationURI' property when telemetry is ingested to IoTHub.\n" + "The value must follow the syntactical rules of a DNS hostname.\nDefault: not set", (string s) => { Regex domainNameRegex = new Regex("^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$"); if (domainNameRegex.IsMatch(s)) { ShopfloorDomain = s; } else { throw new OptionException("The shopfloor domain is not a valid DNS hostname.", "shopfloordomain"); } } }, { "sw|sessionconnectwait=", $"specify the wait time in seconds publisher is trying to connect to disconnected endpoints and starts monitoring unmonitored items\nMin: 10\nDefault: {PublisherSessionConnectWaitSec}", (int i) => { if (i > 10) { PublisherSessionConnectWaitSec = i; } else { throw new OptionException("The sessionconnectwait must be greater than 10 sec", "sessionconnectwait"); } } }, { "vc|verboseconsole=", $"the output of publisher is shown on the console.\nDefault: {VerboseConsole}", (bool b) => VerboseConsole = b }, // IoTHub specific options { "ih|iothubprotocol=", $"the protocol to use for communication with Azure IoTHub (allowed values: {string.Join(", ", Enum.GetNames(IotHubProtocol.GetType()))}).\nDefault: {Enum.GetName(IotHubProtocol.GetType(), IotHubProtocol)}", (Microsoft.Azure.Devices.Client.TransportType p) => IotHubProtocol = p }, { "ms|iothubmessagesize=", $"the max size of a message which could be send to IoTHub. when telemetry of this size is available it will be sent.\n0 will enforce immediate send when telemetry is available\nMin: 0\nMax: 256 * 1024\nDefault: {_MaxSizeOfIoTHubMessageBytes}", (uint u) => { if (u >= 0 && u <= 256 * 1024) { _MaxSizeOfIoTHubMessageBytes = u; } else { throw new OptionException("The iothubmessagesize must be in the range between 1 and 256*1024.", "iothubmessagesize"); } } }, { "si|iothubsendinterval=", $"the interval in seconds when telemetry should be send to IoTHub. If 0, then only the iothubmessagesize parameter controls when telemetry is sent.\nDefault: '{_DefaultSendIntervalSeconds}'", (int i) => { if (i >= 0) { _DefaultSendIntervalSeconds = i; } else { throw new OptionException("The iothubsendinterval must be larger or equal 0.", "iothubsendinterval"); } } }, // opc server configuration options { "lf|logfile=", $"the filename of the logfile to use.\nDefault: './logs/<applicationname>.log.txt'", (string l) => LogFileName = l }, { "pn|portnum=", $"the server port of the publisher OPC server endpoint.\nDefault: {PublisherServerPort}", (ushort p) => PublisherServerPort = p }, { "pa|path=", $"the enpoint URL path part of the publisher OPC server endpoint.\nDefault: '{PublisherServerPath}'", (string a) => PublisherServerPath = a }, { "lr|ldsreginterval=", $"the LDS(-ME) registration interval in ms. If 0, then the registration is disabled.\nDefault: {LdsRegistrationInterval}", (int i) => { if (i >= 0) { LdsRegistrationInterval = i; } else { throw new OptionException("The ldsreginterval must be larger or equal 0.", "ldsreginterval"); } } }, { "ot|operationtimeout=", $"the operation timeout of the publisher OPC UA client in ms.\nDefault: {OpcOperationTimeout}", (int i) => { if (i >= 0) { OpcOperationTimeout = i; } else { throw new OptionException("The operation timeout must be larger or equal 0.", "operationtimeout"); } } }, { "ds|defaultsamplingrate=", $"the sampling interval in milliseconds, the OPC UA servers should use to sample the values of the nodes to publish.\n" + $"Please check the OPC UA spec for more details.\nMin: 100\nDefault: {OpcSamplingRateMillisec}", (int i) => { if (i >= 100) { OpcSamplingRateMillisec = i; } else { throw new OptionException("The sampling rate must be larger or equal 100.", "defaultsamplingrate"); } } }, { "ct|createsessiontimeout=", $"specify the timeout in seconds used when creating a session to an endpoint. On unsuccessful connection attemps a backoff up to {OpcSessionCreationBackoffMax} times the specified timeout value is used.\nMin: 1\nDefault: {OpcSessionCreationTimeout}", (uint u) => { if (u > 1) { OpcSessionCreationTimeout = u; } else { throw new OptionException("The createsessiontimeout must be greater than 1 sec", "createsessiontimeout"); } } }, { "ki|keepaliveinterval=", $"specify the interval in seconds the publisher is sending keep alive messages to the OPC servers on the endpoints it is connected to.\nMin: 2\nDefault: {OpcKeepAliveIntervalInSec}", (int i) => { if (i >= 2) { OpcKeepAliveIntervalInSec = i; } else { throw new OptionException("The keepaliveinterval must be greater or equal 2", "keepalivethreshold"); } } }, { "kt|keepalivethreshold=", $"specify the number of keep alive packets a server could miss, before the session is disconneced\nMin: 1\nDefault: {OpcKeepAliveDisconnectThreshold}", (uint u) => { if (u > 1) { OpcKeepAliveDisconnectThreshold = u; } else { throw new OptionException("The keepalivethreshold must be greater than 1", "keepalivethreshold"); } } }, { "st|opcstacktracemask=", $"the trace mask for the OPC stack. See github OPC .NET stack for definitions.\nTo enable IoTHub telemetry tracing set it to 711.\nDefault: {OpcStackTraceMask:X} ({Program.OpcStackTraceMask})", (int i) => { if (i >= 0) { OpcStackTraceMask = i; } else { throw new OptionException("The OPC stack trace mask must be larger or equal 0.", "opcstacktracemask"); } } }, { "as|autotrustservercerts=", $"the publisher trusts all servers it is establishing a connection to.\nDefault: {OpcPublisherAutoTrustServerCerts}", (bool b) => OpcPublisherAutoTrustServerCerts = b }, // trust own public cert option { "tm|trustmyself=", $"the publisher certificate is put into the trusted certificate store automatically.\nDefault: {TrustMyself}", (bool b) => TrustMyself = b }, // own cert store options { "at|appcertstoretype=", $"the own application cert store type. \n(allowed values: Directory, X509Store)\nDefault: '{OpcOwnCertStoreType}'", (string s) => { if (s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) || s.Equals(Directory, StringComparison.OrdinalIgnoreCase)) { OpcOwnCertStoreType = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? X509Store : Directory; } else { throw new OptionException(); } } }, { "ap|appcertstorepath=", $"the path where the own application cert should be stored\nDefault (depends on store type):\n" + $"X509Store: '{_opcOwnCertX509StorePathDefault}'\n" + $"Directory: '{_opcOwnCertDirectoryStorePathDefault}'", (string s) => OpcOwnCertStorePath = s }, // trusted cert store options { "tt|trustedcertstoretype=", $"the trusted cert store type. \n(allowed values: Directory, X509Store)\nDefault: {OpcTrustedCertStoreType}", (string s) => { if (s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) || s.Equals(Directory, StringComparison.OrdinalIgnoreCase)) { OpcTrustedCertStoreType = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? X509Store : Directory; } else { throw new OptionException(); } } }, { "tp|trustedcertstorepath=", $"the path of the trusted cert store\nDefault (depends on store type):\n" + $"X509Store: '{OpcTrustedCertX509StorePathDefault}'\n" + $"Directory: '{OpcTrustedCertDirectoryStorePathDefault}'", (string s) => OpcTrustedCertStorePath = s }, // rejected cert store options { "rt|rejectedcertstoretype=", $"the rejected cert store type. \n(allowed values: Directory, X509Store)\nDefault: {OpcRejectedCertStoreType}", (string s) => { if (s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) || s.Equals(Directory, StringComparison.OrdinalIgnoreCase)) { OpcRejectedCertStoreType = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? X509Store : Directory; } else { throw new OptionException(); } } }, { "rp|rejectedcertstorepath=", $"the path of the rejected cert store\nDefault (depends on store type):\n" + $"X509Store: '{_opcRejectedCertX509StorePathDefault}'\n" + $"Directory: '{_opcRejectedCertDirectoryStorePathDefault}'", (string s) => OpcRejectedCertStorePath = s }, // issuer cert store options { "it|issuercertstoretype=", $"the trusted issuer cert store type. \n(allowed values: Directory, X509Store)\nDefault: {OpcIssuerCertStoreType}", (string s) => { if (s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) || s.Equals(Directory, StringComparison.OrdinalIgnoreCase)) { OpcIssuerCertStoreType = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? X509Store : Directory; } else { throw new OptionException(); } } }, { "ip|issuercertstorepath=", $"the path of the trusted issuer cert store\nDefault (depends on store type):\n" + $"X509Store: '{_opcIssuerCertX509StorePathDefault}'\n" + $"Directory: '{_opcIssuerCertDirectoryStorePathDefault}'", (string s) => OpcIssuerCertStorePath = s }, // device connection string cert store options { "dt|devicecertstoretype=", $"the iothub device cert store type. \n(allowed values: Directory, X509Store)\nDefault: {IotDeviceCertStoreType}", (string s) => { if (s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) || s.Equals(Directory, StringComparison.OrdinalIgnoreCase)) { IotDeviceCertStoreType = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? X509Store : Directory; IotDeviceCertStorePath = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? _iotDeviceCertX509StorePathDefault : _iotDeviceCertDirectoryStorePathDefault; } else { throw new OptionException(); } } }, { "dp|devicecertstorepath=", $"the path of the iot device cert store\nDefault Default (depends on store type):\n" + $"X509Store: '{_iotDeviceCertX509StorePathDefault}'\n" + $"Directory: '{_iotDeviceCertDirectoryStorePathDefault}'", (string s) => IotDeviceCertStorePath = s }, // misc { "h|help", "show this message and exit", h => shouldShowHelp = h != null }, }; List <string> arguments; try { // parse the command line arguments = options.Parse(args); } catch (OptionException e) { // show message WriteLine($"Error: {e.Message}"); // show usage Usage(options); return; } // Validate and parse arguments. if (arguments.Count > 2 || shouldShowHelp) { Usage(options); return; } else if (arguments.Count == 2) { ApplicationName = arguments[0]; _IotHubOwnerConnectionString = arguments[1]; } else if (arguments.Count == 1) { ApplicationName = arguments[0]; } else { ApplicationName = Utils.GetHostName(); } WriteLine("Publisher is starting up..."); // init OPC configuration and tracing ModuleConfiguration moduleConfiguration = new ModuleConfiguration(ApplicationName); opcTraceInitialized = true; OpcConfiguration = moduleConfiguration.Configuration; // log shopfloor domain setting if (string.IsNullOrEmpty(ShopfloorDomain)) { Trace("There is no shopfloor domain configured."); } else { Trace($"Publisher is in shopfloor domain '{ShopfloorDomain}'."); } // Set certificate validator. if (OpcPublisherAutoTrustServerCerts) { Trace("Publisher configured to auto trust server certificates of the servers it is connecting to."); OpcConfiguration.CertificateValidator.CertificateValidation += new CertificateValidationEventHandler(CertificateValidator_AutoTrustServerCerts); } else { Trace("Publisher configured to not auto trust server certificates. When connecting to servers, you need to manually copy the rejected server certs to the trusted store to trust them."); OpcConfiguration.CertificateValidator.CertificateValidation += new CertificateValidationEventHandler(CertificateValidator_Default); } // start our server interface try { Trace($"Starting server on endpoint {OpcConfiguration.ServerConfiguration.BaseAddresses[0].ToString()} ..."); PublisherServer publisherServer = new PublisherServer(); publisherServer.Start(OpcConfiguration); Trace("Server started."); } catch (Exception e) { Trace(e, $"Failed to start Publisher OPC UA server."); Trace("exiting..."); return; } // get information on the nodes to publish and validate the json by deserializing it. try { if (string.IsNullOrEmpty(NodesToPublishAbsFilename)) { // check if we have an env variable specifying the published nodes path, otherwise use the default NodesToPublishAbsFilename = NodesToPublishAbsFilenameDefault; if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GW_PNFP"))) { Trace("Publishing node configuration file path read from environment."); NodesToPublishAbsFilename = Environment.GetEnvironmentVariable("_GW_PNFP"); } } Trace($"Attempting to load nodes file from: {NodesToPublishAbsFilename}"); NodesToPublish = JsonConvert.DeserializeObject <List <NodeToPublish> >(File.ReadAllText(NodesToPublishAbsFilename)); Trace($"Loaded {NodesToPublish.Count.ToString()} nodes to publish."); } catch (Exception e) { Trace(e, "Loading of the node configuration file failed. Does the file exist and has correct syntax?"); Trace("exiting..."); return; } // initialize and start IoTHub messaging IotHubMessaging = new IotHubMessaging(); if (!IotHubMessaging.Init(_IotHubOwnerConnectionString, _MaxSizeOfIoTHubMessageBytes, _DefaultSendIntervalSeconds)) { return; } // create a list to manage sessions and monitored items. var uniqueEndpointUris = NodesToPublish.Select(n => n.EndPointUri).Distinct(); foreach (var endpointUri in uniqueEndpointUris) { if (!OpcSessions.Any(s => s.EndpointUri.Equals(endpointUri))) { // create new session info. OpcSession opcSession = new OpcSession(endpointUri, OpcSessionCreationTimeout); // add monitored item info for all nodes to publish for this endpoint URI. var nodesOnEndpointUri = NodesToPublish.Where(n => n.EndPointUri.Equals(endpointUri)); foreach (var nodeInfo in nodesOnEndpointUri) { // differentiate if legacy syntax is used if (nodeInfo.NodeId == null) { // differentiate if there is only a single node specified or a node list with a common sampling interval if (nodeInfo.ExpandedNodeId == null) { // create a monitored item for each node in the list and set the sampling interval if specified foreach (var singleExpandedNodeId in nodeInfo.Nodes.ExpandedNodeIds) { MonitoredItemInfo monitoredItemInfo = new MonitoredItemInfo(singleExpandedNodeId, opcSession.EndpointUri); if (nodeInfo.Nodes.SamplingInterval != 0) { monitoredItemInfo.SamplingInterval = nodeInfo.Nodes.SamplingInterval; } opcSession.MonitoredItemsInfo.Add(monitoredItemInfo); } } else { // create a monitored item for the node MonitoredItemInfo monitoredItemInfo = new MonitoredItemInfo(nodeInfo.ExpandedNodeId, opcSession.EndpointUri); if (nodeInfo.SamplingInterval != 0) { monitoredItemInfo.SamplingInterval = nodeInfo.SamplingInterval; } opcSession.MonitoredItemsInfo.Add(monitoredItemInfo); } } else { // give user a warning that the syntax is obsolete Trace($"Please update the syntax of the configuration file and use ExpandedNodeId instead of NodeId property name for node with identifier '{nodeInfo.NodeId.ToString()}' on EndpointUrl '{nodeInfo.EndPointUri}'."); // create a monitored item for the node with the configured or default sampling interval MonitoredItemInfo monitoredItemInfo = new MonitoredItemInfo(nodeInfo.NodeId, opcSession.EndpointUri); if (nodeInfo.SamplingInterval != 0) { monitoredItemInfo.SamplingInterval = nodeInfo.SamplingInterval; } opcSession.MonitoredItemsInfo.Add(monitoredItemInfo); } } // add the session info. OpcSessions.Add(opcSession); } } // kick off the task to maintain all sessions var cts = new CancellationTokenSource(); Task.Run(async() => await SessionConnector(cts.Token)); // stop on user request WriteLine(""); WriteLine(""); WriteLine("Publisher is running. Press ENTER to quit."); WriteLine(""); WriteLine(""); ReadLine(); cts.Cancel(); // close all connected session Task.Run(async() => await SessionShutdown()).Wait(); // shutdown the IoTHub messaging IotHubMessaging.Shutdown(); } catch (Exception e) { if (opcTraceInitialized) { Trace(e, e.StackTrace); e = e.InnerException ?? null; while (e != null) { Trace(e, e.StackTrace); e = e.InnerException ?? null; } Trace("Publisher exiting... "); } else { WriteLine($"{DateTime.Now.ToString()}: {e.Message.ToString()}"); WriteLine($"{DateTime.Now.ToString()}: {e.StackTrace}"); e = e.InnerException ?? null; while (e != null) { WriteLine($"{DateTime.Now.ToString()}: {e.Message.ToString()}"); WriteLine($"{DateTime.Now.ToString()}: {e.StackTrace}"); e = e.InnerException ?? null; } WriteLine($"{DateTime.Now.ToString()}: Publisher exiting..."); } } }