using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO.Ports;
using System.Threading;
using System.IO;
using System.Net;

namespace ClimaChamber
{
    /// <summary>
    /// Climatic chamber for rotary table 
    /// </summary>
    public class ModbusRateTable : IClimaticChamber
    {
        #region public global values
        /// <summary>
        /// Actual temperature
        /// </summary>
        public float Act_Temperature { get { return Chamber.ActualValues.Temperature; } }
        /// <summary>
        /// Actual humidity
        /// </summary>
        public float Act_Humidity { get { return Chamber.ActualValues.Humidity; } }
        /// <summary>
        /// Actual temperature gradient
        /// </summary>
        public float Act_TempGradient { get { return Chamber.ActualValues.TempGradient; } }
        /// <summary>
        /// Actual humidity gradient
        /// </summary>
        public float Act_HumiGradient { get { return Chamber.ActualValues.HumiGradient; } }
        /// <summary>
        /// Nominal temperature
        /// </summary>
        public float Set_Temperature { get { return Chamber.SetPoints.Temperature; } }
        /// <summary>
        /// Nominal humidity
        /// </summary>
        public float Set_Humidity { get { return Chamber.SetPoints.Humidity; } }
        /// <summary>
        /// Nominal temperature gradient
        /// </summary>
        public float Set_TempGradient { get { return Chamber.SetPoints.TempGradient; } }
        /// <summary>
        /// Nominal humidity gradient
        /// </summary>
        public float Set_HumiGradient { get { return Chamber.SetPoints.HumiGradient; } }
        /// <summary>
        /// State of connection fo device
        /// </summary>
        public bool IsConnected { get { return SP.IsOpen; } }

        /// <summary>
        /// Default parameter for Modbus
        /// </summary>
        public readonly int DEF_BaudRate = 9600;
        /// <summary>
        /// Default parameter for Modbus
        /// </summary>
        public readonly Parity DEF_Parity = Parity.None;
        /// <summary>
        /// Default parameter for Modbus
        /// </summary>
        public readonly StopBits DEF_StopBits = StopBits.One;
        /// <summary>
        /// Default parameter for Modbus
        /// </summary>
        public readonly int DEF_DataLen = 8;

        #endregion

        #region private global values
        /// <summary>
        /// bytewise lookup table for CRC generation
        /// </summary>
        private static readonly ushort[] lut =
    {
        0x0000,0xc0c1,0xc181,0x0140,0xc301,0x03c0,0x0280,0xc241,
        0xc601,0x06c0,0x0780,0xc741,0x0500,0xc5c1,0xc481,0x0440,
        0xcc01,0x0cc0,0x0d80,0xcd41,0x0f00,0xcfc1,0xce81,0x0e40,
        0x0a00,0xcac1,0xcb81,0x0b40,0xc901,0x09c0,0x0880,0xc841,
        0xd801,0x18c0,0x1980,0xd941,0x1b00,0xdbc1,0xda81,0x1a40,
        0x1e00,0xdec1,0xdf81,0x1f40,0xdd01,0x1dc0,0x1c80,0xdc41,
        0x1400,0xd4c1,0xd581,0x1540,0xd701,0x17c0,0x1680,0xd641,
        0xd201,0x12c0,0x1380,0xd341,0x1100,0xd1c1,0xd081,0x1040,
        0xf001,0x30c0,0x3180,0xf141,0x3300,0xf3c1,0xf281,0x3240,
        0x3600,0xf6c1,0xf781,0x3740,0xf501,0x35c0,0x3480,0xf441,
        0x3c00,0xfcc1,0xfd81,0x3d40,0xff01,0x3fc0,0x3e80,0xfe41,
        0xfa01,0x3ac0,0x3b80,0xfb41,0x3900,0xf9c1,0xf881,0x3840,
        0x2800,0xe8c1,0xe981,0x2940,0xeb01,0x2bc0,0x2a80,0xea41,
        0xee01,0x2ec0,0x2f80,0xef41,0x2d00,0xedc1,0xec81,0x2c40,
        0xe401,0x24c0,0x2580,0xe541,0x2700,0xe7c1,0xe681,0x2640,
        0x2200,0xe2c1,0xe381,0x2340,0xe101,0x21c0,0x2080,0xe041,
        0xa001,0x60c0,0x6180,0xa141,0x6300,0xa3c1,0xa281,0x6240,
        0x6600,0xa6c1,0xa781,0x6740,0xa501,0x65c0,0x6480,0xa441,
        0x6c00,0xacc1,0xad81,0x6d40,0xaf01,0x6fc0,0x6e80,0xae41,
        0xaa01,0x6ac0,0x6b80,0xab41,0x6900,0xa9c1,0xa881,0x6840,
        0x7800,0xb8c1,0xb981,0x7940,0xbb01,0x7bc0,0x7a80,0xba41,
        0xbe01,0x7ec0,0x7f80,0xbf41,0x7d00,0xbdc1,0xbc81,0x7c40,
        0xb401,0x74c0,0x7580,0xb541,0x7700,0xb7c1,0xb681,0x7640,
        0x7200,0xb2c1,0xb381,0x7340,0xb101,0x71c0,0x7080,0xb041,
        0x5000,0x90c1,0x9181,0x5140,0x9301,0x53c0,0x5280,0x9241,
        0x9601,0x56c0,0x5780,0x9741,0x5500,0x95c1,0x9481,0x5440,
        0x9c01,0x5cc0,0x5d80,0x9d41,0x5f00,0x9fc1,0x9e81,0x5e40,
        0x5a00,0x9ac1,0x9b81,0x5b40,0x9901,0x59c0,0x5880,0x9841,
        0x8801,0x48c0,0x4980,0x8941,0x4b00,0x8bc1,0x8a81,0x4a40,
        0x4e00,0x8ec1,0x8f81,0x4f40,0x8d01,0x4dc0,0x4c80,0x8c41,
        0x4400,0x84c1,0x8581,0x4540,0x8701,0x47c0,0x4680,0x8641,
        0x8201,0x42c0,0x4380,0x8341,0x4100,0x81c1,0x8081,0x4040
    };//lut[]

