using System;
using System.Collections.Generic;
using System.Text;
using System.IO.Ports;
using Modbus;
using Modbus.Device;
using Modbus.Utility;
using System.Net.Sockets;
using System.Net;

namespace MTCPPowerMeterDemoCode
{
    /// </summary>
    /// The "Program" Class will teach you that how to read/write power meter's holding registers, read power meter's Input Registers and so on.
    /// The "ConnectToPowerMeter" function show how to connect to power meter.
    /// The "DisconnectPowerMeter" function show how to disconnect power meter.
    /// The "ReadValueFromPowerMeter" function will show how to read input registers from power meter.
    /// The "WriteDefaultParameterValueToPowerMeter" function show how to write holding registers to power meter.
    /// </summary>
    class Program
    {
        private static ExampleSetting mySetting;
        private static TcpClient tcpClient;
        private static ModbusIpMaster tcpMaster;
        private static int channelNumber;

        /// <summary>
        /// "ExampleSetting" Structure contains connection parameters of power meter.The parameters are set by user interface or console inpute.
        /// In this programm, ExampleSetting member's values are either index or amount of units to set power meter parameters. 
        /// They are not actually the real values of Modbus Holding Registers which specified in hardware manual.
        /// </summary>
        public struct ExampleSetting
        {
            //public string PortName;
            //public int BaudRate;
            //public int StopBit;
            public byte Modbus_slave_ID;
            public int Timeout;
            public int PT;
            public int CT;
            public const int Meter_Ratio = 500; // Truly value: 500
            public int Port;
            public IPAddress IPAddr;
        }

        static void Main(string[] args)
        {
            // Get a list of available serial port names,then select a port number to communicate.

            //string[] ports = SerialPort.GetPortNames();
            //if (ports.Length == 0)
            //{
            //    Console.WriteLine("No available COM port.");
            //    Console.ReadLine();
            //    return;
            //}

            //string portnums = string.Empty;
            //for (int i = 0; i < ports.Length; i++)
            //{ 
            //    portnums += string.Format("[{0}]: {1}, ",i,ports[i]);
            //    if ((i + 1) % 4 == 0 || (i+1) == ports.Length)
            //    { portnums += "\n"; }
            //}
            //Console.WriteLine("Available COM port number: \n" + portnums );
            //Console.Write(string.Format("Selected index ( 0 ~ {0} ): ",ports.Length-1));

            // Get port's index which user input 
            //int selectedPortIndex = 0;
            //bool isright = false;
            //while (!isright)
            //{ 
            //    int cursorleft = Console.CursorLeft;
            //    int cursortop = Console.CursorTop;

            //    if (int.TryParse(Console.ReadLine(), out selectedPortIndex))
            //    {
            //        if (selectedPortIndex < ports.Length)
            //        {
            //            isright = true;
            //       }
            //    }
            //    if (!isright)
            //        Console.SetCursorPosition(cursorleft, cursortop);
            //}



            Console.WriteLine("Device IPv4 Address: ");

            int cursorleft = Console.CursorLeft;
            int cursortop = Console.CursorTop;
            IPAddress ipAddr;
            
            while(!IPAddress.TryParse(Console.ReadLine(), out ipAddr))
            {
                Console.SetCursorPosition(cursorleft, cursortop);
            }

            Console.WriteLine("");
            Console.WriteLine("Select Device: ");
            Console.WriteLine("[1]: PM-213x / PM3114 / PM-3133");
            Console.WriteLine("[2]: PM3112");           
            while(true)
            {
                string ans = Console.ReadLine();
                int ansindex = 0;
                if (int.TryParse(ans, out ansindex))
                {
                    if (ansindex == 1 || ansindex == 2)
                    {
                        if (ansindex == 1)
                        { channelNumber = 4; }
                        else
                        { channelNumber = 2; }
                        break; 
                    }
                }
            }


            // Connect to power meter. If connecting is successful, then 
            // read or write the data of power meter.
            if ( ConnectToPowerMeter(ipAddr) )
            {
                string actionString = 
@"**********************************************************************
Execute:  
    [0] : Programe Exit. 
    [1] : Read value from power meter
    [2] : Write default parameter value to power meter
Input index number: ";

                // Get index number which user input 
                bool isloop = true;
                while (isloop)
                {
                    Console.WriteLine();
                    Console.Write(actionString);
                    // Get index number which user input 
                    int actionIndex = 0;
                    bool isOK= false;
                    while (!isOK)
                    {
                        if (int.TryParse(Console.ReadLine(), out actionIndex))
                        {
                            if (actionIndex < 3)
                            {
                                isOK = true;
                            }
                        }
                        if (!isOK)
                        {
                            Console.SetCursorPosition(cursorleft, cursortop);
                        }
                    }
                    Console.WriteLine();

                    // Execute function by selected index
                    switch (actionIndex)
                    {
                        case 0:
                            DisconnectPowerMeter();
                            isloop = false;
                            break;
                        case 1:
                            ReadValueFromPowerMeter();
                            break;
                        case 2:
                            WriteDefaultParameterValueToPowerMeter();
                            break;
                    }
                }
            }
        }


