using System;
using System.Threading;
using System.IO;
using System.IO.Ports;
using System.Net;
using iSTP_DebugConsole;

namespace ClimaChamber
{
    /// <summary>
    /// Provides communication with climatic chamber Angelantoni ACS (ATT) DY500 C 15 ESS
    /// 
    /// Error -> Receive packet has got MSB in high
    ///                 >   01  Illegal function
    ///                 >   02  Illegal _data address
    ///                 >   03  Illegal _data value
    ///                 >   04  Failure in associated device
    ///                 >   05  Acknowledge: the slave is processing the request
    ///                 >   06  Busy: busy device, rejected _message
    ///                         NAK: it is umpossible to execute the requested function
    ///                 >   07  Chamber parity error
    ///       -> No error > return same function code
    /// Delay -> Between packets minimum 3.5 * T (T is time in microseconds between packets on current baud rate) => 2800 microseconds
    /// Serial iPort -> Baud rate 9600
    ///             -> Parity None
    ///             -> Stop bits 1
    ///             -> Data iLength 8
    /// System measures:    Measure index       Measure address     Meaning
    ///                     0                   0                   Dry bulb temperature/PT100 suction
    ///                     1                   2                   Wet bulb/PT100 discharge
    ///                     2                   4                   PT100-User n.0
    ///                     3                   6                   PT100-User n.1
    ///                     4                   8                   PT100-User n.2
    ///                     5                   10                  PT100-User n.3
    ///                     6                   12                  Low stage suction presuure
    ///                     7                   14                  Low stage discharge pressure
    ///                     8                   16                  High stage suction pressure
    ///                     9                   18                  High stage discharge pressure
    ///                     10                  20                  User analog input 0
    ///                     11                  22                  User analog input 1
    ///                     12                  24                  User analog input 2
    ///                     13                  26                  User analog input 3
    ///                     14                  28                  User analog input 
    ///                     15                  30                  User analog input 5/Capacitive probe
    ///                     16                  32                  Chamber iTemperature
    ///                     17                  34                  Relative humidity
    ///                     18                  36                  Absolute iHumidity
    ///                     19                  38                  Not used
    ///                     20                  40                  Not used
    ///                     21                  42                  Not used
    ///                     22                  44                  Not used
    ///                     23                  46                  Not used
    ///                     24                  48                  Not used
    ///                     25                  50                  Not used
    ///                     26                  52                  CHannel 0 Measure
    ///                     27                  54                  CHannel 1 Measure
    ///                     28                  56                  Not used
    ///                     29                  58                  Not used
    ///                     30                  60                  Not used
    ///                     31                  62                  Not used
    /// User settings:      Index           User settings address       Real settings address       Meaning                         Notes
    ///                     0               69.0                        73.0                        RUN
    ///                     1               69.1                        73.1                        Alarm reset
    ///                     2               69.2                        73.2                        Program (1) - manual (0) mode
    ///                     3               69.3                        73.3                        Local (1) - Remote (0) mode     Reserved keep at 0
    ///                     4               69.4                        73.4                        Pause                           Reserved keep at 0
    ///                     5               69.5                        73.5                        Freese                          Reserved keep at 0
    ///                     8(6 for us)     69.8                        73.8                        Enable channel n. 0             iTemperature
    ///                     9(7 for us)     69.9                        73.9                        Enable channel n. 1             iHumidity
    ///                     16(8 for us)    70.0                        74.0                        Dedicated contact n. 01         Dehumidification
    ///                     17(9 for us)    70.1                        74.1                        Dedicated contact n. 02         Vibration system
    ///                     18(10 for us)   70.2                        74.2                        Dedicated contact n. 03         Device under test
    ///                     19(11 for us)   70.3                        74.3                        Dedicated contact n. 04         UV lamp
    ///                     20(12 for us)   70.4                        74.4                        Dedicated contact n. 05         Water recharge
    /// Setpoints reading:  Index           Address                     Meaning                     Notes
    ///                     0               77-78                       User final setpoint         iTemperature
    ///                     0               79-80                       Current user setpoint       iTemperature
    ///                     0               81-82                       Gradient                    iTemperature
    ///                     1               83-84                       User final setpoint         Relative humidity
    ///                     1               85-86                       Current user setpoint       Relative humidity
    ///                     1               87-88                       Gradient                    Relative humidity
    /// ************************************************************************************************************************************************                     
    /// User action:        Address         Meaning                         Notes
    ///                     500.0           RUN
    ///                     500.1           Alarm reset
    ///                     500.2           Program(1) - Manual (0) mode
    ///                     500.3           Local(1) - Remote (0) mode      Reserved keep at 0
    ///                     500.4           Pause (reserved)                Reserved keep at 0
    ///                     500.5           Freeze (reserved)               Reserved keep at 0
    ///                     500.8           Enable channel n. 0             iTemperature
    ///                     500.9           Enable channel n. 1             Realive humidity
    ///                     501.0           Dedicated contact n. 0          Dehumidification
    ///                     501.1           Dedicated contact n. 1          Vibration system
    ///                     501.2           Dedicated contact n. 2          Device under test
    ///                     501.3           Dedicated contact n. 3          UV Lamp
    ///                     501.4           Dedicated contact n. 4          Water recharger
    /// User sets:          Channel number      Address     Meaning                 Notes
    ///                     0                   504-505     User final setpoint     iTemperature
    ///                     0                   506-507     Gradient                iTemperature
    ///                     1                   508-509     User final setpoint     Relative humidity
    ///                     1                   510-511     Gradient                Relative humidity
    /// Message design:
    ///                 Device Address      Function Code       8b Data array       Error check
    /// ASCII MODE: (for each byte)
    ///         Coding system       >       Hex, ASCII chars
    ///                                     One hex char contained in each ASCII character of the _message
    ///         Bits per Byte       >       1 start bit
    ///                                     7 _data bits, LSB
    ///                                     1 bit for parity
    ///                                     1 stop bit (2 if none parity)
    ///         Error check field   >       Longitudinal Redundancy Check (LRC)
    ///         
    ///         Packets design:     Start   Address     function    _data    LRC     End
    ///                             1 char  2 chars     2 chars     n chars 2 chars 2 chars CRLF 
    /// 
    /// RTU MODE: (for each byte)
    ///         Coding system       >       Hex, ASCII chars
    ///                                     Two hex chars contained in each 8bit field
    ///         Bits per Byte       >       1 start bit
    ///                                     8 _data bits, LSB
    ///                                     1 bit for parity
    ///                                     1 stop bit (2 if none parity)
    ///         Error check field   >       Cyclical Redundancy Check (CRC)  
    ///         
    ///         Packets design:     Start           Address     function    _data        LRC         End
    ///                             T1-T2-T3-T4     8 bits      8 bits      n*8 bits    16 bits     T1-T2-T3-T4
    ///                   
    /// Functions code:     Code    Name                        384     484     584     884     M84     984
    ///                     01      Read coil status            y       y       y       y       y       y
    ///                     02      Read input status           y       y       y       y       y       y
    ///                     03      Read holding register       y       y       y       y       y       y
    ///                     04      Read input register         y       y       y       y       y       y
    ///                     05      Force single coil           y       y       y       y       y       y
    ///                     06      Preset single register      y       y       y       y       y       y
    ///                     07      Read exception status       y       y       y       y       y       y
    ///                     08      Diagnostincs
    ///                     09      Program 484                 n       y       n       n       n       n
    ///                     10      Poll 484                    n       y       n       n       n       n
    ///                     11      Fetch comm. Event ctr.      y       n       y       n       n       y
    ///                     12      Fetch comm. Event log       y       n       y       n       n       y
    ///                     13      Program controller          y       n       y       n       n       y
    ///                     14      Poll controller             y       n       y       n       n       y
    ///                     15      Force multiple coils        y       y       y       y       y       y
    ///                     16      Preset multiple register    y       y       y       y       y       y
    ///                     17      Preset
    ///              
    /// </summary>
    public class Modbus : 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 = new SerialPort();
        /// <summary>
        /// Trigger for received message
        /// </summary>
        private EventWaitHandle RxCancelEvent;
        /// <summary>
        /// Locker
        /// </summary>
        private Object RxSyncObject;
        /// <summary>
        /// Buffer for receive messages
        /// </summary>
        private Byte[] ReadBuffer = new Byte[20];