        /// <summary>
        /// Global serial class
        /// </summary>
        private SerialPort SP;
        /// <summary>
        /// Trigger for received message
        /// </summary>
        private EventWaitHandle RxCancelEvent;
        /// <summary>
        /// Locker
        /// </summary>
        private Object RxSyncObject;
        /// <summary>
        /// Buffer for receive messages
        /// </summary>
        private Byte[] ReadBuffer;

        /// <summary>
        /// Device address
        /// </summary>
        private const byte DeviceAddress = 0x01;
        /// <summary>
        /// Number of registers for Function number 3
        /// </summary>
        private const ushort Func3NumRegisters = 1;
        /// <summary>
        /// Starts register for temperature
        /// </summary>
        private const ushort TEMP = 108;
        /// <summary>
        /// Starts register for Function number 3
        /// </summary>
        private const ushort Func3StartsReg = TEMP;
        /// <summary>
        /// Starts register for nominal temperature
        /// </summary>
        private const ushort SETPOINT_TEMP = 300;
        /// <summary>
        /// Starts register for change value to celsia
        /// </summary>
        private const ushort CELSIA = 901;
        /// <summary>
        /// Holds information about nominal and actual values and settings of device
        /// </summary>
        private Memory_t Chamber;
        /// <summary>
        /// Default temperature gradient = maximal gradient for this device
        /// </summary>
        private readonly float DEF_TempGrad = 15f;
        /// <summary>
        /// Default humidity gradient = maximal gradient for this device
        /// </summary>
        private readonly float DEF_HumiGrad = 6f;
        /// <summary>
        /// Locker
        /// </summary>
        private object Locker;
        /// <summary>
        /// Minimal limit for humidity
        /// </summary>
        private const float DEF_HUMI_MIN = 0f;
        /// <summary>
        /// Maximal limit for humidity
        /// </summary>
        private const float DEF_HUMI_MAX = 90f;
        /// <summary>
        /// Minimal limit for temperature
        /// </summary>
        private const float DEF_TEMP_MIN = -70f;
        /// <summary>
        /// Maximal limit for temperature
        /// </summary>
        private const float DEF_TEMP_MAX = 90f;
        /// <summary>
        /// Locker
        /// </summary>
        private object Locker4Var;
        /// <summary>
        /// Buffer for raw received bytes
        /// </summary>
        private byte[] ReceivedMSG;
        /// <summary>
        /// Locker
        /// </summary>
        private object FuncLock;
        /// <summary>
        /// COM port name
        /// </summary>
        private string COMname = "COM19";
        #endregion