        /// <summary>
        /// This function show how to connect to power meter.
        /// </summary>
        /// <param name="portName"></param>
        /// <returns></returns>
        private static bool ConnectToPowerMeter(IPAddress ipAddr)
        {
            // Before connecting,
            // we should initialize power meter's connection parameters first.
            // You can change parameter value by yourself.
            mySetting = new ExampleSetting();

            mySetting.Modbus_slave_ID = 1;
            mySetting.PT = 1;
            mySetting.CT = 1;
            mySetting.Port = 502;
            mySetting.Timeout = 1500;
            mySetting.IPAddr = ipAddr;

            Console.WriteLine("Start to connect ICP DAS Modbus TCP Power Meter.");



            // new an TCP Client, and to create an instance for modbus communication.
            try
            {
                // Create a client instance for TCP communication
                tcpClient = new TcpClient(mySetting.IPAddr.ToString(), mySetting.Port);

                // After COM opened, 
                // to create modbus master instance which is used to communicate a power meter.
                // Bind modbus master instance to the TCPClient instance.
                tcpMaster = ModbusIpMaster.CreateIp(tcpClient);
                tcpMaster.Transport.ReadTimeout = mySetting.Timeout;
                tcpMaster.Transport.WriteTimeout = mySetting.Timeout;                
            }
            catch (Exception err)
            {
                Console.WriteLine(err.Message);
                //Console.WriteLine(err.StackTrace);
                Console.Read();
                return false;
            }
            Console.WriteLine("Open ICP DAS Modbus TCP Power Meter Success!!");
            return true;
        }

        /// <summary>
        /// This function show how to disconnect power meter.
        /// </summary>
        private static void DisconnectPowerMeter()
        {
            if (tcpClient != null)
            {
                tcpClient.Close();
                tcpClient = null;
                tcpMaster.Dispose();
            }
        }

