Example #1
0
 private async Task UpdateConfigFromTwin(TwinCollection desiredProperties)
 {
     if (!await PublisherNodeConfiguration
         .LoadConfigFromDesiredPropertiesAsync(desiredProperties))
     {
         Trace("Could not update configuration from desired properties.");
         Trace($"Loading last saved config: {PublisherNodeConfiguration.PublisherNodeConfigurationFilename}");
         await PublisherNodeConfiguration.ReadConfigAsync();
     }
 }
Example #2
0
        /// <summary>
        /// Asynchronous part of the main method of the app.
        /// </summary>
        public async static Task MainAsync(string[] args)
        {
            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 (fallback procedure).\nDefault: '{PublisherNodeConfigurationFilename}'", (string p) => PublisherNodeConfigurationFilename = p },
                    { "tc|telemetryconfigfile=", $"the filename to configure the ingested telemetry\nDefault: '{PublisherTelemetryConfigurationFilename}'", (string p) => PublisherTelemetryConfigurationFilename = p },
                    { "sd|shopfloordomain=", $"the domain of the shopfloor. if specified this domain is appended (delimited by a ':' to the 'ApplicationURI' property when telemetry is sent 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");
                          }
                      } },
                    { "mq|monitoreditemqueuecapacity=", $"specify how many notifications of monitored items can be stored in the internal queue, if the data can not be sent quick enough to IoTHub\nMin: 1024\nDefault: {MonitoredItemsQueueCapacity}", (int i) => {
                          if (i >= 1024)
                          {
                              MonitoredItemsQueueCapacity = i;
                          }
                          else
                          {
                              throw new OptionException("The monitoreditemqueueitems must be greater than 1024", "monitoreditemqueueitems");
                          }
                      } },
                    { "di|diagnosticsinterval=", $"shows publisher diagnostic info at the specified interval in seconds. 0 disables diagnostic output.\nDefault: {DiagnosticsInterval}", (uint u) => DiagnosticsInterval = u },

                    { "vc|verboseconsole=", $"the output of publisher is shown on the console.\nDefault: {VerboseConsole}", (bool b) => VerboseConsole = b },

                    { "ns|noshutdown=", $"publisher can not be stopped by pressing a key on the console, but will run forever.\nDefault: {_noShutdown}", (bool b) => _noShutdown = 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 can 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: {IotHubMessageSizeMax}\nDefault: {IotHubMessageSize}", (uint u) => {
                          if (u >= 0 && u <= IotHubMessageSizeMax)
                          {
                              IotHubMessageSize = 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");
                          }
                      } },
                    { "dc|deviceconnectionstring=", $"if publisher is not able to register itself with IoTHub, you can create a device with name <applicationname> manually and pass in the connectionstring of this device.\nDefault: none",
                      (string dc) => DeviceConnectionString = dc },
                    { "lf|logfile=", $"the filename of the logfile to use.\nDefault: './Logs/<applicationname>.log.txt'", (string l) => LogFileName = l },
                    { "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");
                          }
                      } },
                    { "oi|opcsamplinginterval=", "the publisher is using this as default value in milliseconds to request the servers to sample the nodes with this interval\n" +
                      "this value might be revised by the OPC UA servers to a supported sampling interval.\n" +
                      "please check the OPC UA specification for details how this is handled by the OPC UA stack.\n" +
                      "a negative value will set the sampling interval to the publishing interval of the subscription this node is on.\n" +
                      $"0 will configure the OPC UA server to sample in the highest possible resolution and should be taken with care.\nDefault: {OpcSamplingInterval}", (int i) => OpcSamplingInterval = i },
                    { "op|opcpublishinginterval=", "the publisher is using this as default value in milliseconds for the publishing interval setting of the subscriptions established to the OPC UA servers.\n" +
                      "please check the OPC UA specification for details how this is handled by the OPC UA stack.\n" +
                      $"a value less than or equal zero will let the server revise the publishing interval.\nDefault: {OpcPublishingInterval}", (int i) => {
                          if (i > 0 && i >= OpcSamplingInterval)
                          {
                              OpcPublishingInterval = i;
                          }
                          else
                          {
                              if (i <= 0)
                              {
                                  OpcPublishingInterval = 0;
                              }
                              else
                              {
                                  throw new OptionException($"The opcpublishinterval ({i}) must be larger than the opcsamplinginterval ({OpcSamplingInterval}).", "opcpublishinterval");
                              }
                          }
                      } },
                    { "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 can 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}  ({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 },

                    // read the display name of the nodes to publish from the server and publish them instead of the node id
                    { "fd|fetchdisplayname=", $"enable to read the display name of a published node from the server. this will increase the runtime.\nDefault: {FetchOpcNodeDisplayName}", (bool b) => FetchOpcNodeDisplayName = 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;
                              OpcOwnCertStorePath = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? OpcOwnCertX509StorePathDefault : OpcOwnCertDirectoryStorePathDefault;
                          }
                          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;
                                OpcTrustedCertStorePath = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? OpcTrustedCertX509StorePathDefault : OpcTrustedCertDirectoryStorePathDefault;
                            }
                            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;
                              OpcRejectedCertStorePath = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? OpcRejectedCertX509StorePathDefault : OpcRejectedCertDirectoryStorePathDefault;
                          }
                          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;
                                OpcIssuerCertStorePath = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? OpcIssuerCertX509StorePathDefault : OpcIssuerCertDirectoryStorePathDefault;
                            }
                            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             = new List <string>();
                int           maxAllowedArguments   = 2;
                int           applicationNameIndex  = 0;
                int           connectionStringIndex = 1;
                if (IsIotEdgeModule)
                {
                    maxAllowedArguments   = 3;
                    applicationNameIndex  = 1;
                    connectionStringIndex = 2;
                }
                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 > maxAllowedArguments || shouldShowHelp)
                {
                    Usage(options);
                    return;
                }
                else if (arguments.Count == maxAllowedArguments)
                {
                    ApplicationName             = arguments[applicationNameIndex];
                    IotHubOwnerConnectionString = arguments[connectionStringIndex];
                }
                else if (arguments.Count == maxAllowedArguments - 1)
                {
                    ApplicationName = arguments[applicationNameIndex];
                }
                else
                {
                    ApplicationName = Utils.GetHostName();
                }

                WriteLine("Publisher is starting up...");

                // Shutdown token sources.
                ShutdownTokenSource = new CancellationTokenSource();

                // init OPC configuration and tracing
                OpcStackConfiguration opcStackConfiguration = new OpcStackConfiguration();
                await opcStackConfiguration.ConfigureAsync();

                _opcTraceInitialized = true;

                // 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.");
                    PublisherOpcApplicationConfiguration.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.");
                    PublisherOpcApplicationConfiguration.CertificateValidator.CertificateValidation += new CertificateValidationEventHandler(CertificateValidator_Default);
                }

                // read telemetry configuration file
                PublisherTelemetryConfiguration.Init();
                if (!await PublisherTelemetryConfiguration.ReadConfigAsync())
                {
                    return;
                }

                // read node configuration file
                // read it before loading desired properties in case of misconfigurations
                PublisherNodeConfiguration.Init();
                if (!await PublisherNodeConfiguration.ReadConfigAsync())
                {
                    return;
                }

                // initialize and start IoTHub messaging
                IotHubCommunication = new IotHubMessaging();
                if (!await IotHubCommunication.InitAsync())
                {
                    return;
                }

                if (!await PublisherNodeConfiguration.CreateOpcPublishingDataAsync())
                {
                    return;
                }

                // kick off the task to maintain all sessions
                Task sessionConnectorAsync = Task.Run(async() => await SessionConnectorAsync(ShutdownTokenSource.Token));

                // initialize publisher diagnostics
                Diagnostics.Init();

                // stop on user request
                WriteLine("");
                WriteLine("");
                if (_noShutdown)
                {
                    // wait forever if asked to do so
                    WriteLine("Publisher is running infinite...");
                    await Task.Delay(Timeout.Infinite);
                }
                else
                {
                    WriteLine("Publisher is running. Press any key to quit.");
                    try
                    {
                        ReadKey(true);
                    }
                    catch
                    {
                        // wait forever if there is no console
                        WriteLine("There is no console. Publisher is running infinite...");
                        await Task.Delay(Timeout.Infinite);
                    }
                }
                WriteLine("");
                WriteLine("");
                ShutdownTokenSource.Cancel();
                WriteLine("Publisher is shutting down...");

                // Wait for session connector completion
                await sessionConnectorAsync;

                // Clean up Publisher sessions
                await SessionShutdownAsync();

                // shutdown the IoTHub messaging
                await IotHubCommunication.ShutdownAsync();

                IotHubCommunication = null;

                // shutdown diagnostics
                await ShutdownAsync();

                // free resources
                PublisherTelemetryConfiguration.Deinit();
                PublisherNodeConfiguration.Deinit();
                ShutdownTokenSource = null;
            }
            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...");
                }
            }
        }