        #region Structure & Enums
        /// <summary>
        /// All possible state of reading messages
        /// </summary>
        public enum ReadResult
        {
            PacketReceived,
            CrcError,
            Cancelled,
            Error
        }
        /// <summary>
        /// All possible state of parsing income packet
        /// </summary>
        private enum PacketParsingStatus
        {
            Header_s,
            StandardResponse_s,
            ExceptionResponse_s,
            Exit_s
        }
        /// <summary>
        /// All possible state of chamber
        /// </summary>
        public enum ChamberStatus_e
        {
            ErrorInRead,
            ErrorCRC,
            ErrorCantSend,
            SetpointConfirm,
            Disconnected,
            Connected
        }
        /// <summary>
        /// Holds temperature, humidity and their gradients
        /// </summary>
        private struct Values_t
        {
            public float Temperature;
            public float TempGradient;
            public float Humidity;
            public float HumiGradient;
        }
        /// <summary>
        /// Holds infomration about values and settings
        /// </summary>
        private struct Memory_t
        {
            public Values_t SetPoints;
            public Values_t ActualValues;
            public DateTime LastUpdate;
        }
        #endregion

        #region Constructors & Destructor
        /// <summary>
        /// Constructor. Allocate memory.
        /// </summary>
        public ModbusRateTable()
        {
            Chamber.ActualValues = new Values_t();
            Chamber.SetPoints = new Values_t();
            Chamber.SetPoints.HumiGradient = DEF_HumiGrad;
            Chamber.SetPoints.TempGradient = DEF_TempGrad;
            Chamber.LastUpdate = new DateTime();
            RxCancelEvent = new EventWaitHandle(false, EventResetMode.ManualReset);
            RxSyncObject = new Object();
            FuncLock = new object();
            Locker4Var = new object();
            ReceivedMSG = null;
            Locker = new object();
            Chamber = new Memory_t();
            SP = new SerialPort();
            ReadBuffer = new Byte[20];
        }
        #endregion

        #region Private sector
        /// <summary>
        /// Reads income messages
        /// </summary>
        /// <param name="InStream">Stream</param>
        /// <param name="buffer">buffer for message</param>
        /// <param name="Length">Number of bytes to read</param>
        /// <param name="CancelEvent">Cancelation event</param>
        /// <returns>TRUE - success</returns>
        private Boolean ReadToBuffer(Stream InStream, Byte[] buffer, int Length, WaitHandle CancelEvent)
        {
            int pos = 0;
            IAsyncResult Result;
            WaitHandle[] WaitHandles = new WaitHandle[2];
            WaitHandles[1] = CancelEvent;
            Boolean Response = true;

            while (Length != pos)
            {
                Result = InStream.BeginRead(buffer, pos, (Length - pos), null, null);
                WaitHandles[0] = Result.AsyncWaitHandle;


                if (0 == WaitHandle.WaitAny(WaitHandles, 200))
                {
                    try
                    {
                        pos += InStream.EndRead(Result);
                    }
                    catch (Exception ex)
                    {
                        iSTP_DebugConsole.DebugConsole.WriteLine(ex.ToString());
                        Response = false; ;
                    }
                }
                else
                {
                    Response = false;
                    break;
                }
            }

            return Response;
        }
        /// <summary>
        /// Reads income packet and parse them
        /// </summary>
        /// <param name="InStream">Stream</param>
        /// <param name="CancelEvent">Cancelation event</param>
        /// <param name="PacketLock">Locker</param>
        /// <param name="expectedLength">Bytes to read</param>
        /// <returns>TRUE - success</returns>
        private ReadResult ReadFromStream(Stream InStream, WaitHandle CancelEvent, Object PacketLock, int expectedLength)
        {
            ReadResult Result = ReadResult.Error;
            PacketParsingStatus Status = PacketParsingStatus.Header_s;
            do
            {
                switch (Status)
                {
                    case PacketParsingStatus.Header_s:

                        if (false == ReadToBuffer(InStream, ReadBuffer, 2, CancelEvent))
                        {
                            Result = ReadResult.Cancelled;
                            Status = PacketParsingStatus.Exit_s;
                            break;
                        }

                        Monitor.Enter(PacketLock);
                        if ((ReadBuffer[1] & 0x80) > 0)
                        {
                            Status = PacketParsingStatus.ExceptionResponse_s;
                        }
                        else
                        {
                            Status = PacketParsingStatus.StandardResponse_s;
                        }
                        break;

                    case PacketParsingStatus.ExceptionResponse_s:
                        Buffer.BlockCopy(ReadBuffer, 0, ReceivedMSG, 0, 2);
                        if (false == ReadToBuffer(InStream, ReadBuffer, 3, CancelEvent))
                        {
                            Result = ReadResult.Cancelled;
                        }
                        else
                        {
                            Buffer.BlockCopy(ReadBuffer, 0, ReceivedMSG, 2, 3);
                            if (CheckCRC(ReceivedMSG))
                                Result = ReadResult.PacketReceived;
                            else
                                Result = ReadResult.CrcError;
                        }
                        Status = PacketParsingStatus.Exit_s;
                        Monitor.Exit(PacketLock);
                        break;

                    case PacketParsingStatus.StandardResponse_s:
                        Buffer.BlockCopy(ReadBuffer, 0, ReceivedMSG, 0, 2);
                        if (false == ReadToBuffer(InStream, ReadBuffer, expectedLength - 2, CancelEvent))
                        {
                            Result = ReadResult.Cancelled;
                        }
                        else
                        {
                            Buffer.BlockCopy(ReadBuffer, 0, ReceivedMSG, 2, expectedLength - 2);
                            if (CheckCRC(ReceivedMSG))
                                Result = ReadResult.PacketReceived;
                            else
                                Result = ReadResult.CrcError;
                        }
                        Status = PacketParsingStatus.Exit_s;
                        Monitor.Exit(PacketLock);
                        break;

                }
            } while (Status != PacketParsingStatus.Exit_s);


            return Result;
        }

