<commit_before>﻿/*
    This file is part of ZWaveLib Project source code.

    ZWaveLib is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    ZWaveLib is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with ZWaveLib.  If not, see <http://www.gnu.org/licenses/>.  
*/

/*
 *     Author: <NAME> <<EMAIL>>
 *     Project Homepage: https://github.com/genielabs/zwave-lib-dotnet
 */

using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Serialization;

using SerialPortLib;

using ZWaveLib.Values;
using ZWaveLib.CommandClasses;

namespace ZWaveLib
{
    /// <summary>
    /// Z-Wave Controller.
    /// </summary>
    public class ZWaveController : IDisposable
    {
        
        #region Private fields

        private SerialPortInput serialPort;
        private string portName = "";

        private ManualResetEvent sendMessageAck = new ManualResetEvent(false);
        private bool busyReceiving = false;
        private bool discoveryRunning = false;

        private ZWaveMessage pendingRequest;
        private QueryStage currentStage;

        private bool disposing = false;
        private Thread queueManager;
        private List<ZWaveMessage> queuedMessages;

        private ControllerStatus controllerStatus = ControllerStatus.Disconnected;

        private List<ZWaveNode> nodeList = new List<ZWaveNode>();

        private byte[] lastMessage = null;
        private DateTime lastMessageTimestamp = DateTime.UtcNow;

        private string configFolder;

        #endregion

        #region Public events

        /// <summary>
        /// Controller status changed event handler.
        /// </summary>
        public delegate void ControllerStatusChangedEventHandler(object sender, ControllerStatusEventArgs args);

        /// <summary>
        /// Occurs when controller status changed.
        /// </summary>
        public event ControllerStatusChangedEventHandler ControllerStatusChanged;

        /// <summary>
        /// Discovery progress event handler.
        /// </summary>
        public delegate void DiscoveryProgressEventHandler(object sender, DiscoveryProgressEventArgs args);

        /// <summary>
        /// Occurs during discovery process.
        /// </summary>
        public event DiscoveryProgressEventHandler DiscoveryProgress;

        /// <summary>
        /// Node operation progress event handler.
        /// </summary>
        public delegate void NodeOperationProgressEventHandler(object sender, NodeOperationProgressEventArgs args);

        /// <summary>
        /// Occurs when a node operation is taking place.
        /// </summary>
        public event NodeOperationProgressEventHandler NodeOperationProgress;

        /// <summary>
        /// Node updated event handler.
        /// </summary>
        public delegate void NodeUpdatedEventHandler(object sender, NodeUpdatedEventArgs args);

        /// <summary>
        /// Occurs when node data is updated.
        /// </summary>
        public event NodeUpdatedEventHandler NodeUpdated;

        #endregion

        #region Lifecycle

