public BasemeterComThread(string ipAddress, string tcpPort, CommunicationPort port)
        {
            this.endpoint = new IPEndPoint(IPAddress.Parse(ipAddress), int.Parse(tcpPort));
            this.port = port;
            this.logger = LogManager.GetLogger($"{port.ComPortName}");

            // initialize bus communication
            this.communication = new Communication(new NLogLogger());
            this.communication.DeviceDiscovered += OnDeviceDiscovered;
            this.communication.DeviceDisappeared += OnDeviceDisappeared;
        }
        public BasemeterComThread(string portName, CommunicationPort port, int baudRate = 921600)
        {
            this.portName = portName;
            this.baudRate = baudRate;
            this.port = port;
            this.logger = LogManager.GetLogger(this.portName);

            // initialize bus communication
            this.communication = new Communication(new NLogLogger());
            this.communication.DeviceDiscovered += OnDeviceDiscovered;
            this.communication.DeviceDisappeared += OnDeviceDisappeared;
        }
        /// <summary>
        /// Reads the device identification.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="communicationPort">The communication port.</param>
        /// <returns>The meter's device identification data.</returns>
        public async Task<EdlDeviceIdentification> ReadDeviceIdentification(Guid clientId, CommunicationPort communicationPort)
        {
            CheckConfiguration(communicationPort, clientId);

            var edlComThread = ClientComListDict[communicationPort.ComPortName].ComThread as IEdlComThread;
            var di = await edlComThread.ReadDeviceIdentification();
            if (di == null)
            {
                throw new InvalidDataException("No result for device identification available.");
            }

            return di;
        }
        /// <summary>
        /// Reads the number of manipulation events.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="communicationPort">The communication port.</param>
        /// <returns>Total number of manipulation events.</returns>
        public async Task<UnsignedParam> ReadNumberOfManipulationEvents(Guid clientId, CommunicationPort communicationPort)
        {
            CheckConfiguration(communicationPort, clientId);

            var numParam = new UnsignedParam() {ObisCode = (long) ObisId.EdlManipulationEvent, Data = null};

            numParam = await ClientComListDict[communicationPort.ComPortName].ComThread.ReadParameter(numParam);
            return numParam;
        }
        /// <summary>
        /// Reads the ownership number.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="communicationPort">The communication port.</param>
        /// <returns></returns>
        public async Task<BinaryParam> ReadClientId(Guid clientId, CommunicationPort communicationPort)
        {
            CheckConfiguration(communicationPort, clientId);

            var binaryParam = new BinaryParam()
            {
                ObisCode = (long) ObisId.EdlDzgClientId, Data = null
            };

            binaryParam = await ClientComListDict[communicationPort.ComPortName].ComThread.ReadParameter(binaryParam);
            return binaryParam;
        }
        /// <summary>
        /// Reads the tariff switching program number.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="communicationPort">The communication port.</param>
        /// <returns>The current tariff switching program number</returns>
        public async Task<TextParam> ReadTariffSwitchingProgramNumber(Guid clientId, CommunicationPort communicationPort)
        {
            CheckConfiguration(communicationPort, clientId);

            var textParam = new TextParam()
            {
                ObisCode = (long)ObisId.EdlTariffProgramNumber,
                Data = string.Empty
            };

            textParam = await ClientComListDict[communicationPort.ComPortName].ComThread.ReadParameter(textParam);
            return textParam;
        }
        /// <summary>
        /// Reads whether the manipulation detection is active.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="communicationPort">The communication port.</param>
        /// <returns>True if the manipulation detection is active.</returns>
        public async Task<BooleanParam> ReadIsManipulationDetectionActive(Guid clientId, CommunicationPort communicationPort)
        {
            CheckConfiguration(communicationPort, clientId);

            var boolParam = new BooleanParam()
            {
                ObisCode = (long) ObisId.EdlIsManipulationDectectionActive, Data = null
            };

            boolParam = await ClientComListDict[communicationPort.ComPortName].ComThread.ReadParameter(boolParam);
            return boolParam;
        }
        /// <summary>
        /// Reads whether the INFO interface is activated.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="communicationPort">The communication port.</param>
        /// <returns>True if INFO interface is activated.</returns>
        public async Task<BooleanParam> ReadIsInfoInterfaceActivated(Guid clientId, CommunicationPort communicationPort)
        {
            var port = CheckConfiguration(communicationPort, clientId);

            var boolParam = new BooleanParam { ObisCode = (long)ObisId.EdlInfoInterfaceActive };
            boolParam = await port.ComThread.ReadParameter(boolParam);
            return boolParam;
        }
 public async Task<BooleanParam> ReadCustomerPush(Guid clientId, CommunicationPort communicationPort)
 {
     var port = CheckConfiguration(communicationPort, clientId);
     
     var boolParam = new BooleanParam { ObisCode = (long)ObisId.EdlManufacturerPushData };
     boolParam = await port.ComThread.ReadParameter(boolParam);
     return boolParam;
 }
        /// <summary>
        /// Reads the event log.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="communicationPort">The communication port.</param>
        /// <returns></returns>
        public async Task<List<EventLogItem>> ReadEventLog(Guid clientId, CommunicationPort communicationPort)
        {
            CheckConfiguration(communicationPort, clientId);

            var port = ClientComListDict[communicationPort.ComPortName];
            var comThread = port.ComThread as IEdlComThread;
            var evl = await comThread.ReadEventLog(port.EdlMeterDevice.PublicKey);
            if (evl == null)
            {
                throw new InvalidDataException("No result for event log available.");
            }

            return evl;
        }
        /// <summary>
        /// Reads the status word.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="communicationPort">The communication port.</param>
        /// <returns>The EDL statusword.</returns>
        public async Task<EdlStatusWord> ReadEdlStatusWord(Guid clientId, CommunicationPort communicationPort)
        {
            var port = CheckConfiguration(communicationPort, clientId);

            if (port.MeterType == MeterType.Edl)
            {
                var edlComThread = port.ComThread as IEdlComThread;
                var sw = await edlComThread?.ReadStatusWord();
                if (sw == null)
                {
                    throw new DataException("No result for statusword available.");
                }

                return sw.Value;
            }
            else
            {
                throw new InvalidOperationException("Meter type must be Edl");
            }
        }
        /// <summary>
        /// Writes the date and time.
        /// <remarks>An offset to the host time base can be specified in order to minimize time deviations due to client/server communication.
        /// </remarks>
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="communicationPort">The communication port.</param>
        /// <param name="hostOffsetSeconds">The offset in seconds to the host date/time.
        /// <remarks> If the intention is to set the current host time to the meter, then the hostOffsetSeconds should be '0'.</remarks></param>
        public async Task WriteDateAndTimeWithHostOffset(Guid clientId, CommunicationPort communicationPort, int hostOffsetSeconds)
        {
            CheckConfiguration(communicationPort, clientId);

            // wait until a new second has begun
            var sw = Stopwatch.StartNew();
            while (DateTime.UtcNow.Millisecond > 100 && sw.ElapsedMilliseconds < 2000)
            {
                Thread.Sleep(10);
            }
            sw.Reset();

            var dt = new DateTimeParam() { ObisCode = (long)ObisId.CurrentMeterTime, Data = DateTime.UtcNow.AddSeconds(hostOffsetSeconds) };
            await ClientComListDict[communicationPort.ComPortName].ComThread.WriteParameter(dt);
        }
        /// <summary>
        /// Writes the date and time.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="communicationPort">The communication port.</param>
        /// <param name="newTime">The new time.</param>
        public async Task WriteDateAndTime(Guid clientId, CommunicationPort communicationPort, DateTime newTime)
        {
            CheckConfiguration(communicationPort, clientId);

            var dt = new DateTimeParam() { ObisCode = (long)ObisId.CurrentMeterTime, Data = newTime };
            await ClientComListDict[communicationPort.ComPortName].ComThread.WriteParameter(dt);
        }
        public async Task<DateTimeParam> ReadDateAndTimeParam(Guid clientId, CommunicationPort communicationPort)
        {
            CheckConfiguration(communicationPort, clientId);
            
            var dt = new DateTimeParam() { ObisCode = (long)ObisId.CurrentMeterTime, Data = null };
            dt = await ClientComListDict[communicationPort.ComPortName].ComThread.ReadParameter(dt);
            if (dt.Data.HasValue == false)
            {
                throw new InvalidDataException("No result for date/time available.");
            }

            return dt;
        }
        /// <summary>
        /// Reads the date and time.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="communicationPort">The communication port.</param>
        /// <returns>The current meter date time.</returns>
        public async Task<DateTime> ReadDateAndTime(Guid clientId, CommunicationPort communicationPort)
        {
            var param = await this.ReadDateAndTimeParam(clientId, communicationPort);
            if (param.Data == null)
            {
                throw new InvalidDataException("No Data");
            }

            return param.Data.Value;
        }
        /// <summary>
        /// Reads the display special text.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="communicationPort">The communication port.</param>
        /// <returns>The special text shown on the LCD's 2nd line.</returns>
        public async Task<TextParam> ReadDisplaySpecialText(Guid clientId, CommunicationPort communicationPort)
        {
            CheckConfiguration(communicationPort, clientId);

            var textParam = new TextParam()
            {
                ObisCode = (long)ObisId.EdlInfoText,
                Data = string.Empty
            };

            textParam = await ClientComListDict[communicationPort.ComPortName].ComThread.ReadParameter(textParam);
            return textParam;
        }
        /// <summary>
        /// Writes the display special text.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="communicationPort">The communication port.</param>
        /// <param name="displaySpecialText">The display special text.</param>
        public async Task WriteDisplaySpecialText(Guid clientId, CommunicationPort communicationPort, string displaySpecialText)
        {
            CheckConfiguration(communicationPort, clientId);

            var textParam = new TextParam()
            {
                ObisCode = (long)ObisId.EdlInfoText,
                Data = displaySpecialText
            };

            await ClientComListDict[communicationPort.ComPortName].ComThread.WriteParameter(textParam);
        }
        public async Task WriteCustomerPush(Guid clientId, CommunicationPort communicationPort, bool isOn)
        {
            var port = CheckConfiguration(communicationPort, clientId);

            var boolParam = new BooleanParam { ObisCode = (long)ObisId.EdlManufacturerPushData, Data = isOn };
            await port.ComThread.WriteParameter(boolParam);
        }
        /// <summary>
        /// Writes the is info interface activated.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="communicationPort">The communication port.</param>
        /// <param name="isActive">if set to <c>true</c> [is active].</param>
        public async Task WriteIsInfoInterfaceActivated(Guid clientId, CommunicationPort communicationPort, bool isActive)
        {
            var port = CheckConfiguration(communicationPort, clientId);

            var boolParam = new BooleanParam { ObisCode = (long)ObisId.EdlInfoInterfaceActive, Data = isActive };
            await port.ComThread.WriteParameter(boolParam);
        }
        /// <summary>
        /// Reads whether the operation mode is equal to EDL40.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="communicationPort">The communication port.</param>
        /// <returns>True if operation mode equals to EDL40.</returns>
        public async Task<BooleanParam> ReadIsOperationModeEdl40(Guid clientId, CommunicationPort communicationPort)
        {
            var port = CheckConfiguration(communicationPort, clientId);

            var boolParam = new BooleanParam()
            {
                ObisCode = (long)ObisId.EdlOperationModeEdl40,
                Data = null
            };

            boolParam = await port.ComThread.ReadParameter(boolParam);
            return boolParam;
        }
        /// <summary>
        /// Writes the is manipulation detection active.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="communicationPort">The communication port.</param>
        /// <param name="isActive">if set to <c>true</c> [is active].</param>
        public async Task WriteIsManipulationDetectionActive(Guid clientId, CommunicationPort communicationPort, bool isActive)
        {
            CheckConfiguration(communicationPort, clientId);

            var boolParam = new BooleanParam {ObisCode = (long) ObisId.EdlIsManipulationDectectionActive, Data = isActive};
            await ClientComListDict[communicationPort.ComPortName].ComThread.WriteParameter(boolParam);
        }
        /// <summary>
        /// Writes the is operation mode edl40.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="communicationPort">The communication port.</param>
        /// <param name="isActive">if set to <c>true</c> [is active].</param>
        public async Task WriteIsOperationModeEdl40(Guid clientId, CommunicationPort communicationPort, bool isActive)
        {
            var port = CheckConfiguration(communicationPort, clientId);

            var boolParam = new BooleanParam() { ObisCode = (long)ObisId.EdlOperationModeEdl40, Data = isActive };

            if (port.EdlMeterDevice != null)
            {
                // Set activation state, if set to true, the com thread will update the meter periodically
                port.EdlMeterDevice.IsOperationModeEdl40 = boolParam;
            }

            await port.ComThread.WriteParameter(boolParam);
        }
        /// <summary>
        /// Writes the tariff switching program number.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="communicationPort">The communication port.</param>
        /// <param name="switchingProgramNo">The switching program no.</param>
        public async Task WriteTariffSwitchingProgramNumber(Guid clientId, CommunicationPort communicationPort, string switchingProgramNo)
        {
            CheckConfiguration(communicationPort, clientId);

            var textParam = new TextParam()
            {
                ObisCode = (long)ObisId.EdlTariffProgramNumber,
                Data = switchingProgramNo
            };

            await ClientComListDict[communicationPort.ComPortName].ComThread.WriteParameter(textParam);
        }
 /// <summary>
 /// Enables the operation mode edl40 by setting operation mode to EDL40.
 /// After setting the operation mode, the meter time is adjusted to newMeterTime.
 /// </summary>
 /// <param name="clientId">The client id.</param>
 /// <param name="communicationPort">The communication port.</param>
 /// <param name="newMeterTime">The new meter time.</param>
 public async Task EnableOperationModeEdl40(Guid clientId, CommunicationPort communicationPort, DateTime newMeterTime)
 {
     await this.WriteIsOperationModeEdl40(clientId, communicationPort, true);
     await this.WriteDateAndTime(clientId, communicationPort, newMeterTime);
 }
        /// <summary>
        /// Writes the ownership number.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="communicationPort">The communication port.</param>
        /// <param name="clientIdNumber">The ownership number.</param>
        public async Task WriteClientId(Guid clientId, CommunicationPort communicationPort, string clientIdNumber)
        {
            CheckConfiguration(communicationPort, clientId);

            // fill up with '0' if too short
            while (clientIdNumber.Length < 10)
            {
                clientIdNumber = string.Format("{0}{1}", "0", clientIdNumber);
            }

            // shorten from left side if too long
            while (clientIdNumber.Length > 10)
            {
                clientIdNumber = clientIdNumber.Remove(0, 1);
            }

            var binaryParam = new BinaryParam()
            {
                ObisCode = (long) ObisId.EdlDzgClientId, Data = Encoding.UTF8.GetBytes(clientIdNumber)
            };

            await ClientComListDict[communicationPort.ComPortName].ComThread.WriteParameter(binaryParam);
        }
 /// <summary>
 /// Enables the operation mode edl40.
 /// </summary>
 /// <param name="clientId">The client id.</param>
 /// <param name="communicationPort">The communication port.</param>
 /// <param name="hostOffsetSeconds">The host offset seconds.</param>
 public async Task EnableOperationModeEdl40WithHostOffset(Guid clientId, CommunicationPort communicationPort, int hostOffsetSeconds)
 {
     await this.WriteIsOperationModeEdl40(clientId, communicationPort, true);
     await this.WriteDateAndTimeWithHostOffset(clientId, communicationPort, hostOffsetSeconds);
 }
        /// <summary>
        /// Reads the number of tariffs.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="communicationPort">The communication port.</param>
        /// <returns>Total number of configured tariffs.</returns>
        public async Task<UnsignedParam> ReadNumberOfTariffs(Guid clientId, CommunicationPort communicationPort)
        {
            var port = CheckConfiguration(communicationPort, clientId);
            
            if (port.MeterType == MeterType.Basemeter)
            {
                var boolParam = new BooleanParam()
                {
                    ObisCode = RegisterIds.TariffOptionEnabled.AsLong()
                };

                boolParam = await port.ComThread.ReadParameter(boolParam);
                if (boolParam.Data.Value)
                {
                    return new UnsignedParam()
                    {
                        ObisCode = (long) ObisId.EdlDzgNumberOfTariffs,
                        Data = 2
                    };
                }
                else
                {
                    return new UnsignedParam()
                    {
                        ObisCode = (long)ObisId.EdlDzgNumberOfTariffs,
                        Data = 0
                    };
                }
            }

            var numParam = new UnsignedParam()
            {
                ObisCode = (long)ObisId.EdlDzgNumberOfTariffs,
                Data = null
            };
            numParam = await ClientComListDict[communicationPort.ComPortName].ComThread.ReadParameter(numParam);
            return numParam;
        }
        /// <summary>
        /// Reads whether the manufacturer specific (extended) billing data set is used.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="communicationPort">The communication port.</param>
        /// <returns>True if manufacturer specific billing data set is used.</returns>
        public async Task<BooleanParam> ReadIsManufacturerSpecificBillingDataUsed(Guid clientId, CommunicationPort communicationPort)
        {
            CheckConfiguration(communicationPort, clientId);

            var boolParam = new BooleanParam()
            {
                ObisCode = (long)ObisId.EdlManufacturerPushData,
                Data = null
            };

            boolParam = await ClientComListDict[communicationPort.ComPortName].ComThread.ReadParameter(boolParam);
            return boolParam;
        }
        /// <summary>
        /// Writes the is manufacturer specific billing data used.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="communicationPort">The communication port.</param>
        /// <param name="isUsed">if set to <c>true</c> [is used].</param>
        public async Task WriteIsManufacturerSpecificBillingDataUsed(Guid clientId, CommunicationPort communicationPort, bool isUsed)
        {
            CheckConfiguration(communicationPort, clientId);

            var boolParam = new BooleanParam { ObisCode = (long)ObisId.EdlManufacturerPushData, Data = isUsed };
            await ClientComListDict[communicationPort.ComPortName].ComThread.WriteParameter(boolParam);
        }
        /// <summary>
        /// Reads the last pushed billing data.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="communicationPort">The com port.</param>
        /// <returns>
        /// A billing data from an EDL meter.
        /// </returns>
        public EdlBillingData ReadLastPushedBillingData(Guid clientId, CommunicationPort communicationPort)
        {
            CheckConfiguration(communicationPort, clientId);
            var edlComThread = ClientComListDict[communicationPort.ComPortName].ComThread as IEdlComThread;
            var bd = edlComThread.CollectedBillingData;
            if (bd == null)
            {
                // Wait a while then try again
                Thread.Sleep(5000);
                bd = edlComThread.CollectedBillingData;

                if (bd == null)
                {
                    throw new InvalidDataException("No result for device billing data available.");
                }
            }

            return bd;
        }