        /// <summary>
        /// Check present of error message and decode it
        /// </summary>
        /// <returns>TRUE - error message received</returns>
        private bool IsClimaExceptionPresent()
        {
            bool retVal = false;
            if ((ReceivedMSG[1] & 0x80) > 0)
            {
                switch (ReceivedMSG[2]) // exception code
                {
                    case 1:
                        ClimError.Add("Modbus - Chamber error", "Illegal command", "Error type 01");
                        break;
                    case 2:
                        ClimError.Add("Modbus - Chamber error", "Illegal data address", "Error type 02");
                        break;
                    case 3:
                        ClimError.Add("Modbus - Chamber error", "Illegal data value", "Error type 03");
                        break;
                }
                retVal = true;
            }

            return retVal;
        }

        /// <summary>
        /// Calculates CRC
        /// </summary>
        /// <param name="message">Message</param>
        /// <param name="CRC">Final CRC</param>
        private void GetCRC(byte[] message, ref byte[] CRC)
        {
            //Function expects a modbus _message of any iLength as well as a 2 byte CRC array in which to
            //return the CRC values:

            ushort CRCFull = 0xFFFF;

            for (int i = 0; i < (message.Length - 2); i++)
            {
                CRCFull ^= message[i];
                CRCFull = (ushort)(lut[CRCFull & 0xff] ^ ((CRCFull >> 8) & 0xff));
            }
            CRC[1] = (byte)(CRCFull >> 8);
            CRC[0] = (byte)CRCFull;
        }