        /// <summary>
        /// Initializes a new instance of the <see cref="ZWaveLib.ZWaveController"/> class.
        /// </summary>
        public ZWaveController()
        {
            string codeBase = GetType().Assembly.CodeBase;
            UriBuilder uri = new UriBuilder(codeBase);
            string path = Uri.UnescapeDataString(uri.Path);
            configFolder = Path.GetDirectoryName(path);
            // Setup Serial Port
            serialPort = new SerialPortInput();
            serialPort.MessageReceived += SerialPort_MessageReceived;
            serialPort.ConnectionStatusChanged += SerialPort_ConnectionStatusChanged;
            // Setup Queue Manager Task
            queuedMessages = new List<ZWaveMessage>();
            queueManager = new Thread(QueueManagerTask);
            queueManager.Start();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ZWaveLib.ZWaveController"/> class.
        /// </summary>
        /// <param name="portName">The serial port name.</param>
        public ZWaveController(string portName) : this()
        {
            PortName = portName;
        }

        public void Dispose()
        {
            // Dispose the message Queue Manager
            disposing = true;
            if (!queueManager.Join(ZWaveMessage.SendMessageTimeoutMs))
                queueManager.Abort();
            queueManager = null;
            // Disconnect the serial port
            Disconnect();
            // Update the nodes configuration file
            SaveNodesConfig();
        }

        #endregion

        #region Public members

        /// <summary>
        /// Connect this instance.
        /// </summary>
        public void Connect()
        {
            LoadNodesConfig();
            new Thread(() => { serialPort.Connect(); }).Start();
        }

        /// <summary>
        /// Disconnect this instance.
        /// </summary>
        public void Disconnect()
        {
            new Thread(() => { serialPort.Disconnect(); }).Start();
        }

        /// <summary>
        /// Gets or sets the name of the serial port.
        /// </summary>
        /// <value>The name of the port.</value>
        public string PortName
        {
            get { return portName; }
            set
            {
                portName = value;
                serialPort.SetPort(value);
            }
        }

        /// <summary>
        /// Gets the status.
        /// </summary>
        /// <value>The status.</value>
        public ControllerStatus Status
        {
            get { return controllerStatus; }
        }

        #region Controller commands

        /// <summary>
        /// Queues the message.
        /// </summary>
        /// <returns>The ZWaveMessage object itself.</returns>
        /// <param name="message">Message.</param>
        public ZWaveMessage QueueMessage(ZWaveMessage message)
        {
            bool addToQueue = true;
            var node = GetNode(message.NodeId);
            if (node != null && node.SupportCommandClass(CommandClass.WakeUp))
            {
                var wakeUpStatus = node.GetData("WakeUpStatus");
                if (wakeUpStatus != null && wakeUpStatus.Value != null && ((WakeUpStatus)wakeUpStatus.Value).IsSleeping)
                {
                    Utility.logger.Warn("Node is flagged as sleeping, message will be re-sent on Wake Up (Node={0}, CallbackId={0}, Function={1}, CommandClass={2})", message.NodeId, message.CallbackId.ToString("X2"), message.Function, message.CommandClass);
                    WakeUp.ResendOnWakeUp(node, message.RawData);
                    addToQueue = false;
                }
            }
            if (addToQueue)
            {
                queuedMessages.Add(message);
                message.sentAck.Reset();
            }
            else
            {
                message.sentAck.Set();
            }
            return message;
        }

        /// <summary>
        /// Sends the message without waiting other pending requests to complete.
        /// </summary>
        /// <returns>True if sending succesfull, False otherwise.</returns>
        /// <param name="message">Message.</param>
        public bool SendMessage(ZWaveMessage message)
        {
            #region Debug 
            Utility.logger.Trace("[[[ BEGIN REQUEST ]]]");
            var stopWatch = new Stopwatch();
            stopWatch.Start();
            #endregion
            SetQueryStage(QueryStage.WaitAck);
            pendingRequest = message;
            sendMessageAck.Reset();
            Utility.logger.Trace("Sending Message (Node={0}, CallbackId={1}, Function={2}, CommandClass={3})", pendingRequest.NodeId, pendingRequest.CallbackId.ToString("X2"), pendingRequest.Function, pendingRequest.CommandClass);
            if (serialPort.SendMessage(message.RawData))
            {
                if (!sendMessageAck.WaitOne(ZWaveMessage.SendMessageTimeoutMs))
                {
                    SetQueryStage(QueryStage.Error);
                    // TODO: Dump Diagnostic Statistics
                    Utility.logger.Warn("Message timeout (Node={0}, CallbackId={0}, Function={1}, CommandClass={2})", pendingRequest.NodeId, pendingRequest.CallbackId.ToString("X2"), pendingRequest.Function, pendingRequest.CommandClass);
                    if (message.NodeId > 1)
                        UpdateOperationProgress(message.NodeId, NodeQueryStatus.Timeout);
                    //System.Diagnostics.Debugger.Break();
                }
            }
            else
            {
                SetQueryStage(QueryStage.Error);
                Utility.logger.Warn("Controller status error (Node={0}, CallbackId={0}, Function={1}, CommandClass={2})", pendingRequest.NodeId, pendingRequest.CallbackId.ToString("X2"), pendingRequest.Function, pendingRequest.CommandClass);
            }
            pendingRequest = null;
            #region Debug 
            stopWatch.Stop();
            Utility.logger.Trace("[[[ END REQUEST ]]] took {0} ms", stopWatch.ElapsedMilliseconds);
            #endregion
            return (currentStage != QueryStage.Error);
        }

        /// <summary>
        /// SoftReset the controller.
        /// </summary>
        public void SoftReset()
        {
            Utility.logger.Trace("BEGIN");
            byte[] message = new byte[] {
                (byte)FrameHeader.SOF,
                0x04,
                (byte)MessageType.Request,
                (byte)ZWaveFunction.ControllerSoftReset,
                0xff,
                0x00
            };
            SendMessage(new ZWaveMessage(message, MessageDirection.Outbound, true));
            Utility.logger.Trace("END");
        }

        /// <summary>
        /// HardReset the controller.
        /// </summary>
        public void HardReset()
        {
            Utility.logger.Trace("BEGIN");
            byte[] message = new byte[] {
                (byte)FrameHeader.SOF,
                0x04,
                (byte)MessageType.Request,
                (byte)ZWaveFunction.ControllerSetDefault,
                0xff,
                0x00
            };
            SendMessage(new ZWaveMessage(message, MessageDirection.Outbound, false));
            nodeList.Clear();
            Utility.logger.Trace("END");
        }

        #endregion

        #region ZWave Discovery / Node Querying

        /// <summary>
        /// Initialize the controller (get the node list).
        /// </summary>
        public void Initialize()
        {
            Utility.logger.Trace("BEGIN");
            Thread.Sleep(1000);
            OnControllerStatusChanged(new ControllerStatusEventArgs(ControllerStatus.Initializing));
            var initialized = SendMessage(new ZWaveMessage(new byte[] { 0x01, 0x03, 0x00, (byte)ZWaveFunction.GetInitData, 0xFE }, MessageDirection.Outbound, false));
            if (initialized)
                OnControllerStatusChanged(new ControllerStatusEventArgs(ControllerStatus.Ready));
            else
                OnControllerStatusChanged(new ControllerStatusEventArgs(ControllerStatus.Error));
            Utility.logger.Trace("END");
        }

        /// <summary>
        /// Query capabilities, Supported Classes and Manufacturer Specific of each node.
        /// </summary>
        public void Discovery()
        {
            // Discovery can be a long time consuming operation, so we ensure
            // that only one instance of this is running
            if (!discoveryRunning)
            {
                bool discoveryError = false;
                OnDiscoveryProgress(new DiscoveryProgressEventArgs(DiscoveryStatus.DiscoveryStart));
                discoveryRunning = true;
                foreach (ZWaveNode zn in nodeList)
                {
                    if (controllerStatus != ControllerStatus.Ready || !serialPort.IsConnected)
                    {
                        discoveryError = true;
                        break;
                    }
                    Utility.logger.Trace("Querying/Updating node {0}", zn.Id);
                    // Get Generic/Basic/Specific Class if not already cached
                    if (zn.ProtocolInfo.BasicType == 0 && zn.ProtocolInfo.GenericType == 0 && zn.ProtocolInfo.SpecificType == 0)
                        GetNodeProtocolInfo(zn.Id);
                    
                    // TODO: should check for SecureNodeInformationFrame as well??

                    // NIF, if cached just return the cached value
                    if (zn.NodeInformationFrame.Length == 0)
                        GetNodeInformationFrame(zn.Id).Wait();
                    else
                        OnNodeUpdated(new NodeUpdatedEventArgs(zn.Id, new NodeEvent(zn, EventParameter.NodeInfo, BitConverter.ToString(zn.NodeInformationFrame).Replace("-", " "), 0)));
                    
                    // For nodes that support version command class, query each one for its version.
                    GetNodeCcsVersion(zn);

                    // Manufacturer Specific, if cached just return the cached value
                    if (String.IsNullOrWhiteSpace(zn.ManufacturerSpecific.ManufacturerId))
                        ManufacturerSpecific.Get(zn);
                    else
                        zn.OnNodeUpdated(new NodeEvent(zn, EventParameter.ManufacturerSpecific, zn.ManufacturerSpecific, 0));
                    // Raise the node updated event
                    UpdateOperationProgress(zn.Id, NodeQueryStatus.NodeUpdated);
                }
                discoveryRunning = false;
                if (!discoveryError)
                {
                    SaveNodesConfig();
                }
                else
                {
                    OnDiscoveryProgress(new DiscoveryProgressEventArgs(DiscoveryStatus.DiscoveryError));<commit_msg>Add CommandDelay and LastCommand variables<commit_after>﻿/*
    This file is part of ZWaveLib Project source code.

    ZWaveLib is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    ZWaveLib is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with ZWaveLib.  If not, see <http://www.gnu.org/licenses/>.  
*/

/*
 *     Author: <NAME> <<EMAIL>>
 *     Project Homepage: https://github.com/genielabs/zwave-lib-dotnet
 */

using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Serialization;

using SerialPortLib;

using ZWaveLib.Values;
using ZWaveLib.CommandClasses;

namespace ZWaveLib
{
    /// <summary>
    /// Z-Wave Controller.
    /// </summary>
    public class ZWaveController : IDisposable
    {
        
        #region Private fields

        private SerialPortInput serialPort;
        private string portName = "";
        private int commandDelay = 0;
        private DateTime lastCommand = DateTime.Now;

        private ManualResetEvent sendMessageAck = new ManualResetEvent(false);
        private bool busyReceiving = false;
        private bool discoveryRunning = false;

        private ZWaveMessage pendingRequest;
        private QueryStage currentStage;

        private bool disposing = false;
        private Thread queueManager;
        private List<ZWaveMessage> queuedMessages;

        private ControllerStatus controllerStatus = ControllerStatus.Disconnected;

        private List<ZWaveNode> nodeList = new List<ZWaveNode>();

        private byte[] lastMessage = null;
        private DateTime lastMessageTimestamp = DateTime.UtcNow;

        private string configFolder;

        #endregion

        #region Public events

        /// <summary>
        /// Controller status changed event handler.
        /// </summary>
        public delegate void ControllerStatusChangedEventHandler(object sender, ControllerStatusEventArgs args);

        /// <summary>
        /// Occurs when controller status changed.
        /// </summary>
        public event ControllerStatusChangedEventHandler ControllerStatusChanged;

        /// <summary>
        /// Discovery progress event handler.
        /// </summary>
        public delegate void DiscoveryProgressEventHandler(object sender, DiscoveryProgressEventArgs args);

        /// <summary>
        /// Occurs during discovery process.
        /// </summary>
        public event DiscoveryProgressEventHandler DiscoveryProgress;

        /// <summary>
        /// Node operation progress event handler.
        /// </summary>
        public delegate void NodeOperationProgressEventHandler(object sender, NodeOperationProgressEventArgs args);

        /// <summary>
        /// Occurs when a node operation is taking place.
        /// </summary>
        public event NodeOperationProgressEventHandler NodeOperationProgress;

        /// <summary>
        /// Node updated event handler.
        /// </summary>
        public delegate void NodeUpdatedEventHandler(object sender, NodeUpdatedEventArgs args);

        /// <summary>
        /// Occurs when node data is updated.
        /// </summary>
        public event NodeUpdatedEventHandler NodeUpdated;

        #endregion

        #region Lifecycle

        /// <summary>
        /// Initializes a new instance of the <see cref="ZWaveLib.ZWaveController"/> class.
        /// </summary>
        public ZWaveController()
        {
            string codeBase = GetType().Assembly.CodeBase;
            UriBuilder uri = new UriBuilder(codeBase);
            string path = Uri.UnescapeDataString(uri.Path);
            configFolder = Path.GetDirectoryName(path);
            // Setup Serial Port
            serialPort = new SerialPortInput();
            serialPort.MessageReceived += SerialPort_MessageReceived;
            serialPort.ConnectionStatusChanged += SerialPort_ConnectionStatusChanged;
            // Setup Queue Manager Task
            queuedMessages = new List<ZWaveMessage>();
            queueManager = new Thread(QueueManagerTask);
            queueManager.Start();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ZWaveLib.ZWaveController"/> class.
        /// </summary>
        /// <param name="portName">The serial port name.</param>
        public ZWaveController(string portName) : this()
        {
            PortName = portName;
        }

        public void Dispose()
        {
            // Dispose the message Queue Manager
            disposing = true;
            if (!queueManager.Join(ZWaveMessage.SendMessageTimeoutMs))
                queueManager.Abort();
            queueManager = null;
            // Disconnect the serial port
            Disconnect();
            // Update the nodes configuration file
            SaveNodesConfig();
        }

        #endregion

        #region Public members

        /// <summary>
        /// Connect this instance.
        /// </summary>
        public void Connect()
        {
            LoadNodesConfig();
            new Thread(() => { serialPort.Connect(); }).Start();
        }

        /// <summary>
        /// Disconnect this instance.
        /// </summary>
        public void Disconnect()
        {
            new Thread(() => { serialPort.Disconnect(); }).Start();
        }

        /// <summary>
        /// Gets or sets the name of the serial port.
        /// </summary>
        /// <value>The name of the port.</value>
        public string PortName
        {
            get { return portName; }
            set
            {
                portName = value;
                serialPort.SetPort(value);
            }
        }

        /// <summary>
        /// Gets or sets the amount of command delay.
        /// </summary>
        /// <value>The length of the delay in ms.</value>
        public int CommandDelay
        {
            get { return commandDelay; }
            set
            {
                commandDelay = value;
            }
        }

        /// <summary>
        /// Gets or sets the wait boolean.
        /// </summary>
        /// <value>The current wait state.</value>
        public DateTime LastCommand
        {
            get { return lastCommand; }
            set
            {
                lastCommand = value;
            }
        }

        /// <summary>
        /// Gets the status.
        /// </summary>
        /// <value>The status.</value>
        public ControllerStatus Status
        {
            get { return controllerStatus; }
        }

        #region Controller commands

        /// <summary>
        /// Queues the message.
        /// </summary>
        /// <returns>The ZWaveMessage object itself.</returns>
        /// <param name="message">Message.</param>
        public ZWaveMessage QueueMessage(ZWaveMessage message)
        {
            bool addToQueue = true;
            var node = GetNode(message.NodeId);
            if (node != null && node.SupportCommandClass(CommandClass.WakeUp))
            {
                var wakeUpStatus = node.GetData("WakeUpStatus");
                if (wakeUpStatus != null && wakeUpStatus.Value != null && ((WakeUpStatus)wakeUpStatus.Value).IsSleeping)
                {
                    Utility.logger.Warn("Node is flagged as sleeping, message will be re-sent on Wake Up (Node={0}, CallbackId={0}, Function={1}, CommandClass={2})", message.NodeId, message.CallbackId.ToString("X2"), message.Function, message.CommandClass);
                    WakeUp.ResendOnWakeUp(node, message.RawData);
                    addToQueue = false;
                }
            }
            if (addToQueue)
            {
                queuedMessages.Add(message);
                message.sentAck.Reset();
            }
            else
            {
                message.sentAck.Set();
            }
            return message;
        }

        /// <summary>
        /// Sends the message without waiting other pending requests to complete.
        /// </summary>
        /// <returns>True if sending succesfull, False otherwise.</returns>
        /// <param name="message">Message.</param>
        public bool SendMessage(ZWaveMessage message)
        {
            #region Debug 
            Utility.logger.Trace("[[[ BEGIN REQUEST ]]]");
            var stopWatch = new Stopwatch();
            stopWatch.Start();
            #endregion
            SetQueryStage(QueryStage.WaitAck);
            pendingRequest = message;
            sendMessageAck.Reset();
            Utility.logger.Trace("Sending Message (Node={0}, CallbackId={1}, Function={2}, CommandClass={3})", pendingRequest.NodeId, pendingRequest.CallbackId.ToString("X2"), pendingRequest.Function, pendingRequest.CommandClass);
            if (serialPort.SendMessage(message.RawData))
            {
                if (!sendMessageAck.WaitOne(ZWaveMessage.SendMessageTimeoutMs))
                {
                    SetQueryStage(QueryStage.Error);
                    // TODO: Dump Diagnostic Statistics
                    Utility.logger.Warn("Message timeout (Node={0}, CallbackId={0}, Function={1}, CommandClass={2})", pendingRequest.NodeId, pendingRequest.CallbackId.ToString("X2"), pendingRequest.Function, pendingRequest.CommandClass);
                    if (message.NodeId > 1)
                        UpdateOperationProgress(message.NodeId, NodeQueryStatus.Timeout);
                    //System.Diagnostics.Debugger.Break();
                }
            }
            else
            {
                SetQueryStage(QueryStage.Error);
                Utility.logger.Warn("Controller status error (Node={0}, CallbackId={0}, Function={1}, CommandClass={2})", pendingRequest.NodeId, pendingRequest.CallbackId.ToString("X2"), pendingRequest.Function, pendingRequest.CommandClass);
            }
            pendingRequest = null;
            #region Debug 
            stopWatch.Stop();
            Utility.logger.Trace("[[[ END REQUEST ]]] took {0} ms", stopWatch.ElapsedMilliseconds);
            #endregion
            return (currentStage != QueryStage.Error);
        }

        /// <summary>
        /// SoftReset the controller.
        /// </summary>
        public void SoftReset()
        {
            Utility.logger.Trace("BEGIN");
            byte[] message = new byte[] {
                (byte)FrameHeader.SOF,
                0x04,
                (byte)MessageType.Request,
                (byte)ZWaveFunction.ControllerSoftReset,
                0xff,
                0x00
            };
            SendMessage(new ZWaveMessage(message, MessageDirection.Outbound, true));
            Utility.logger.Trace("END");
        }

        /// <summary>
        /// HardReset the controller.
        /// </summary>
        public void HardReset()
        {
            Utility.logger.Trace("BEGIN");
            byte[] message = new byte[] {
                (byte)FrameHeader.SOF,
                0x04,
                (byte)MessageType.Request,
                (byte)ZWaveFunction.ControllerSetDefault,
                0xff,
                0x00
            };
            SendMessage(new ZWaveMessage(message, MessageDirection.Outbound, false));
            nodeList.Clear();
            Utility.logger.Trace("END");
        }

        #endregion

        #region ZWave Discovery / Node Querying

        /// <summary>
        /// Initialize the controller (get the node list).
        /// </summary>
        public void Initialize()
        {
            Utility.logger.Trace("BEGIN");
            Thread.Sleep(1000);
            OnControllerStatusChanged(new ControllerStatusEventArgs(ControllerStatus.Initializing));
            var initialized = SendMessage(new ZWaveMessage(new byte[] { 0x01, 0x03, 0x00, (byte)ZWaveFunction.GetInitData, 0xFE }, MessageDirection.Outbound, false));
            if (initialized)
                OnControllerStatusChanged(new ControllerStatusEventArgs(ControllerStatus.Ready));
            else
                OnControllerStatusChanged(new ControllerStatusEventArgs(ControllerStatus.Error));
            Utility.logger.Trace("END");
        }

        /// <summary>
        /// Query capabilities, Supported Classes and Manufacturer Specific of each node.
        /// </summary>
        public void Discovery()
        {
            // Discovery can be a long time consuming operation, so we ensure
            // that only one instance of this is running
            if (!discoveryRunning)
            {
                bool discoveryError = false;
                OnDiscoveryProgress(new DiscoveryProgressEventArgs(DiscoveryStatus.DiscoveryStart));
                discoveryRunning = true;
                foreach (ZWaveNode zn in nodeList)
                {
                    if (controllerStatus != ControllerStatus.Ready || !serialPort.IsConnected)
                    {
                        discoveryError = true;
                        break;
                    }
                    Utility.logger.Trace("Querying/Updating node {0}", zn.Id);
                    // Get Generic/Basic/Specific Class if not already cached
                    if (zn.ProtocolInfo.BasicType == 0 && zn.ProtocolInfo.GenericType == 0 && zn.ProtocolInfo.SpecificType == 0)
                        GetNodeProtocolInfo(zn.Id);
                    
                    // TODO: should check for SecureNodeInformationFrame as well??

                    // NIF, if cached just return the cached value
                    if (zn.NodeInformationFrame.Length == 0)
                        GetNodeInformationFrame(zn.Id).Wait();
                    else
                        OnNodeUpdated(new NodeUpdatedEventArgs(zn.Id, new NodeEvent(zn, EventParameter.NodeInfo, BitConverter.ToString(zn.NodeInformationFrame).Replace("-", " "), 0)));
                    
                    // For nodes that support version command class, query each one for its version.
                    GetNodeCcsVersion(zn);

                    // Manufacturer Specific, if cached just return the cached value
                    if (String.IsNullOrWhiteSpace(zn.ManufacturerSpecific.ManufacturerId))
                        ManufacturerSpecific.Get(zn);
                    else
                        zn.OnNodeUpdated(new NodeEvent(zn, EventParameter.ManufacturerSpecific, zn.ManufacturerSpecific, 0));
                    // Raise the node updated event
                    UpdateOperationProgress(zn.Id, NodeQueryStatus.NodeUpdated);
                    System.Threading.Thread.Sleep(commandDelay);
                }
                discoveryRunning = false;
                if (!discoveryError)
                {
                    SaveNodesConfig();
                }
                else
                {
                    OnDiscoveryProgress(new DiscoveryProgressEventArgs(DiscoveryStatus.DiscoveryError));
{'fraction_non_alphanumeric': 0.09628936909254868, 'fraction_numerical': 0.003907685114057646, 'mean_word_length': 1.7401848631829413, 'pattern_counts': {'":': 0, '<': 143, '<?xml version=': 0, '>': 149, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5256838', 'n_tokens_mistral': 7236, 'n_tokens_neox': 6879, 'n_words': 1829}