private static void OpcClient_BreakDetected(object sender, EventArgs e)
        {
            Console.WriteLine("BREAK DETECTED");

            var logLevelMessage = new LogLevelMessage {
                logLevel = LogLevelMessage.LogLevel.Warning, code = "01", message = "BREAK DETECTED"
            };

            SendLogLevelMessage(logLevelMessage).Wait();
        }
        private static void OpcClient_Disconnected(object sender, EventArgs e)
        {
            Console.WriteLine("DISCONNECTED");

            var logLevelMessage = new LogLevelMessage {
                logLevel = LogLevelMessage.LogLevel.Error, code = "02", message = "DISCONNECTED"
            };

            SendLogLevelMessage(logLevelMessage).Wait();
        }
        private static void OpcClient_Disconnecting(object sender, EventArgs e)
        {
            Console.WriteLine("DISCONNECTING");

            var logLevelMessage = new LogLevelMessage {
                logLevel = LogLevelMessage.LogLevel.Warning, code = "03", message = "DISCONNECTING"
            };

            SendLogLevelMessage(logLevelMessage).Wait();
        }
        private static void OpcClient_Reconnected(object sender, EventArgs e)
        {
            Console.WriteLine("RECONNECTED");

            var logLevelMessage = new LogLevelMessage {
                logLevel = LogLevelMessage.LogLevel.Warning, code = "04", message = "RECONNECTED"
            };

            SendLogLevelMessage(logLevelMessage).Wait();
        }
        private static void OpcClient_Reconnecting(object sender, EventArgs e)
        {
            Console.WriteLine("RECONNECTING");

            var logLevelMessage = new LogLevelMessage {
                logLevel = LogLevelMessage.LogLevel.Error, code = "05", message = "RECONNECTING"
            };

            SendLogLevelMessage(logLevelMessage).Wait();
        }
        private static async Task SendLogLevelMessage(LogLevelMessage moduleStateMessage)
        {
            if (moduleStateMessage.logLevel < MinimalLogLevel)
            {
                Console.WriteLine($"Error message {moduleStateMessage.code}- Level {moduleStateMessage.logLevel} ignored.");

                return;
            }

            var jsonMessage = JsonConvert.SerializeObject(moduleStateMessage);

            var messageBytes = Encoding.UTF8.GetBytes(jsonMessage);

            using (var message = new Message(messageBytes))
            {
                message.ContentEncoding = "utf-8";
                message.ContentType     = "application/json";
                message.Properties.Add("content-type", "application/opcua-error-json");

                await ioTHubModuleClient.SendEventAsync("outputError", message);
            }
        }
        private static async Task onDesiredPropertiesUpdate(TwinCollection desiredProperties, object userContext)
        {
            if (desiredProperties.Count == 0)
            {
                Console.WriteLine("Empty desired properties ignored.");

                return;
            }

            try
            {
                Console.WriteLine("Desired property change:");
                Console.WriteLine(JsonConvert.SerializeObject(desiredProperties));

                var client = userContext as ModuleClient;

                if (client == null)
                {
                    throw new InvalidOperationException($"UserContext doesn't contain expected ModuleClient");
                }

                var reportedProperties = new TwinCollection();

                if (desiredProperties.Contains("address"))
                {
                    if (desiredProperties["address"] != null)
                    {
                        Address = desiredProperties["address"];
                    }
                    else
                    {
                        Address = DefaultAddress;
                    }

                    Console.WriteLine($"Address changed to {Address}");

                    reportedProperties["address"] = Address;
                }

                if (desiredProperties.Contains("nodePotentio1"))
                {
                    if (desiredProperties["nodePotentio1"] != null)
                    {
                        NodePotentio1 = desiredProperties["nodePotentio1"];
                    }
                    else
                    {
                        NodePotentio1 = DefaultNodePotentio1;
                    }

                    Console.WriteLine($"NodePotentio1 changed to {NodePotentio1}");

                    reportedProperties["nodePotentio1"] = NodePotentio1;
                }

                if (desiredProperties.Contains("nodePotentio2"))
                {
                    if (desiredProperties["nodePotentio2"] != null)
                    {
                        NodePotentio2 = desiredProperties["nodePotentio2"];
                    }
                    else
                    {
                        NodePotentio2 = DefaultNodePotentio2;
                    }

                    Console.WriteLine($"NodePotentio2 changed to {NodePotentio2}");

                    reportedProperties["nodePotentio2"] = NodePotentio2;
                }

                if (desiredProperties.Contains("nodeSwitch1"))
                {
                    if (desiredProperties["nodeSwitch1"] != null)
                    {
                        NodeSwitch1 = desiredProperties["nodeSwitch1"];
                    }
                    else
                    {
                        NodeSwitch1 = DefaultNodeSwitch1;
                    }

                    Console.WriteLine($"NodeSwitch1 changed to {NodeSwitch1}");

                    reportedProperties["nodeSwitch1"] = NodeSwitch1;
                }

                if (desiredProperties.Contains("nodeSwitch2"))
                {
                    if (desiredProperties["nodeSwitch2"] != null)
                    {
                        NodeSwitch2 = desiredProperties["nodeSwitch2"];
                    }
                    else
                    {
                        NodeSwitch2 = DefaultNodeSwitch2;
                    }

                    Console.WriteLine($"NodeSwitch2 changed to {NodeSwitch2}");

                    reportedProperties["nodeSwitch2"] = NodeSwitch2;
                }

                if (desiredProperties.Contains("nodeRelay1"))
                {
                    if (desiredProperties["nodeRelay1"] != null)
                    {
                        NodeRelay1 = desiredProperties["nodeRelay1"];
                    }
                    else
                    {
                        NodeRelay1 = DefaultNodeRelay1;
                    }

                    Console.WriteLine($"NodeRelay1 changed to {NodeRelay1}");

                    reportedProperties["nodeRelay1"] = NodeRelay1;
                }

                if (desiredProperties.Contains("nodeRelay2"))
                {
                    if (desiredProperties["nodeRelay2"] != null)
                    {
                        NodeRelay2 = desiredProperties["nodeRelay2"];
                    }
                    else
                    {
                        NodeRelay2 = DefaultNodeRelay2;
                    }

                    Console.WriteLine($"NodeRelay2 changed to {NodeRelay2}");

                    reportedProperties["nodeRelay2"] = NodeRelay2;
                }

                if (desiredProperties.Contains("licenseKey"))
                {
                    if (desiredProperties["licenseKey"] != null)
                    {
                        LicenseKey = desiredProperties["licenseKey"];
                    }
                    else
                    {
                        LicenseKey = DefaultLicenseKey;
                    }

                    Console.WriteLine($"LicenseKey changed to {LicenseKey}");

                    reportedProperties["licenseKey"] = LicenseKey;
                }

                if (desiredProperties.Contains("minimalLogLevel"))
                {
                    if (desiredProperties["minimalLogLevel"] != null)
                    {
                        var minimalLogLevel = desiredProperties["minimalLogLevel"];

                        // casting from int to enum needed
                        var minimalLogLevelInteger = Convert.ToInt32(minimalLogLevel);

                        MinimalLogLevel = (LogLevelMessage.LogLevel)minimalLogLevelInteger;
                    }
                    else
                    {
                        MinimalLogLevel = DefaultMinimalLogLevel;
                    }

                    Console.WriteLine($"MinimalLogLevel changed to '{MinimalLogLevel}'");

                    reportedProperties["minimalLogLevel"] = MinimalLogLevel;
                }
                else
                {
                    Console.WriteLine($"MinimalLogLevel ignored");
                }

                if (reportedProperties.Count > 0)
                {
                    await client.UpdateReportedPropertiesAsync(reportedProperties);

                    if (opcClient != null)
                    {
                        opcClient.Disconnect();

                        if (LicenseKey != string.Empty)
                        {
                            Opc.UaFx.Licenser.LicenseKey = LicenseKey;
                        }
                        else
                        {
                            Console.WriteLine("No license key available.");

                            Opc.UaFx.Licenser.LicenseKey = string.Empty;
                        }

                        opcClient.ServerAddress = new Uri(Address);
                        opcClient.Connect();

                        var commands = new List <OpcSubscribeDataChange>();

                        if (!string.IsNullOrEmpty(NodePotentio1))
                        {
                            commands.Add(new OpcSubscribeDataChange(NodePotentio1, OpcDataChangeTrigger.StatusValue, HandleDataChangedMachineLineNode));
                        }
                        else
                        {
                            System.Console.WriteLine("Ignored empty NodePotentio1");
                        }

                        if (!string.IsNullOrEmpty(NodePotentio2))
                        {
                            commands.Add(new OpcSubscribeDataChange(NodePotentio2, OpcDataChangeTrigger.StatusValue, HandleDataChangedMachineLineNode));
                        }
                        else
                        {
                            System.Console.WriteLine("Ignored empty NodePotentio2");
                        }

                        if (!string.IsNullOrEmpty(NodeSwitch1))
                        {
                            commands.Add(new OpcSubscribeDataChange(NodeSwitch1, OpcDataChangeTrigger.StatusValue, HandleDataChangedMachineLineNode));
                        }
                        else
                        {
                            System.Console.WriteLine("Ignored empty NodeSwitch1");
                        }

                        if (!string.IsNullOrEmpty(NodeSwitch2))
                        {
                            commands.Add(new OpcSubscribeDataChange(NodeSwitch2, OpcDataChangeTrigger.StatusValue, HandleDataChangedMachineLineNode));
                        }
                        else
                        {
                            System.Console.WriteLine("Ignored empty NodeSwitch2");
                        }

                        if (!string.IsNullOrEmpty(NodeRelay1))
                        {
                            commands.Add(new OpcSubscribeDataChange(NodeRelay1, OpcDataChangeTrigger.StatusValue, HandleDataChangedMachineLineNode));
                        }
                        else
                        {
                            System.Console.WriteLine("Ignored empty NodeRelay1");
                        }

                        if (!string.IsNullOrEmpty(NodeRelay2))
                        {
                            commands.Add(new OpcSubscribeDataChange(NodeRelay2, OpcDataChangeTrigger.StatusValue, HandleDataChangedMachineLineNode));
                        }
                        else
                        {
                            System.Console.WriteLine("Ignored empty NodeRelay2");
                        }

                        OpcSubscription subscription = opcClient.SubscribeNodes(commands);

                        Console.WriteLine($"Client started... (listening to '{NodePotentio1},{NodePotentio2},{NodeSwitch1},{NodeSwitch2},{NodeRelay1},{NodeRelay2}' at '{Address}')");
                    }
                    else
                    {
                        Console.WriteLine("Client construction postponed.");
                    }

                    Console.WriteLine("Changes to desired properties can be enforced by restarting the module.");
                }
            }
            catch (AggregateException ex)
            {
                Console.WriteLine($"Desired properties change error: {ex.Message}");

                var logLevelMessage = new LogLevelMessage {
                    logLevel = LogLevelMessage.LogLevel.Error, code = "98", message = $"Desired properties change error: {ex.Message}"
                };

                await SendLogLevelMessage(logLevelMessage);

                foreach (Exception exception in ex.InnerExceptions)
                {
                    Console.WriteLine($"Error when receiving desired properties: {exception}");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error when receiving desired properties: {ex.Message}");

                var logLevelMessage = new LogLevelMessage {
                    logLevel = LogLevelMessage.LogLevel.Error, code = "99", message = $"Error when receiving desired properties: {ex.Message}"
                };

                await SendLogLevelMessage(logLevelMessage);
            }
        }
        private static void ThreadBody()
        {
            try
            {
                if (LicenseKey != string.Empty)
                {
                    Opc.UaFx.Licenser.LicenseKey = LicenseKey;
                }
                else
                {
                    Console.WriteLine("No license key available.");

                    Opc.UaFx.Licenser.LicenseKey = string.Empty;
                }

                opcClient                = new OpcClient(Address);
                opcClient.Connecting    += OpcClient_Connecting;
                opcClient.Connected     += OpcClient_Connected;
                opcClient.BreakDetected += OpcClient_BreakDetected;
                opcClient.Disconnected  += OpcClient_Disconnected;
                opcClient.Disconnecting += OpcClient_Disconnecting;
                opcClient.Reconnected   += OpcClient_Reconnected;
                opcClient.Reconnecting  += OpcClient_Reconnecting;

                opcClient.Connect();

                var commands = new List <OpcSubscribeDataChange>();

                if (!string.IsNullOrEmpty(NodePotentio1))
                {
                    commands.Add(new OpcSubscribeDataChange(NodePotentio1, OpcDataChangeTrigger.StatusValue, HandleDataChangedMachineLineNode));
                }
                else
                {
                    System.Console.WriteLine("Ignored empty NodePotentio1");
                }

                if (!string.IsNullOrEmpty(NodePotentio2))
                {
                    commands.Add(new OpcSubscribeDataChange(NodePotentio2, OpcDataChangeTrigger.StatusValue, HandleDataChangedMachineLineNode));
                }
                else
                {
                    System.Console.WriteLine("Ignored empty NodePotentio2");
                }

                if (!string.IsNullOrEmpty(NodeSwitch1))
                {
                    commands.Add(new OpcSubscribeDataChange(NodeSwitch1, OpcDataChangeTrigger.StatusValue, HandleDataChangedMachineLineNode));
                }
                else
                {
                    System.Console.WriteLine("Ignored empty NodeSwitch1");
                }

                if (!string.IsNullOrEmpty(NodeSwitch2))
                {
                    commands.Add(new OpcSubscribeDataChange(NodeSwitch2, OpcDataChangeTrigger.StatusValue, HandleDataChangedMachineLineNode));
                }
                else
                {
                    System.Console.WriteLine("Ignored empty NodeSwitch2");
                }

                if (!string.IsNullOrEmpty(NodeRelay1))
                {
                    commands.Add(new OpcSubscribeDataChange(NodeRelay1, OpcDataChangeTrigger.StatusValue, HandleDataChangedMachineLineNode));
                }
                else
                {
                    System.Console.WriteLine("Ignored empty NodeRelay1");
                }

                if (!string.IsNullOrEmpty(NodeRelay2))
                {
                    commands.Add(new OpcSubscribeDataChange(NodeRelay2, OpcDataChangeTrigger.StatusValue, HandleDataChangedMachineLineNode));
                }
                else
                {
                    System.Console.WriteLine("Ignored empty NodeRelay2");
                }

                OpcSubscription subscription = opcClient.SubscribeNodes(commands);

                Console.WriteLine($"Client started... (listening to '{NodePotentio1},{NodePotentio2},{NodeSwitch1},{NodeSwitch2},{NodeRelay1},{NodeRelay2}' at '{Address}')");

                while (true)
                {
                    // keep thread alive
                    Thread.Sleep(1000);
                }
            }
            catch (System.Exception ex)
            {
                // TODO: Test for Timeout towards OPC-UA Server connection

                Console.WriteLine($"Fatal ThreadBody exception: {ex.Message}");

                var logLevelMessage = new LogLevelMessage {
                    logLevel = LogLevelMessage.LogLevel.Critical, code = "00", message = $"ThreadBody exception: {ex.Message}"
                };

                SendLogLevelMessage(logLevelMessage).Wait();

                Console.WriteLine("Halted...");
            }
        }