        /// <summary>
        /// Read holding register. 017 003 000 032 000 004 071 083.
        /// </summary>
        /// <param name="iAddress">Device address</param>
        /// <param name="iStart">Address to start to read</param>
        /// <param name="iRegisters">Number of registers to read</param>
        /// <returns>TRUE - success</returns>
        private bool SendFc3(byte iAddress, ushort iStart, ushort iRegisters)
        {
            ReadResult result;
            bool retVal = true;

            lock (FuncLock)
            {
                //Ensure iPort is open:
                if (SP.IsOpen)
                {

                    //Function 3 request is always 8 bytes => 4 bytes for _data
                    byte[] _msgData = new byte[4];
                    _msgData[0] = (byte)(iStart >> 8);
                    _msgData[1] = (byte)(iStart & 0x00FF);
                    _msgData[2] = (byte)(iRegisters >> 8);
                    _msgData[3] = (byte)(iRegisters & 0x00FF);

                    //Function 3 _response buffer:
                    int responseLength = 5 + 2 * iRegisters;

                    //Send modbus message to Serial Port:
                    try
                    {
                        //Build outgoing modbus message:
                        BuildSendMessage(iAddress, (byte)3, ref _msgData);
                        ReceivedMSG = new byte[responseLength];
                        result = ReadFromStream(SP.BaseStream, RxCancelEvent, RxSyncObject, responseLength);

                        if (result == ReadResult.CrcError)
                        {
                            string _textFull = "|";
                            foreach (byte b in ReceivedMSG)
                            {
                                _textFull += b.ToString() + "|";
                            }
                            ClimError.Add("Modbus.SendFc3", "Bad CRC", string.Format("Msg:  {0} Time: {1}", _textFull, DateTime.Now));
                            retVal = false;
                        }
                        else if (result == ReadResult.Cancelled)
                        {
                            Chamber.ActualValues.Temperature = 0;
                            retVal = false;
                        }
                        else
                        {
                            if (IsClimaExceptionPresent())
                                retVal = false;

                            lock (Locker4Var)
                            {
                                byte[] _val = new byte[2];
                                Buffer.BlockCopy(ReceivedMSG, 3, _val, 0, 2);
                                short _inttemp = (short)((int)ReceivedMSG[3] << 8 | (int)ReceivedMSG[4]);

                                Chamber.ActualValues.Temperature = Convert.ToSingle(_inttemp) / 10f;
                            }
                        }
                    }
                    catch (IOException)
                    {
                        iSTP_DebugConsole.DebugConsole.WriteLine("IOException in Fcn3");
                        iSTP_DebugConsole.DebugConsole.WriteLine(string.Format("Try reconnect ... result: {0}", Connect(COMname, "0", "0", "0", "0")));   
                    }
                    catch (Exception ex)
                    {
                        ClimError.Add("Modbus.SendFc3", "Exception", ex.ToString());
                        iSTP_DebugConsole.DebugConsole.WriteLine(ex.ToString());
                        retVal = false;
                    }
                }
                else
                {
                    ClimError.Add("Modbus.SendFc3", "Communication problem", "Serial Port not open");
                    retVal = false;
                }
            }
            return retVal;
        }

        /// <summary>
        /// Writes new set points
        /// </summary>
        /// <param name="iAddress">Device address</param>
        /// <param name="iStart">Address to start to write</param>
        /// <param name="iRegisters">Number of registers to write</param>
        /// <param name="iValues">Values to write in registers</param>
        /// <returns>TRUE - success</returns>
        private bool SendFc16(byte iAddress, ushort iStart, ushort iRegisters, short[] iValues)
        {
            ReadResult result;
            bool retVal = true;
            //Ensure iPort is open:
            if (SP.IsOpen)
            {
                byte[] _msgData = new byte[5 + 2 * iValues.Length];
                _msgData[0] = (byte)(iStart >> 8);
                _msgData[1] = (byte)(iStart & 0x00FF);
                _msgData[2] = (byte)(iRegisters >> 8);
                _msgData[3] = (byte)(iRegisters & 0x00FF);
                _msgData[4] = (byte)(2 * iRegisters);
                int _index = 5;
                foreach (short s in iValues)
                {
                    _msgData[_index++] = (byte)(s >> 8);
                    _msgData[_index++] = (byte)(s & 0x00FF);
                }
                //Function 16 _response buffer:
                const int responseLength = 8;

                //Send modbus _message to Serial Port:
                try
                {
                    //Build outgoing modbus message:
                    BuildSendMessage(iAddress, (byte)16, ref _msgData);

                    ReceivedMSG = new byte[responseLength];
                    result = ReadFromStream(SP.BaseStream, RxCancelEvent, RxSyncObject, responseLength);

                    if (result == ReadResult.CrcError)
                    {
                        string _textFull = "|";
                        foreach (byte b in ReceivedMSG)
                        {
                            _textFull += b.ToString() + "|";
                        }
                        ClimError.Add("Modbus.SendFc16", "Bad CRC", string.Format("Msg:  {0} Time: {1}", _textFull, DateTime.Now));
                        retVal = false;
                    }
                    else if (result == ReadResult.Cancelled)
                    {
                        retVal = false;
                    }
                    else
                    {
                        if (IsClimaExceptionPresent())
                            retVal = false;
                    }
                }
                catch (Exception ex)
                {
                    ClimError.Add("Modbus.SendFc16", "Exception", ex.ToString());
                    retVal = false;
                    iSTP_DebugConsole.DebugConsole.WriteLine(ex.ToString());
                }
            }
            else
            {
                ClimError.Add("Modbus.SendFc16", "Communication problem", "Serial Port not open");
                retVal = false;
            }
            return retVal;
        }