Example #3
0
        /// <summary>
        /// Asynchronous part of the main method of the app.
        /// </summary>
        public async static Task MainAsync(string[] args)
        {
            try
            {
                var shouldShowHelp = false;

                // Shutdown token sources.
                ShutdownTokenSource = new CancellationTokenSource();

                // detect the runtime environment. either we run standalone (native or containerized) or as IoT Edge module (containerized)
                // check if we have an environment variable containing an IoT Edge connectionstring, we run as IoT Edge module
                if (IsIotEdgeModule)
                {
                    WriteLine("IoTEdge detected.");
                }

                // command line options
                Mono.Options.OptionSet options = new Mono.Options.OptionSet {
                    // Publisher configuration options
                    { "pf|publishfile=", $"the filename to configure the nodes to publish.\nDefault: '{PublisherNodeConfigurationFilename}'", (string p) => PublisherNodeConfigurationFilename = p },
                    { "tc|telemetryconfigfile=", $"the filename to configure the ingested telemetry\nDefault: '{PublisherTelemetryConfigurationFilename}'", (string p) => PublisherTelemetryConfigurationFilename = p },
                    { "s|site=", $"the site OPC Publisher is working in. if specified this domain is appended (delimited by a ':' to the 'ApplicationURI' property when telemetry is sent to IoTHub.\n" +
                      "The value must follow the syntactical rules of a DNS hostname.\nDefault: not set", (string s) => {
                          Regex siteNameRegex = 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 (siteNameRegex.IsMatch(s))
                          {
                              PublisherSite = s;
                          }
                          else
                          {
                              throw new OptionException("The shopfloor site is not a valid DNS hostname.", "site");
                          }
                      } },
                    { "sd|shopfloordomain=", $"same as site option, only there for backward compatibility\n" +
                      "The value must follow the syntactical rules of a DNS hostname.\nDefault: not set", (string s) => {
                          Regex siteNameRegex = 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 (siteNameRegex.IsMatch(s))
                          {
                              PublisherSite = s;
                          }
                          else
                          {
                              throw new OptionException("The shopfloor domain is not a valid DNS hostname.", "shopfloordomain");
                          }
                      } },
                    { "ic|iotcentral", $"publisher will send OPC UA data in IoTCentral compatible format (DisplayName of a node is used as key, this key is the Field name in IoTCentral). you need to ensure that all DisplayName's are unique. (Auto enables fetch display name)\nDefault: {IotCentralMode}", b => IotCentralMode = FetchOpcNodeDisplayName = b != null },
                    { "sw|sessionconnectwait=", $"specify the wait time in seconds publisher is trying to connect to disconnected endpoints and starts monitoring unmonitored items\nMin: 10\nDefault: {SessionConnectWaitSec}", (int i) => {
                          if (i > 10)
                          {
                              SessionConnectWaitSec = i;
                          }
                          else
                          {
                              throw new OptionException("The sessionconnectwait must be greater than 10 sec", "sessionconnectwait");
                          }
                      } },
                    { "mq|monitoreditemqueuecapacity=", $"specify how many notifications of monitored items can be stored in the internal queue, if the data can not be sent quick enough to IoTHub\nMin: 1024\nDefault: {MonitoredItemsQueueCapacity}", (int i) => {
                          if (i >= 1024)
                          {
                              MonitoredItemsQueueCapacity = i;
                          }
                          else
                          {
                              throw new OptionException("The monitoreditemqueueitems must be greater than 1024", "monitoreditemqueueitems");
                          }
                      } },
                    { "di|diagnosticsinterval=", $"shows publisher diagnostic info at the specified interval in seconds (need log level info). 0 disables diagnostic output.\nDefault: {DiagnosticsInterval}", (uint u) => DiagnosticsInterval = u },

                    { "vc|verboseconsole=", $"ignored, only supported for backward comaptibility.", b => {} },

                    { "ns|noshutdown=", $"same as runforever.\nDefault: {_noShutdown}", (bool b) => _noShutdown = b },
                    { "rf|runforever", $"publisher can not be stopped by pressing a key on the console, but will run forever.\nDefault: {_noShutdown}", b => _noShutdown = b != null },

                    { "lf|logfile=", $"the filename of the logfile to use.\nDefault: './{_logFileName}'", (string l) => _logFileName = l },
                    { "lt|logflushtimespan=", $"the timespan in seconds when the logfile should be flushed.\nDefault: {_logFileFlushTimeSpanSec} sec", (int s) => {
                          if (s > 0)
                          {
                              _logFileFlushTimeSpanSec = TimeSpan.FromSeconds(s);
                          }
                          else
                          {
                              throw new Mono.Options.OptionException("The logflushtimespan must be a positive number.", "logflushtimespan");
                          }
                      } },
                    { "ll|loglevel=", $"the loglevel to use (allowed: fatal, error, warn, info, debug, verbose).\nDefault: info", (string l) => {
                          List <string> logLevels = new List <string> {
                              "fatal", "error", "warn", "info", "debug", "verbose"
                          };
                          if (logLevels.Contains(l.ToLowerInvariant()))
                          {
                              _logLevel = l.ToLowerInvariant();
                          }
                          else
                          {
                              throw new Mono.Options.OptionException("The loglevel must be one of: fatal, error, warn, info, debug, verbose", "loglevel");
                          }
                      } },
                    // IoTHub specific options
                    { "ih|iothubprotocol=", $"{(IsIotEdgeModule ? "not supported when running as IoTEdge module (Mqtt_Tcp_Only is enforced)\n" : $"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) => {
                          if (IsIotEdgeModule)
                          {
                              if (p != Microsoft.Azure.Devices.Client.TransportType.Mqtt_Tcp_Only)
                              {
                                  WriteLine("When running as IoTEdge module Mqtt_Tcp_Only is enforced.");
                                  IotHubProtocol = Microsoft.Azure.Devices.Client.TransportType.Mqtt_Tcp_Only;
                              }
                          }
                          else
                          {
                              IotHubProtocol = p;
                          }
                      } },
                    { "ms|iothubmessagesize=", $"the max size of a message which can 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: {HubMessageSizeMax}\nDefault: {HubMessageSize}", (uint u) => {
                          if (u >= 0 && u <= HubMessageSizeMax)
                          {
                              HubMessageSize = 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");
                          }
                      } },
                    { "dc|deviceconnectionstring=", $"{(IsIotEdgeModule ? "not supported when running as IoTEdge module\n" : $"if publisher is not able to register itself with IoTHub, you can create a device with name <applicationname> manually and pass in the connectionstring of this device.\nDefault: none")}",
                      (string dc) => DeviceConnectionString = (IsIotEdgeModule ? null : dc) },
                    { "c|connectionstring=", $"the IoTHub owner connectionstring.\nDefault: none",
                      (string cs) => IotHubOwnerConnectionString = cs },

                    // opc server configuration options
                    { "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");
                          }
                      } },
                    { "ol|opcmaxstringlen=", $"the max length of a string opc can transmit/receive.\nDefault: {OpcMaxStringLength}", (int i) => {
                          if (i > 0)
                          {
                              OpcMaxStringLength = i;
                          }
                          else
                          {
                              throw new OptionException("The max opc string length must be larger than 0.", "opcmaxstringlen");
                          }
                      } },
                    { "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");
                          }
                      } },
                    { "oi|opcsamplinginterval=", "the publisher is using this as default value in milliseconds to request the servers to sample the nodes with this interval\n" +
                      "this value might be revised by the OPC UA servers to a supported sampling interval.\n" +
                      "please check the OPC UA specification for details how this is handled by the OPC UA stack.\n" +
                      "a negative value will set the sampling interval to the publishing interval of the subscription this node is on.\n" +
                      $"0 will configure the OPC UA server to sample in the highest possible resolution and should be taken with care.\nDefault: {OpcSamplingInterval}", (int i) => OpcSamplingInterval = i },
                    { "op|opcpublishinginterval=", "the publisher is using this as default value in milliseconds for the publishing interval setting of the subscriptions established to the OPC UA servers.\n" +
                      "please check the OPC UA specification for details how this is handled by the OPC UA stack.\n" +
                      $"a value less than or equal zero will let the server revise the publishing interval.\nDefault: {OpcPublishingInterval}", (int i) => {
                          if (i > 0 && i >= OpcSamplingInterval)
                          {
                              OpcPublishingInterval = i;
                          }
                          else
                          {
                              if (i <= 0)
                              {
                                  OpcPublishingInterval = 0;
                              }
                              else
                              {
                                  throw new OptionException($"The opcpublishinterval ({i}) must be larger than the opcsamplinginterval ({OpcSamplingInterval}).", "opcpublishinterval");
                              }
                          }
                      } },
                    { "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 can 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=", $"ignored, only supported for backward comaptibility.", i => {} },

                    { "as|autotrustservercerts=", $"same as autoaccept, only supported for backward cmpatibility.\nDefault: {OpcPublisherAutoTrustServerCerts}", (bool b) => OpcPublisherAutoTrustServerCerts = b },
                    { "aa|autoaccept", $"the publisher trusts all servers it is establishing a connection to.\nDefault: {OpcPublisherAutoTrustServerCerts}", b => OpcPublisherAutoTrustServerCerts = b != null },

                    // trust own public cert option
                    { "tm|trustmyself=", $"same as trustowncert.\nDefault: {TrustMyself}", (bool b) => TrustMyself = b },
                    { "to|trustowncert", $"the publisher certificate is put into the trusted certificate store automatically.\nDefault: {TrustMyself}", t => TrustMyself = t != null },
                    // read the display name of the nodes to publish from the server and publish them instead of the node id
                    { "fd|fetchdisplayname=", $"same as fetchname.\nDefault: {FetchOpcNodeDisplayName}", (bool b) => FetchOpcNodeDisplayName = IotCentralMode ? true : b },
                    { "fn|fetchname", $"enable to read the display name of a published node from the server. this will increase the runtime.\nDefault: {FetchOpcNodeDisplayName}", b => FetchOpcNodeDisplayName = IotCentralMode ? true : b != null },

                    // 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(CertificateStoreType.Directory, StringComparison.OrdinalIgnoreCase))
                          {
                              OpcOwnCertStoreType = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? X509Store : CertificateStoreType.Directory;
                              OpcOwnCertStorePath = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? OpcOwnCertX509StorePathDefault : OpcOwnCertDirectoryStorePathDefault;
                          }
                          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(CertificateStoreType.Directory, StringComparison.OrdinalIgnoreCase))
                            {
                                OpcTrustedCertStoreType = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? X509Store : CertificateStoreType.Directory;
                                OpcTrustedCertStorePath = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? OpcTrustedCertX509StorePathDefault : OpcTrustedCertDirectoryStorePathDefault;
                            }
                            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(CertificateStoreType.Directory, StringComparison.OrdinalIgnoreCase))
                          {
                              OpcRejectedCertStoreType = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? X509Store : CertificateStoreType.Directory;
                              OpcRejectedCertStorePath = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? OpcRejectedCertX509StorePathDefault : OpcRejectedCertDirectoryStorePathDefault;
                          }
                          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(CertificateStoreType.Directory, StringComparison.OrdinalIgnoreCase))
                            {
                                OpcIssuerCertStoreType = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? X509Store : CertificateStoreType.Directory;
                                OpcIssuerCertStorePath = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? OpcIssuerCertX509StorePathDefault : OpcIssuerCertDirectoryStorePathDefault;
                            }
                            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(CertificateStoreType.Directory, StringComparison.OrdinalIgnoreCase))
                          {
                              IotDeviceCertStoreType = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? X509Store : CertificateStoreType.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
                    { "i|install", $"register OPC Publisher with IoTHub and then exits.\nDefault:  {_installOnly}", i => _installOnly = i != null },
                    { "h|help", "show this message and exit", h => shouldShowHelp = h != null },
                };


                List <string> extraArgs = new List <string>();
                try
                {
                    // parse the command line
                    extraArgs = options.Parse(args);
                }
                catch (OptionException e)
                {
                    // initialize logging
                    InitLogging();

                    // show message
                    Logger.Error(e, "Error in command line options");
                    Logger.Error($"Command line arguments: {String.Join(" ", args)}");

                    // show usage
                    Usage(options);
                    return;
                }

                // initialize logging
                InitLogging();

                // show usage if requested
                if (shouldShowHelp)
                {
                    Usage(options);
                    return;
                }

                // Validate and parse extra arguments.
                const int APP_NAME_INDEX = 0;
                const int CS_INDEX       = 1;
                switch (extraArgs.Count)
                {
                case 0:
                {
                    ApplicationName = Utils.GetHostName();
                    break;
                }

                case 1:
                {
                    ApplicationName = extraArgs[APP_NAME_INDEX];
                    break;
                }

                case 2:
                {
                    ApplicationName = extraArgs[APP_NAME_INDEX];
                    if (IsIotEdgeModule)
                    {
                        WriteLine($"Warning: connection string parameter is not supported in IoTEdge context, given parameter is ignored");
                    }
                    else
                    {
                        IotHubOwnerConnectionString = extraArgs[CS_INDEX];
                    }
                    break;
                }

                default:
                {
                    Logger.Error("Error in command line options");
                    Logger.Error($"Command line arguments: {String.Join(" ", args)}");
                    Usage(options);
                    return;
                }
                }

                // install only if requested
                if (_installOnly)
                {
                    // initialize and start IoTHub communication
                    IotHubCommunication = new IotHubCommunication(ShutdownTokenSource.Token);
                    if (!await IotHubCommunication.InitAsync())
                    {
                        return;
                    }
                    Logger.Information("Installation completed. Exiting...");
                    return;
                }

                // start operation
                Logger.Information("Publisher is starting up...");

                // allow canceling the application
                var quitEvent = new ManualResetEvent(false);
                try
                {
                    Console.CancelKeyPress += (sender, eArgs) =>
                    {
                        quitEvent.Set();
                        eArgs.Cancel = true;
                        ShutdownTokenSource.Cancel();
                    };
                }
                catch
                {
                }

                // init OPC configuration and tracing
                OpcStackConfiguration opcStackConfiguration = new OpcStackConfiguration();
                await opcStackConfiguration.ConfigureAsync();

                // log shopfloor site setting
                if (string.IsNullOrEmpty(PublisherSite))
                {
                    Logger.Information("There is no site configured.");
                }
                else
                {
                    Logger.Information($"Publisher is in site '{PublisherSite}'.");
                }

                // Set certificate validator.
                if (OpcPublisherAutoTrustServerCerts)
                {
                    Logger.Information("Publisher configured to auto trust server certificates of the servers it is connecting to.");
                    PublisherOpcApplicationConfiguration.CertificateValidator.CertificateValidation += new CertificateValidationEventHandler(CertificateValidator_AutoTrustServerCerts);
                }
                else
                {
                    Logger.Information("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.");
                    PublisherOpcApplicationConfiguration.CertificateValidator.CertificateValidation += new CertificateValidationEventHandler(CertificateValidator_Default);
                }

                // start our server interface
                try
                {
                    Logger.Information($"Starting server on endpoint {PublisherOpcApplicationConfiguration.ServerConfiguration.BaseAddresses[0].ToString()} ...");
                    _publisherServer = new PublisherServer();
                    _publisherServer.Start(PublisherOpcApplicationConfiguration);
                    Logger.Information("Server started.");
                }
                catch (Exception e)
                {
                    Logger.Fatal(e, "Failed to start Publisher OPC UA server.");
                    Logger.Fatal("exiting...");
                    return;
                }

                // read telemetry configuration file
                PublisherTelemetryConfiguration.Init(ShutdownTokenSource.Token);
                if (!await PublisherTelemetryConfiguration.ReadConfigAsync())
                {
                    return;
                }

                // read node configuration file
                PublisherNodeConfiguration.Init();
                if (!await PublisherNodeConfiguration.ReadConfigAsync())
                {
                    return;
                }

                // initialize hub communication
                if (IsIotEdgeModule)
                {
                    // initialize and start EdgeHub communication
                    IotEdgeHubCommunication = new IotEdgeHubCommunication(ShutdownTokenSource.Token);
                    if (!await IotEdgeHubCommunication.InitAsync())
                    {
                        return;
                    }
                }
                else
                {
                    // initialize and start IoTHub communication
                    IotHubCommunication = new IotHubCommunication(ShutdownTokenSource.Token);
                    if (!await IotHubCommunication.InitAsync())
                    {
                        return;
                    }
                }

                if (!await CreateOpcPublishingDataAsync())
                {
                    return;
                }

                // kick off OPC session creation and node monitoring
                await SessionStartAsync();

                // Show notification on session events
                _publisherServer.CurrentInstance.SessionManager.SessionActivated += ServerEventStatus;
                _publisherServer.CurrentInstance.SessionManager.SessionClosing   += ServerEventStatus;
                _publisherServer.CurrentInstance.SessionManager.SessionCreated   += ServerEventStatus;

                // initialize publisher diagnostics
                Diagnostics.Init();

                // stop on user request
                Logger.Information("");
                Logger.Information("");
                if (_noShutdown)
                {
                    // wait forever if asked to do so
                    Logger.Information("Publisher is running infinite...");
                    await Task.Delay(Timeout.Infinite);
                }
                else
                {
                    Logger.Information("Publisher is running. Press CTRL-C to quit.");

                    // wait for Ctrl-C
                    quitEvent.WaitOne(Timeout.Infinite);
                }

                Logger.Information("");
                Logger.Information("");
                ShutdownTokenSource.Cancel();
                Logger.Information("Publisher is shutting down...");

                // stop the server
                _publisherServer.Stop();

                // shutdown all OPC sessions
                await SessionShutdownAsync();

                // shutdown the IoTHub messaging
                await IotHubCommunication.ShutdownAsync();

                IotHubCommunication = null;

                // shutdown diagnostics
                await ShutdownAsync();

                // free resources
                PublisherTelemetryConfiguration.Deinit();
                PublisherNodeConfiguration.Deinit();
                ShutdownTokenSource = null;
            }
            catch (Exception e)
            {
                Logger.Fatal(e, e.StackTrace);
                e = e.InnerException ?? null;
                while (e != null)
                {
                    Logger.Fatal(e, e.StackTrace);
                    e = e.InnerException ?? null;
                }
                Logger.Fatal("Publisher exiting... ");
            }
        }