/// <summary>
        /// Adds the set unsigned param request.
        /// </summary>
        /// <param name="reqFile">The req file.</param>
        /// <param name="param">The param.</param>
        private void AddSetUnsignedParamRequest(SmlFile reqFile, UnsignedParam param)
        {
            var msg = new Core.Sml.Messages.Message()
            {
                TransactionId = new[] { (byte)(reqFile.Count + 1) },
                GroupNo = 0x00,
                SetProcParameterRequest = new Core.Sml.Messages.SetProcParameterRequest()
                {
                    TreePath = new List<Core.Obis.ObisId>()
                    {
                        new Core.Obis.ObisId((ulong)param.ObisCode)
                    },
                    Tree = new Tree(new Core.Obis.ObisId((ulong)param.ObisCode), param.Data.Value)
                }
            };
            //var obisId = (ObisId)param.ObisCode;
            //var paramTreePath = new SmlTreePath(obisId);
            //var tree = new SmlTree { ParameterName = new SmlOctetstring(ObisUtil.GetBytes(obisId)) };
            //if (param.Data.HasValue == false)
            //{
            //    throw new ArgumentNullException("param");
            //}

            //// TODO: check if casting is obsolete
            //if (obisId == ObisId.EdlActiveTariffAplus || obisId == ObisId.EdlActiveTariffAminus)
            //{
            //    tree.ParameterValue = new SmlProcParValue(new SmlUnsigned8((byte)param.Data.Value));
            //}
            //else
            //{
            //    tree.ParameterValue = new SmlProcParValue(new SmlUnsigned64(param.Data.Value));
            //}

            //var msg = SmlMessageFactory.SetProcParamReq(paramTreePath, tree);

            //var transactionId = new byte[] { (byte)(reqFile.Count + 1) };
            //msg.TransactionId = new SmlOctetstring(transactionId);
            //msg.GroupNo = 0x00;
            reqFile.Add(msg);
        }
        /// <summary>
        /// Creates the set request file for tariff.
        /// </summary>
        /// <param name="portId">The port id.</param>
        /// <param name="bitMask">The bit mask.</param>
        /// <param name="tariffAplus">The tariff aplus.</param>
        /// <param name="tarrifAminus">The tarrif aminus.</param>
        /// <returns>The request file to set the tariffs and bitmask.</returns>
        public static SmlFile CreateSetRequestFileForTariff(
           string portId, UnsignedParam bitMask, UnsignedParam tariffAplus, UnsignedParam tarrifAminus)
        {
            var handler = new SmlHandler(portId);
            var smlFile = new SmlFile();
            handler.AddOpenRequest(smlFile);

            handler.AddSetBooleanParamRequest(
               smlFile, new BooleanParam() { ObisCode = (long)ObisId.EdlOperationModeEdl40, Data = false });
            handler.AddSetUnsignedParamRequest(smlFile, bitMask);

            if (tariffAplus != null)
            {
                handler.AddSetUnsignedParamRequest(smlFile, tariffAplus);
            }

            if (tarrifAminus != null)
            {
                handler.AddSetUnsignedParamRequest(smlFile, tarrifAminus);
            }

            handler.AddCloseRequest(smlFile);

            return smlFile;
        }
        public async Task WriteParameter(UnsignedParam param, bool enableManufacturerMode = false)
        {
            if (param.ObisCode == RegisterIds.SetTariff.AsLong())
            {
                var isTarrifTwo = param.Data.Value == 2;

                TariffConfiguration tariffConfiguration = TariffConfiguration.None;
                
                var mm = await this.productionParameters.GetMeasurementMode().ConfigureAwait(false);
                switch (mm)
                {
                    case MeasurmentMode.MM1_ImportReverseLocking:
                        tariffConfiguration = TariffConfiguration.Import;
                        break;
                    case MeasurmentMode.MM2_ImportExport:
                        tariffConfiguration = TariffConfiguration.Import | TariffConfiguration.Export;
                        break;
                    case MeasurmentMode.MM3_ExportLocking:
                    case MeasurmentMode.MM4_Export:
                        tariffConfiguration = TariffConfiguration.Export;
                        break;
                }

                // read configuration to get status of reverse tariff control
                var tc = await this.baseMeterDevice.TariffControl.GetTariffConfiguration().ConfigureAwait(false);
                if (tc.HasFlag(TariffConfiguration.OnActivationUseT1))
                {
                    tariffConfiguration |= TariffConfiguration.OnActivationUseT1;
                }

                await this.baseMeterDevice.TariffControl.SetTariffConfiguration(tariffConfiguration).ConfigureAwait(false);
                await this.baseMeterDevice.TariffControl.SetLmnTariffSignal(isTarrifTwo).ConfigureAwait(false);
            }
            else if (param.ObisCode == VendorSpecificRegisterIds.MeasurementMode.AsLong())
            {
                MeasurmentMode mmToWrite = (MeasurmentMode) param.Data.Value;
                await this.productionParameters.SetMeasurementMode(mmToWrite);
            }
            else
            {
                var obis = new ObisId((ulong) param.ObisCode);
                await this.baseMeterDevice.WriteRegisterAsync(obis, param.Data.Value).ConfigureAwait(false);
            }
        }
        /// <summary>
        /// Creates the set request file.
        /// </summary>
        /// <param name="portId">The port id.</param>
        /// <param name="param">The param.</param>
        /// <returns></returns>
        public static SmlFile CreateSetRequestFile(string portId, UnsignedParam param)
        {
            var handler = new SmlHandler(portId);
            var smlFile = new SmlFile();
            handler.AddOpenRequest(smlFile);
            handler.AddSetUnsignedParamRequest(smlFile, param);
            handler.AddCloseRequest(smlFile);

            return smlFile;
        }
        /// <summary>
        /// Writes the parameter.
        /// </summary>
        /// <param name="param">The param.</param>
        public async Task WriteParameter(UnsignedParam param, bool enableManufacturerMode = false)
        {
            logger.Info("Writing unsigned parameter {0} with value {1}.", (ObisId)param.ObisCode, BitConverter.ToString(BitConverter.GetBytes(param.Data.Value)));
            await Retry.DoTask(
                async () =>
                {
                    var requestFile = SmlHandler.CreateSetRequestFile(portName, param);
                    await SendRequestFile(requestFile, enableManufacturerMode).ConfigureAwait(false);
                    if (writeOnlyParams.Contains(param.ObisCode))
                    {
                        return;
                    }

                    var result = await ReadParameter(new UnsignedParam { ObisCode = param.ObisCode });

                    if (param.Data != result.Data)
                    {
                        throw new DataException(
                            string.Format(
                                "Failed to write and verify {0:X12}: written: {1}, read: {2}",
                                param.ObisCode,
                                param.Data,
                                result.Data));
                    }
                });
        }
        public async Task<UnsignedParam> ReadParameter(UnsignedParam param)
        {
            if (param.ObisCode == VendorSpecificRegisterIds.MeasurementMode.AsLong())
            {
                var pp = new ProductionParameters(this.baseMeterDevice);
                MeasurmentMode mm = await pp.GetMeasurementMode().ConfigureAwait(false);
                ulong mmNumber = 0;
                switch (mm)
                {
                    case MeasurmentMode.MM1_ImportReverseLocking:
                        mmNumber = 1;
                        break;
                    case MeasurmentMode.MM2_ImportExport:
                        mmNumber = 2;
                        break;
                    case MeasurmentMode.MM3_ExportLocking:
                        mmNumber = 3;
                        break;
                    case MeasurmentMode.MM4_Export:
                        mmNumber = 4;
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }

                return new UnsignedParam()
                {
                    ObisCode = param.ObisCode,
                    Data = mmNumber
                };
            }

            var obis = new ObisId((ulong)param.ObisCode);
            var result = await this.baseMeterDevice.ReadRegisterAsync(obis).ConfigureAwait(false);
            if (result == null)
            {
                throw new NullReferenceException();
            }

            return new UnsignedParam()
            {
                ObisCode = param.ObisCode, Data = result.GetAsULong()
            };
        }
        /// <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 the parameter.
        /// </summary>
        /// <param name="param">The param.</param>
        /// <returns></returns>
        public async Task<UnsignedParam> ReadParameter(UnsignedParam param)
        {
            logger.Info("Reading unsigned parameter.");

            var requestFile = SmlHandler.CreateGetRequestFile(portName, param.ObisCode);

            return await Retry.Do(
                async () =>
                {
                    var resVal = await SendFile(requestFile).ConfigureAwait(false);
                    CheckAttentionResponse(param.ObisCode, resVal);

                    if (resVal != null)
                    {
                        var valB = Convert.ToUInt64(resVal);
                        param.Data = valB;
                        logger.Info("Responding with unsigned result: {0}.", param.Data);
                        return param;
                    }

                    logger.Info("No response for reading unsigned parameter.");
                    throw new NullReferenceException();
                });
        }
        /// <summary>
        /// Writes the current aminus tariff.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="communicationPort">The communication port.</param>
        /// <param name="tariffNumber">The tariff number.</param>
        public async Task WriteCurrentAminusTariff(Guid clientId, CommunicationPort communicationPort, byte tariffNumber)
        {
            var port = CheckConfiguration(communicationPort, clientId);

            if (communicationPort.MeterType == MeterType.Edl)
            {
                var meterDevice = ClientComListDict[communicationPort.ComPortName].EdlMeterDevice;
                if (tariffNumber > meterDevice.TariffCount)
                {
                    throw new ArgumentOutOfRangeException("Selected tariff is not supported by this meter.");
                }

                await Retry.DoTask(async () =>
                {
                    // deactivate EDL40 mode
                    if (meterDevice.IsOperationModeEdl40.Data == true)
                    {
                        await this.WriteIsOperationModeEdl40(clientId, communicationPort, false);
                    }

                    // set tariff mask to this meter's maximum tariff count
                    var rawBytes = BitConverter.GetBytes(meterDevice.TariffMaskForLcd.Data.Value);
                    Array.Resize(ref rawBytes, 2);
                    var tariffCount = meterDevice.TariffCount;

                    rawBytes[1] = 0;
                    while (tariffCount-- > 0)
                    {
                        rawBytes[1] |= (byte)(rawBytes[1] << 1);
                        rawBytes[1] |= 1;
                    }

                    if (meterDevice.MeasurementMode == "MM2")
                    {
                        tariffCount = meterDevice.TariffCount;
                        rawBytes[0] = 0;
                        while (tariffCount-- > 0)
                        {
                            rawBytes[0] |= (byte)(rawBytes[0] << 1);
                            rawBytes[0] |= 1;
                        }
                    }

                    var bitMask = BitConverter.ToUInt16(rawBytes, 0);
                    meterDevice.TariffMaskForLcd.Data = bitMask;

                    // configure the parameter here and the thread intetrnally checks, sets and keeps the active tariff
                    byte tariffByte = 1;
                    tariffByte = (byte)(tariffByte << (tariffNumber - 1));

                    var tariffParam = new UnsignedParam()
                    {
                        ObisCode = (long)ObisId.EdlActiveTariffAminus,
                        Data = tariffByte
                    };

                    var thread = ClientComListDict[communicationPort.ComPortName].ComThread;
                    var device = ClientComListDict[communicationPort.ComPortName].EdlMeterDevice;

                    await thread.WriteParameter(device.TariffMaskForLcd);
                    await thread.WriteParameter(device.ActiveTariffAminus = tariffParam);
                });
            }
            else
            {
                var setTariffParam = new UnsignedParam()
                {
                    ObisCode = RegisterIds.SetTariff.AsLong(),
                    Data = tariffNumber
                };

                await port.ComThread.WriteParameter(setTariffParam);
            }
        }
        /// <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>
        /// Writes the measurement mode.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="communicationPort">The communication port.</param>
        /// <param name="measurementMode">The measurement mode.</param>
        public async Task WriteMeasurementMode(Guid clientId, CommunicationPort communicationPort, string measurementMode)
        {
            CheckConfiguration(communicationPort, clientId);

            if (communicationPort.MeterType == MeterType.Edl)
            {
                byte mode = 0x08;
                switch (measurementMode)
                {
                    case "MM1":
                        mode = 0x08;
                        break;
                    case "MM2":
                        mode = 0x0A;
                        break;
                    case "MM3":
                        mode = 0x02;
                        break;
                    case "MM4":
                        mode = 0x01;
                        break;
                    default:
                        throw new InvalidEnumArgumentException("Invalid measurement mode. Measurement mode must be within MM1 ... MM4.");
                }

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

                await ClientComListDict[communicationPort.ComPortName].ComThread.WriteParameter(numParam);
            }
            else
            {
                // basemeter
                MeasurmentMode mode;
                switch (measurementMode)
                {
                    case "MM1":
                        mode = MeasurmentMode.MM1_ImportReverseLocking;
                        break;
                    case "MM2":
                        mode = MeasurmentMode.MM2_ImportExport;
                        break;
                    case "MM3":
                        mode = MeasurmentMode.MM3_ExportLocking;
                        break;
                    case "MM4":
                        mode = MeasurmentMode.MM4_Export;
                        break;
                    default:
                        throw new InvalidEnumArgumentException("Invalid measurement mode. Measurement mode must be within MM1 ... MM4.");
                }

                var numParam = new UnsignedParam()
                {
                    ObisCode = VendorSpecificRegisterIds.MeasurementMode.AsLong(), Data = (ulong) mode
                };

                await ClientComListDict[communicationPort.ComPortName].ComThread.WriteParameter(numParam);
            }
        }
        /// <summary>
        /// Reads the measurement mode.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="communicationPort">The communication port.</param>
        /// <returns>The configured measurement mode.</returns>
        public async Task<UnsignedParam> ReadMeasurementMode(Guid clientId, CommunicationPort communicationPort)
        {
            CheckConfiguration(communicationPort, clientId);

            var numParam = new UnsignedParam()
            {
                Data = null
            };

            if (communicationPort.MeterType == MeterType.Basemeter)
            {
                numParam.ObisCode = VendorSpecificRegisterIds.MeasurementMode.AsLong();
            }
            else if (communicationPort.MeterType == MeterType.Edl)
            {
                numParam.ObisCode = (long) ObisId.EdlDzgMeasurementMode;
            }
            else
            {
                throw new InvalidOperationException();
            }

            numParam = await ClientComListDict[communicationPort.ComPortName].ComThread.ReadParameter(numParam);
            return numParam;
        }
        //-- BEGIN

        public async Task<UnsignedParam> ReadMeterType(Guid clientId, CommunicationPort communicationPort)
        {
            var port = CheckConfiguration(communicationPort, clientId);

            var param = new UnsignedParam
            {
                Data = null,
                ObisCode = VendorSpecificRegisterIds.MeterType.AsLong()
            };

            param = await port.ComThread.ReadParameter(param);
            return param;
        }
        /// <summary>
        /// Writes the number of tariffs.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="communicationPort">The communication port.</param>
        /// <param name="numberOfTariffs">The number of tariffs.</param>
        public async Task WriteNumberOfTariffs(Guid clientId, CommunicationPort communicationPort, byte numberOfTariffs)
        {
            CheckConfiguration(communicationPort, clientId);

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

            await ClientComListDict[communicationPort.ComPortName].ComThread.WriteParameter(numParam);
        }
        /// <summary>
        /// Writes the parameter.
        /// </summary>
        /// <param name="param">The param.</param>
        public async Task WriteParameter(UnsignedParam param, bool enableManufacturerMode = false)
        {
            this.logger.Info("Writing unsigned parameter {0} with value {1}.", (ObisId)param.ObisCode, BitConverter.ToString(BitConverter.GetBytes(param.Data.Value)));
            await Retry.DoTask(
                async () =>
                {
                    var requestFile = SmlHandler.CreateSetRequestFile(this.endpoint, param);
                    await this.SendRequestFile(requestFile, enableManufacturerMode);
                    if (writeOnlyParams.Contains(param.ObisCode))
                    {
                        return;
                    }

                    var result = await this.ReadParameter(new UnsignedParam() { ObisCode = param.ObisCode });

                    if (param.Data != result.Data)
                    {
                        throw new DataException(
                            $"Failed to write and verify {param.ObisCode:X12}: written: {param.Data}, read: {result.Data}");
                    }
                });
        }