        /// <summary>
        /// Verify message
        /// </summary>
        /// <param name="iResponse">receive message</param>
        /// <returns>TRUE - message verified</returns>
        private bool CheckCRC(byte[] iResponse)
        {
            byte[] _crc = new byte[2];
            GetCRC(iResponse, ref _crc);
            if (_crc[0] == iResponse[iResponse.Length - 2] && _crc[1] == iResponse[iResponse.Length - 1])
                return true;
            return false;
        }

        /// <summary>
        /// Builds message and sends it to device
        /// </summary>
        /// <param name="iAddress">Device address</param>
        /// <param name="iFuncNum">Function number</param>
        /// <param name="iMessage">Message to send</param>
        /// <returns>Number of bytes</returns>
        private int BuildSendMessage(byte iAddress, byte iFuncNum, ref byte[] iMessage)
        {
            if (!SP.IsOpen)
                return 0;

            byte[] _msg = new byte[iMessage.Length + 4];
            _msg[0] = iAddress;
            _msg[1] = iFuncNum;
            uint _index = 2;
            foreach (byte b in iMessage)
                _msg[_index++] = b;

            byte[] _crc = new byte[2];
            GetCRC(_msg, ref _crc);
            _msg[_index++] = _crc[0];
            _msg[_index++] = _crc[1];


            try
            {
                lock (Locker)
                {
                    SP.DiscardOutBuffer();
                    SP.DiscardInBuffer();
                    SP.BaseStream.Write(_msg, 0, _msg.Length);
                }
            }
            catch (ArgumentNullException ex)
            {
                ClimError.Add("Modbus.BuildSendMessage", "Null argument", ex.ToString());
            }
            catch (ArgumentOutOfRangeException ex)
            {
                ClimError.Add("Modbus.BuildSendMessage", "Argument out of range", ex.ToString());
            }
            catch (ArgumentException ex)
            {
                ClimError.Add("Modbus.BuildSendMessage", "Argument exception", ex.ToString());
            }
            catch (InvalidOperationException ex)
            {
                ClimError.Add("Modbus.BuildSendMessage", "Invalid operation", ex.ToString());
            }
            catch (TimeoutException ex)
            {
                ClimError.Add("Modbus.BuildSendMessage", "Timeout", ex.ToString());
            }
            catch (Exception ex)
            {
                ClimError.Add("Modbus.BuildSendMessage", "Exception", ex.ToString());
            }
            return _msg.Length;
        }

        /// <summary>
        /// Check values with limit
        /// </summary>
        /// <param name="iParam">Value to verify</param>
        /// <param name="iMin">Miminal value</param>
        /// <param name="iMax">Maximal value</param>
        private void RangeLimit(ref float iParam, float iMin, float iMax)
        {
            if (iParam > iMax)
                iParam = iMax;
            else if (iParam < iMin)
                iParam = iMin;
        }
        /// <summary>
        /// Gets data from knew nominal values
        /// </summary>
        /// <returns>All nominal values, Temperatue, Temp. graident, Humidity, Humi. gradient</returns>
        private short[] PrepareChamberCommandData()
        {
            return new short[] 
            {    
                BitConverter.ToInt16(BitConverter.GetBytes(Chamber.SetPoints.Temperature), 2),
                BitConverter.ToInt16(BitConverter.GetBytes(Chamber.SetPoints.Temperature), 0),
                BitConverter.ToInt16(BitConverter.GetBytes(Chamber.SetPoints.Humidity), 2),
                BitConverter.ToInt16(BitConverter.GetBytes(Chamber.SetPoints.Humidity), 0),
            };
        }
        #endregion