        /// <summary>
        /// Device address
        /// </summary>
        private readonly byte DeviceAddress = 0x11;
        /// <summary>
        /// Starts register for Function number 3
        /// </summary>
        private readonly ushort Func3StartsReg = 32;
        /// <summary>
        /// Number of registers for Function number 3
        /// </summary>
        private readonly ushort Func3NumRegisters = 4;
        /// <summary>
        /// Locker
        /// </summary>
        private object Locker = new object();

        /// <summary>
        /// Temperature address in device
        /// </summary>
        private readonly ushort WriteTempAdr = 504;
        //private readonly ushort WriteTempGradAdr = 506;
        /// <summary>
        /// Humidity address in device
        /// </summary>
        private readonly ushort WriteHumAdr = 508;
        //private readonly ushort WriteHumGradAdr = 510;

        /// <summary>
        /// Control address
        /// </summary>
        private readonly ushort ControlAdr_part1 = 500;
        //private readonly ushort ControlAdr_part2 = 501;

        /// <summary>
        /// Part 1 of basic initialization for device
        /// </summary>
        private readonly short RUN = 0;
        private readonly short ALARM = 1;
        private readonly short MANUAL = 2;
        private readonly short REMOTE = 3;
        private readonly short PAUSE = 4;
        private readonly short FREEZE = 5;
        private readonly short TEMPSENS = 8;
        private readonly short HUMISENS = 9;
        /// <summary>
        /// Part 2 of basic initialization for device
        /// </summary>
        private readonly ushort DEHUM = 1;
        private readonly ushort VIBRATION = 1;
        private readonly ushort DEVICEunderTEST = 2;
        private readonly ushort UVlamp = 3;
        private readonly ushort WATERrecharger = 4;
        private readonly ushort EXTtempSens_chamber1 = 7;
        private readonly ushort EXTtempSens_chamber2 = 6;