        /// <summary>
        /// This function show how to read input registers from power meter.
        /// </summary>
        private static void ReadValueFromPowerMeter()
        {
            // Read Input Registers (Fun04).
            // Power meter will return 36 Power Parameters (Voltage, Current, Power, Energy...etc) 
            ushort startAddress = 0x1100;  // 4352;
            ushort numRegister =(ushort)( channelNumber * 18);//72; // 36*2                   

            ushort[] PowerParameters = new ushort[72];

            if (tcpClient.Connected)
            {

                try
                {
                    // Set parameters of function to get all input register values in power meter.
                    Console.WriteLine("Modbus_slave_ID: {0}, startAddress: {1}, numRegister: {2}", mySetting.Modbus_slave_ID, startAddress, numRegister);
                    PowerParameters = tcpMaster.ReadInputRegisters(mySetting.Modbus_slave_ID, startAddress, numRegister);

                    // Convert two UInt16 values into a IEEE 32 floating point format value.
                    float[] fvalue = new float[36];
                    for (int i = 0; i < (numRegister / 2); i++)
                    {
                        fvalue[i] = ModbusUtility.GetSingle(PowerParameters[2 * i + 1], PowerParameters[2 * i]);
                    }

                    //Output the result.
                    string titleformate = "{0,13}   {1,13}   {2,13}   {3,13}   {4,13}";
                    string dataformate = "{0,13}   {1,13:f3}   {2,13:f3}   {3,13:f3}   {4,13:f3}";
                    if (channelNumber == 2)
                        dataformate = "{0,13}   {1,13:f3}   {2,13:f3}";
                    //Console.WriteLine(string.Format(titleformate, "", "<<Channel 1>>", "<<Channel 2>>", "<<Channel 3>>", "<<Channel 4>>"));
                    Console.WriteLine();
                    Console.WriteLine(string.Format(dataformate, "[Voltage]", fvalue[0], fvalue[9], fvalue[18], fvalue[27]));
                    Console.WriteLine(string.Format(dataformate, "[Current]", fvalue[1], fvalue[10], fvalue[19], fvalue[28]));
                    Console.WriteLine(string.Format(dataformate, "[kW]", fvalue[2], fvalue[11], fvalue[20], fvalue[29]));
                    Console.WriteLine(string.Format(dataformate, "[kVar]", fvalue[3], fvalue[12], fvalue[21], fvalue[30]));
                    Console.WriteLine(string.Format(dataformate, "[kVA]", fvalue[4], fvalue[13], fvalue[22], fvalue[31]));
                    Console.WriteLine(string.Format(dataformate, "[PF]", fvalue[5], fvalue[14], fvalue[23], fvalue[32]));
                    Console.WriteLine(string.Format(dataformate, "[kWh]", fvalue[6], fvalue[15], fvalue[24], fvalue[33]));
                    Console.WriteLine(string.Format(dataformate, "[kvarh]", fvalue[7], fvalue[16], fvalue[25], fvalue[34]));
                    Console.WriteLine(string.Format(dataformate, "[kVah]", fvalue[8], fvalue[17], fvalue[26], fvalue[35]));

                }
                catch (Exception err)
                {
                    Console.WriteLine(err.Message);
                    //Console.WriteLine(err.StackTrace);
                }
            }
        }

        /// <summary>
        /// This function show how to write and read holding registers 
        /// which store power meter's connection parameters and meter parameters.
        /// In this function, you will know how to convert modbus holding regester
        /// values to UI value. 
        /// </summary>
        private static void WriteDefaultParameterValueToPowerMeter()
        {
            ushort startAddress = 0x1003; // 4096          
            ushort numRegister = 2;         // only PT, CT
            ushort[] PowerParameters = new ushort[2];
            ushort[] feedbackParams = new ushort[2];

            if (tcpClient.Connected)
            {
                try
                {
                    // Default power meter parameters.
                    // User must realize that how to convert the UI value to Modbus register value.
                    // Modbus register value ===> UI value        
                    PowerParameters[0] = 1 * 100;  // PT ratio: 1
                    PowerParameters[1] = 1;    // CT ratio: 1

                    // Write data to power meter's holding registers by modbus protocol.
                    tcpMaster.WriteMultipleRegisters(mySetting.Modbus_slave_ID, startAddress, PowerParameters);

                    // After write new baudrate data to holding registers,
                    // it must reassign serialport's baudrate value for next connection.


                    // Read data from power meter's holding registers by modbus protocol.
                    // User must realize how to convert modbus register value to UI value.
                    feedbackParams = tcpMaster.ReadHoldingRegisters(mySetting.Modbus_slave_ID, startAddress, numRegister);
                    Console.WriteLine("The parameter values read back after writing: ");
                    Console.WriteLine(string.Format("   [PT ratio]: {0}", PowerParameters[0] * 0.01));
                    Console.WriteLine(string.Format("   [CT ratio]: {0}", PowerParameters[1]));
                }
                catch (Exception err)
                {
                    Console.WriteLine(err.Message);
                    //Console.WriteLine(err.StackTrace);
                }
            }
        }
    }
}