        #region Public sector
        /// <summary>
        /// Connect to chamber through rs232
        /// </summary>
        /// <param name="portName">Name of the COM port.</param>
        /// <param name="baudRate">Ignored, the method uses default value.</param>
        /// <param name="dataBits">Ignored, the method uses default value.</param>
        /// <param name="stopBits">Ignored, the method uses default value.</param>
        /// <param name="parity">Ignored, the method  uses default value.</param>
        /// <returns>If port is open, the methods will return TRUE</returns>
        public bool Connect
            (
                string portName,
                string baudRate,
                string dataBits,
                string stopBits,
                string parity
            )
        {
            COMname = portName;
            if (!SP.IsOpen)
            {
                SP.PortName = portName;
                SP.BaudRate = DEF_BaudRate;
                SP.DataBits = DEF_DataLen;
                SP.StopBits = DEF_StopBits;
                SP.Parity = DEF_Parity;

                try
                {
                    SP.Open();
                }
                catch (UnauthorizedAccessException ex)
                {
                    ClimError.Add("Modbus.OpenSerialPort", "Unauthorizes Access", ex.ToString());
                }
                catch (ArgumentOutOfRangeException ex)
                {
                    ClimError.Add("Modbus.OpenSerialPort", "Argument out of range", ex.ToString());
                }
                catch (ArgumentException ex)
                {
                    ClimError.Add("Modbus.OpenSerialPort", "Argument exception", ex.ToString());
                }
                catch (IOException ex)
                {
                    ClimError.Add("Modbus.OpenSerialPort", "IO exception", ex.ToString());
                }
                catch (InvalidOperationException ex)
                {
                    ClimError.Add("Modbus.OpenSerialPort", "Invalid operation", ex.ToString());
                }
                catch (Exception ex)
                {
                    ClimError.Add("Modbus.OpenSerialPort", "Exception", ex.ToString());
                }
            }

            //Switch to Celsius
            short[] _val = new short[] { 1 };
            SendFc16(DeviceAddress, CELSIA, (ushort)1, _val);

            return SP.IsOpen;
        }

        /// <summary>
        /// The Angelantoni DY500 chamber does not support network connection.
        /// </summary>
        /// <param name="iIP">Chambers IP address</param>
        /// <param name="port">Chambers port</param>
        /// <returns>always false</returns>
        public bool Connect(IPAddress iIP, int port)
        {
            return false;
        }

        /// <summary>
        /// Diconnect from chamber
        /// </summary>
        public void Disconnect()
        {
            if (SP.IsOpen)
            {
                RxCancelEvent.Set();
                SP.Close();
            }
        }

        /// <summary>
        /// Set new temperature to chamber without starting program.
        /// </summary>
        /// <param name="iTemp">Temperature in Celsius</param>
        /// <returns>Chamber accepts new temperature = TRUE</returns>
        public bool SetTemperature(float iTemp)
        {
            RangeLimit(ref iTemp, DEF_TEMP_MIN, DEF_TEMP_MAX);
            Chamber.SetPoints.Temperature = iTemp;
            short[] _val = new short[] { Convert.ToInt16(iTemp*10) };

            return SendFc16(DeviceAddress, SETPOINT_TEMP, (ushort)1, _val);
        }

        /// <summary>
        /// Set new humidity to chamber without starting program. ... Not supported
        /// </summary>
        /// <param name="iHumi">Humidity in %</param>
        /// <returns>Chamber accepts new humidity = TRUE</returns>
        public bool SetHumidity(float iHumi)
        {
            return false;
        }

        /// <summary>
        /// Set new humidity (Not supported) and temperature to chamber without starting program.
        /// </summary>
        /// <param name="iTemp">Temperature in Celsius</param>
        /// <param name="iHumi">Humidity in %</param>
        /// <returns>Chamber accepts new nominal values = TRUE</returns>
        public bool SetTempHumi(float iTemp, float iHumi)
        {
            RangeLimit(ref iTemp, DEF_TEMP_MIN, DEF_TEMP_MAX);
            Chamber.SetPoints.Temperature = iTemp;
            RangeLimit(ref iHumi, DEF_HUMI_MIN, DEF_HUMI_MAX);
            Chamber.SetPoints.Humidity = 0;
            float temp = iTemp;

            short[] _val = new short[] { (short)((int)(Chamber.SetPoints.Temperature * 10)) };

            return SendFc16(DeviceAddress, SETPOINT_TEMP, (ushort)1, _val);
        }

        /// <summary>
        /// Return last actual temperature
        /// </summary>
        /// <returns>Last actual temperature</returns>
        public float GetActTemp()
        {
            lock (Locker4Var)
            {
                return Act_Temperature;
            }
        }

        /// <summary>
        /// Return last actual humidity
        /// </summary>
        /// <returns>Last actual humidity</returns>
        public float GetActHumi()
        {
            lock (Locker4Var)
            {
                return Act_Humidity;
            }
        }

        /// <summary>
        /// Return last nominal temperature
        /// </summary>
        /// <returns>Last nominal temperature</returns>
        public float GetNominalTemp()
        {
            lock (Locker4Var)
            {
                return Set_Temperature;
            }
        }