        /// <summary>
        /// Holds information about nominal and actual values and settings of device
        /// </summary>
        private Memory_t Chamber = new Memory_t();
        /// <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>
        /// Internal thread for automatic update
        /// </summary>
        private Thread UpdateTh = null;
        /// <summary>
        /// Internal timer for automatic update
        /// </summary>
        private TimeSpan UpdaterDelay = TimeSpan.FromSeconds(5);

        /// <summary>
        /// Minimal limit for humidity
        /// </summary>
        private readonly float DEF_HUMI_MIN = 10f;
        /// <summary>
        /// Maximal limit for humidity
        /// </summary>
        private readonly float DEF_HUMI_MAX = 98f;
        /// <summary>
        /// Minimal limit for temperature
        /// </summary>
        private readonly float DEF_TEMP_MIN = -120f;
        /// <summary>
        /// Maximal limit for temperature
        /// </summary>
        private readonly float DEF_TEMP_MAX = 180f;
        /// <summary>
        /// Locker
        /// </summary>
        private object Locker4Var = new object();
        /// <summary>
        /// State of internal updater
        /// </summary>
        private volatile bool UpdaterEnabled;
        /// <summary>
        /// Buffer for raw received bytes
        /// </summary>
        private byte[] ReceivedMSG = null;
        /// <summary>
        /// Locker
        /// </summary>
        private object FuncLock = new object();
        #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;
            public short LastSettings_part1;
            public short LastSettings_part2;
        }
        #endregion

        #region Constructors & Destructor
        /// <summary>
        /// Constructor. Allocate memory.
        /// </summary>
        public Modbus()
        {
            Chamber.ActualValues = new Values_t();
            Chamber.SetPoints = new Values_t();
            Chamber.SetPoints.HumiGradient = DEF_HumiGrad;
            Chamber.SetPoints.TempGradient = DEF_TempGrad;
            Chamber.LastUpdate = new DateTime();
            Chamber.LastSettings_part1 = BasicSettingsPart1();
            Chamber.LastSettings_part2 = BasicSettingsPart2();
            RxCancelEvent = new EventWaitHandle(false, EventResetMode.ManualReset);
            RxSyncObject = new Object();
        }
        #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))
                {
                    try
                    {
                        pos += InStream.EndRead(Result);
                    }
                    catch (Exception ex)
                    {
                        ClimError.Add("Modbus.cs", "Cannot read data from receive buffer", 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>
        public 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 function", "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;
                    case 4:
                        ClimError.Add("Modbus - Chamber error", "Failure in associated device", "Error type 04");
                        break;
                    case 5:
                        ClimError.Add("Modbus - Chamber error", "Acknowledge: the slave is processing the request", "Error type 05");
                        break;
                    case 6:
                        ClimError.Add("Modbus - Chamber error", "Device is busy - message rejected", "Error type 06");
                        break;
                    case 7:
                        ClimError.Add("Modbus - Chamber error", "Memory parity error", "Error type 07");
                        break;
                }
                retVal = true;                    
            }
         
            return retVal;
        }

        /// <summary>
        /// Updating temperature and humidity every 5 seconds
        /// </summary>
        private void UpdaterThread()
        {
            try
            {
                DebugConsole.WriteLine("Starting Modbus updater");
                while (UpdaterEnabled)
                {
                    TimeSpan _delay = DateTime.Now - this.Chamber.LastUpdate;
                    if(_delay < TimeSpan.FromMilliseconds(400))
                        Thread.Sleep(TimeSpan.FromMilliseconds(400 -_delay.TotalMilliseconds));
                    if (SP.IsOpen)
                    {
                        this.SendFc3(DeviceAddress, Func3StartsReg, Func3NumRegisters);
                    }
                    Thread.Sleep(UpdaterDelay);
                }
                DebugConsole.WriteLine("ending Modbus updater");
            }
            catch (ThreadAbortException ex)
            {
                DebugConsole.WriteLine("Modbus updater ThreadAbortException encountered");
                ClimError.Add("Modbus.UpdaterThread", "Thread abort", ex.ToString());
                Thread.ResetAbort();
            }
            catch (Exception ex)
            {
                ClimError.Add("Modbus.UpdaterThread", "Exception", ex.ToString());
            }
        }

        /// <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)
                {
                    TimeSpan _delay = DateTime.UtcNow - Chamber.LastUpdate;
                    if (_delay < TimeSpan.FromMilliseconds(300))
                        Thread.Sleep(_delay);

                    //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 (IsClimaExceptionPresent())
                                retVal = false;
                        
                            lock (Locker4Var)
                            {
                                byte[] _val = new byte[4];
                                Buffer.BlockCopy(ReceivedMSG, 3, _val, 0, 4);
                                Array.Reverse(_val);
                                Chamber.ActualValues.Temperature = BitConverter.ToSingle(_val, 0);
                                byte[] _val2 = new byte[4];
                                Buffer.BlockCopy(ReceivedMSG, 7, _val2, 0, 4);
                                Array.Reverse(_val2);
                                Chamber.ActualValues.Humidity = BitConverter.ToSingle(_val2, 0);
                            }                         
                        }
                    }
                    catch (Exception ex)
                    {
                        ClimError.Add("Modbus.SendFc3", "Exception", 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 (IsClimaExceptionPresent())
                            retVal = false;
                    }
                }
                catch (Exception ex)
                {
                    ClimError.Add("Modbus.SendFc16", "Exception", ex.ToString());
                    retVal = false;
                }
            }
            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>
        /// Gets Basic setting Part 1
        /// </summary>
        /// <returns>Gets Basic setting Part 1</returns>
        private short BasicSettingsPart1()
        {
            return (short)((0 << RUN) |
                            (0 << ALARM) |
                            (0 << MANUAL) |
                            (0 << REMOTE) |
                            (0 << PAUSE) |
                            (0 << FREEZE) |
                            (1 << TEMPSENS) |
                            (1 << HUMISENS));
        }

        /// <summary>
        /// Gets Basic setting Part 1
        /// </summary>
        /// <returns>Gets Basic setting Part 1</returns>
        private short BasicSettingsPart2()
        {
            return (short)((0 << DEHUM) |
                            (0 << VIBRATION) |
                            (0 << DEVICEunderTEST) |
                            (0 << UVlamp) |
                            (0 << WATERrecharger) |
                            (1 << EXTtempSens_chamber1));
        }

        /// <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.TempGradient), 2),
                                    BitConverter.ToInt16(BitConverter.GetBytes(Chamber.SetPoints.TempGradient), 0),
                                    BitConverter.ToInt16(BitConverter.GetBytes(Chamber.SetPoints.Humidity), 2),
                                    BitConverter.ToInt16(BitConverter.GetBytes(Chamber.SetPoints.Humidity), 0),
                                    BitConverter.ToInt16(BitConverter.GetBytes(Chamber.SetPoints.HumiGradient), 2),
                                    BitConverter.ToInt16(BitConverter.GetBytes(Chamber.SetPoints.HumiGradient), 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)
        {
            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());
                }
            }

            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></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[] { BitConverter.ToInt16(BitConverter.GetBytes(iTemp), 2),
                                         BitConverter.ToInt16(BitConverter.GetBytes(iTemp), 0) };
            return SendFc16(DeviceAddress, WriteTempAdr, (ushort)2, _val);
        }

        /// <summary>
        /// Set new humidity to chamber without starting program.
        /// </summary>
        /// <param name="iHumi">Humidity in %</param>
        /// <returns>Chamber accepts new humidity = TRUE</returns>
        public bool SetHumidity(float iHumi)
        {
            RangeLimit(ref iHumi, DEF_HUMI_MIN, DEF_HUMI_MAX);
            Chamber.SetPoints.Humidity = iHumi;
            short[] _val = new short[] { BitConverter.ToInt16(BitConverter.GetBytes(iHumi), 2),
                                         BitConverter.ToInt16(BitConverter.GetBytes(iHumi), 0) };
            return SendFc16(DeviceAddress, WriteHumAdr, (ushort)2, _val);
        }

        /// <summary>
        /// Set new humidity 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 = iHumi;
            float temp = iTemp;
            short[] _val = PrepareChamberCommandData();
            return SendFc16(DeviceAddress, WriteTempAdr, (ushort)8, _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
        /// </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;

            short[] _val = PrepareChamberCommandData();
            return SendFc16(DeviceAddress, WriteTempAdr, (ushort)8, _val);
        }

        /// <summary>
        /// Sets new heat and humidity gradients
        /// </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>
        /// Sets chamber to run mode.
        /// </summary>
        /// <returns>Chamber accepts changes = TRUE</returns>
        public bool Run()
        {
            Chamber.LastSettings_part2 = 0;
            Chamber.LastSettings_part2 |= (short)(1 << EXTtempSens_chamber1); //Turn on external sensor

            Chamber.LastSettings_part1 |= (short)(1 << RUN);
            Chamber.LastSettings_part1 |= (short)(1 << TEMPSENS);
            Chamber.LastSettings_part1 |= (short)(1 << HUMISENS);

            short[] _val = new short[] { Chamber.LastSettings_part1,
                                    Chamber.LastSettings_part2};
            return SendFc16(DeviceAddress, this.ControlAdr_part1, (ushort)2, _val);
        }

        /// <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 = PrepareChamberCommandData();
            return SendFc16(DeviceAddress, WriteTempAdr, (ushort)8, _val);
        }

        /// <summary>
        /// Sets chamber to stop mode.
        /// </summary>
        /// <returns>Chamber accepts changes = TRUE</returns>
        public bool Stop()
        {
            Chamber.LastSettings_part1 = (short)((int)Chamber.LastSettings_part1 & 0xFFFFFFFE);
            short[] _val = new short[] { Chamber.LastSettings_part1 };
            return SendFc16(DeviceAddress, this.ControlAdr_part1, (ushort)1, _val);
        }

        /// <summary>
        /// Gets time and date of last update
        /// </summary>
        /// <returns>Time and date of last update</returns>
        public DateTime LastUpdate()
        {
            return Chamber.LastUpdate;
        }

        /// <summary>
        /// Start internal thread. The thread updates actual values untill app calls UpdaterStop()
        /// </summary>
        /// <param name="iSleepInt">Updater interval</param>
        /// <returns>Updater is alive = TRUE</returns>
        public bool UpdaterStart(TimeSpan iSleepInt)
        {
            if (UpdateTh == null || !UpdateTh.IsAlive)
            {
                UpdaterDelay = iSleepInt;
                UpdaterEnabled = true;
                UpdateTh = new Thread(this.UpdaterThread) { Name = "iSTP Modbus Updater", IsBackground = true };
                UpdateTh.Start();
            }
            return UpdateTh.IsAlive;
        }

        /// <summary>
        /// Stop internal thread.
        /// </summary>
        public void UpdaterStop()
        {
            if (UpdateTh != null)
            {
                if (UpdateTh.IsAlive)
                    UpdaterEnabled = false;
                UpdateTh.Join();
                UpdateTh = null;
            }
        }

        /// <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 ("ATT DY500 C 15 ESS"); } }

        #endregion
    }
}