        /// <summary>
        /// Return last nominal humidity
        /// </summary>
        /// <returns>Last nominal humidity</returns>
        public float GetNominalHumi()
        {
            lock (Locker4Var)
            {
                return Set_Humidity;
            }
        }

        /// <summary>
        /// Sets new heat and humidity gradients ... Not supported
        /// </summary>
        /// <param name="iHeatGradient">Temperature gradient</param>
        /// <param name="iHumidityGradient">Humidity gradient</param>
        /// <returns>Chamber accepts new gradients = TRUE</returns>
        public bool SetChangeRates(float iHeatGradient, float iHumidityGradient)
        {
            RangeLimit(ref iHeatGradient, 0f, DEF_TempGrad);
            Chamber.SetPoints.TempGradient = iHeatGradient;
            RangeLimit(ref iHumidityGradient, 0f, DEF_HumiGrad);
            Chamber.SetPoints.HumiGradient = iHumidityGradient;

            return false;
        }

        /// <summary>
        /// Sets new heat and humidity gradients ... Not supported
        /// </summary>
        /// <param name="iHeatGradient">Temperature gradient</param>
        /// <param name="iCoolGradient">Temperature gradient</param>
        /// <param name="iHumidityGradient">Humidity gradient</param>
        /// <param name="iDeHumidityGradient">Humidity gradient</param>
        /// <returns>Chamber accepts new gradients = TRUE</returns>
        public bool SetChangeRates(float iHeatGradient, float iCoolGradient, float iHumidityGradient, float iDeHumidityGradient)
        {
            return SetChangeRates(iHeatGradient, iHumidityGradient);
        }

        /// <summary>
        /// Not supported
        /// </summary>
        /// <returns>Chamber accepts changes = TRUE</returns>
        public bool Run()
        {
            return true;
        }

        /// <summary>
        /// Sets all possible  parameters
        /// </summary>
        /// <param name="iHeatGradient">Heating gradient</param>
        /// <param name="iCoolGradient">Cooling gradient</param>
        /// <param name="iHumidityGradient">Humidity gradient</param>
        /// <param name="iDeHumidityGradient">Dehumidity gradient</param>
        /// <param name="iTemp">Nominal temperature</param>
        /// <param name="iHumi">Nominal humidity</param>
        /// <returns>TRUE - success</returns>
        public bool SetAll(float iHeatGradient, float iCoolGradient, float iHumidityGradient, float iDeHumidityGradient, float iTemp, float iHumi)
        {
            RangeLimit(ref iTemp, DEF_TEMP_MIN, DEF_TEMP_MAX);
            Chamber.SetPoints.Temperature = iTemp;
            RangeLimit(ref iHumi, DEF_HUMI_MIN, DEF_HUMI_MAX);
            Chamber.SetPoints.Humidity = iHumi;
            RangeLimit(ref iHeatGradient, 0f, DEF_TempGrad);
            Chamber.SetPoints.TempGradient = iHeatGradient;
            RangeLimit(ref iHumidityGradient, 0f, DEF_HumiGrad);
            Chamber.SetPoints.HumiGradient = iHumidityGradient;


            short[] _val = new short[] { (short)((int)(Chamber.SetPoints.Temperature * 10)) };
            SendFc16(DeviceAddress, SETPOINT_TEMP, (ushort)1, _val);
            return true;
        }

        /// <summary>
        /// Not supported
        /// </summary>
        /// <returns>Chamber accepts changes = TRUE</returns>
        public bool Stop()
        {
            return false;
        }

        public DateTime LastUpdate()
        {
            return Chamber.LastUpdate;
        }

        /// <summary>
        /// Not supported
        /// </summary>
        /// <param name="iSleepInt">Updater interval</param>
        /// <returns>Updater is alive = TRUE</returns>
        public bool UpdaterStart(TimeSpan iSleepInt)
        {
            return false;
        }

        /// <summary>
        /// Not supported
        /// </summary>
        public void UpdaterStop()
        {

        }

        /// <summary>
        /// Update actual values.
        /// </summary>
        public void ManualUpdate()
        {
            SendFc3(DeviceAddress, Func3StartsReg, Func3NumRegisters);
        }
        /// <summary>
        /// This function is not supported
        /// </summary>
        /// <returns></returns>
        public bool CheckAndClearErr()
        {
            return true;
        }
        /// <summary>
        /// Gets devices name
        /// </summary>
        public string ChamberName { get { return ("WATLOW F4"); } }

        #endregion
    }
}
