using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;

using System.Windows.Forms;

namespace RS485tool
{



  public partial class MainForm : Form
  {

    class PortItem
    {
      YSerialPort p;
      public PortItem(YSerialPort p)
      {
        this.p = p;

      }

      public YSerialPort port
      { get { return this.p; } }

      public override string ToString()
      {
        return p.get_friendlyName();

      }
    }

    bool UpdateNeeded = false;
    bool disableUpdatecallback = false;

    public void deviceArrival(YModule m) { log("Device arrival: " + m.get_hardwareId()); UpdateNeeded = true; m.registerConfigChangeCallback(deviceConfigChange); }
    public void deviceRemoval(YModule m) { log("Device removal: " + m.get_hardwareId()); UpdateNeeded = true; }

    public void log(string msg) { textlogs.Text += DateTime.Now.ToString("HH:mm:ss.fff")+ " "+ msg + "\r\n"; }
    // main form initalization
    public MainForm()
    {
      InitializeComponent();
     
      log("Hello !");
      log(Program.UsingVirtualHub ? "Using Virtual hub" : "Using local USB");
      log("YAPI version is " + YAPI.GetAPIVersion());
      YAPI.RegisterLogFunction(log);
      YAPI.RegisterDeviceArrivalCallback(deviceArrival);
      YAPI.RegisterDeviceRemovalCallback(deviceRemoval);
      slaveAddr_TextChanged(null, null);
      timer1.Enabled = true;
     

    }

    // automatically called when a Yoctôpuce device configuration has changed
    void deviceConfigChange(YModule m)
    {
      if (interfaceChooser.SelectedIndex < 0) return;
      if (((PortItem)interfaceChooser.SelectedItem).port.get_module() == m)
      { // a change occured to the selected module, force an update 
        UpdateNeeded = true;

      }

    }

    // Scan for modbus slaves on the bus.
    private List<int> StartNewModbusScan()
    {
      List<int> addresses = new List<int>();
      string errmsg = "";
      List<int> empty = new List<int>();
      YSerialPort s = ((PortItem)interfaceChooser.SelectedItem).port;
      s.reset();
     
      for (int i = 1; i <= 247; i++)
      { string cmd = String.Format("{0:X02}", i)+"0300000001";  // try to read holding registers (0x03) #0000 on slave i;
        s.writeMODBUS(cmd);
        progressBar1.Value = (100 * i) / 257;
        YAPI.Sleep(150, ref errmsg);
      }
    
      YAPI.Sleep(1000, ref errmsg);
      progressBar1.Value = 0;
      while (s.read_avail() > 0)
      {
        string str = s.readLine();
        YAPI.Sleep(100, ref errmsg);
        log("read " + str);
        if ((str.Length >= 3) && str.StartsWith(":"))
          addresses.Add(Convert.ToInt32(str.Substring(1, 2), 16));
      }
      return addresses;
    }

    // find out in a serialport is alread present in the dropdown chooser
    private PortItem findItemInCombo(YSerialPort p)
    {
      for (int j = 0; j < interfaceChooser.Items.Count; j++)
        if (((PortItem)interfaceChooser.Items[j]).port == p)
          return (PortItem)interfaceChooser.Items[j];
      return null;
    }

    // search for Yocotpuce serial port with Modubus settings
    // and update the UI
    void ScanForRS485interfaces()
    {
      disableUpdatecallback = true;
      List<PortItem> l = new List<PortItem>();
      YSerialPort p = YSerialPort.FirstSerialPort();
      int currentindex = interfaceChooser.SelectedIndex;
      while (p != null)
      {
        string protocol = p.get_protocol().ToUpper();
        if (protocol.IndexOf("MODBUS") >= 0) l.Add(new PortItem(p));  // Filter RS485 interface only
        p = p.nextSerialPort();
      }
      // remove gone interfaces
      for (int i = interfaceChooser.Items.Count - 1; i >= 0; i--)
      {
        if (!l.Contains((PortItem)(interfaceChooser.Items[i]))) interfaceChooser.Items.RemoveAt(i);

      }
      // add new interfaces
      for (int i = 0; i < l.Count; i++)
      {
        if (findItemInCombo(l[i].port) == null) interfaceChooser.Items.Add(l[i]);
      }

      disableUpdatecallback = false;
      if ((interfaceChooser.SelectedIndex < 0) && (interfaceChooser.Items.Count > 0))
        interfaceChooser.SelectedIndex = 0;

      if (interfaceChooser.SelectedIndex < 0) disableConfigDropDrowns();

    }
    // Disable all Dropdown
    private void disableConfigDropDrowns()
    {
      Protocol.Enabled = false;
      Speed.Enabled = false;
      Parity.Enabled = false;
      Protocol.SelectedIndex = -1;
      Speed.SelectedIndex = -1;
      Parity.SelectedIndex = -1;
    }

    // automatic refresh
    int n = 0;
    private void timer1_Tick(object sender, EventArgs e)
    {
      String errmsg = "";
      YAPI.HandleEvents(ref errmsg);
      if ((n++) % 20 == 0) YAPI.UpdateDeviceList(ref errmsg);
      if (UpdateNeeded) { ScanForRS485interfaces(); UpdateNeeded = false; }

    }

    // update configuration drop down when current choosen interface has changed
    private void interfaceChooser_SelectedIndexChanged(object sender, EventArgs e)
    {
      StopScan = true;
      if (interfaceChooser.SelectedIndex < 0)
      {
        disableConfigDropDrowns();
        return;
      }
      Protocol.Enabled = true;
      Speed.Enabled = true;
      Parity.Enabled = true;
      YSerialPort p = ((PortItem)interfaceChooser.SelectedItem).port;
      if (!p.isOnline()) return;

      String sProtocol = p.get_protocol();
      Boolean found = false;
      for (int i = 0; i < Protocol.Items.Count && !found; i++)
        if (Protocol.Items[i].ToString() == sProtocol) { Protocol.SelectedIndex = i; found = true; }
      if (!found) Protocol.SelectedIndex = Protocol.Items.Add(sProtocol);

      string mode = p.get_serialMode();
      int n = mode.IndexOf(',');
      if (n > 0)
      {
        string sSpeed = mode.Substring(0, n);
        string sParity = mode.Substring(n + 1);
        found = false;
        for (int i = 0; i < Speed.Items.Count && !found; i++)
          if (Speed.Items[i].ToString() == sSpeed) { Speed.SelectedIndex = i; found = true; }
        if (!found) Speed.SelectedIndex = Speed.Items.Add(sSpeed);
        found = false;
        for (int i = 0; i < Parity.Items.Count && !found; i++)
          if (Parity.Items[i].ToString() == sParity) { Parity.SelectedIndex = i; found = true; }
        if (!found) Parity.SelectedIndex = Parity.Items.Add(sParity);
      }

      disableUpdatecallback = false;
    }

    // set device MODBUS protocol
    private void Protocol_SelectedIndexChanged(object sender, EventArgs e)
    {
      StopScan = true;
      if (disableUpdatecallback) return;
      if (interfaceChooser.SelectedIndex < 0) return;
      YSerialPort p = ((PortItem)interfaceChooser.SelectedItem).port;
      if (!p.isOnline()) return;
      String sProtocol = Protocol.SelectedItem.ToString();
      p.set_protocol(sProtocol);
    }

    // sevice device MODBUS speed and parity
    private void Speed_SelectedIndexChanged(object sender, EventArgs e)
    {
      StopScan = true;
      if (disableUpdatecallback) return;
      if (Speed.SelectedIndex < 0) return;
      if (Parity.SelectedIndex < 0) return;
      YSerialPort p = ((PortItem)interfaceChooser.SelectedItem).port;
      if (!p.isOnline()) return;
      String serialMode = Speed.SelectedItem.ToString() + "," + Parity.SelectedItem.ToString();
      p.set_serialMode(serialMode);
    }

    // someone has clicked on the scan for slaves button 
    private void scanForSlave_btn_Click(object sender, EventArgs e)
    {
      scanlabel.Text = "Scanning, please wait...";
      Application.DoEvents();

      List<int> addr = StartNewModbusScan();
      if (addr.Count == 0) scanlabel.Text = "Nothing found (doesn't mean there is nothing though)";
      else if (addr.Count == 1)
      {
        scanlabel.Text = "One slave found at address " + addr[0].ToString();
        if (slaveAddr.Text == "") slaveAddr.Text = addr[0].ToString();
      }
      else
      {
        string msg = addr.Count.ToString() + " slaves found at addresses 0x" + addr[0].ToString();
        for (int i = 1; i < addr.Count; i++) msg += ", 0x" + String.Format("{0:X02}", addr[i].ToString());

      }
    }

    
    private void readCoil_btn_Click(object sender, EventArgs e)
    {
      int address = inputValue(slaveAddr, 1, 247);
      int coil = inputValue(CoilNumber, 0, 9999);

      string cmd = "calling YSerialPort.modbusReadBits(" + address.ToString() + "," + coil.ToString() + ",1) ";
      try
      { List<int> values = ((PortItem)interfaceChooser.SelectedItem).port.modbusReadBits(address, coil, 1);
        coilValue.Text = values[0].ToString();
        ReadCoilResult.Text = "Read operation sucessfull";
        cmd +=" SUCCESS ("+ values[0].ToString()+")";
      }
      catch (Exception ex)
      { ReadCoilResult.Text = "Read operation failed (" + ex.Message + ")";
        cmd += " FAILED (" + ex.Message + ")";
      }
      log(cmd);

    }

    private void writeCoil_btn_Click(object sender, EventArgs e)
    { int address = inputValue(slaveAddr, 1, 247);
      int coildindex = inputValue(CoilNumber, 0, 9999);
      int value = inputValue(coilWriteValue, 0, 1);
      Boolean ok = false;
      string cmd = "calling YSerialPort.modbusWriteBit(" + address.ToString() + "," + coildindex.ToString() + "," + value.ToString() + ") ";
      try
      {
        ((PortItem)interfaceChooser.SelectedItem).port.modbusWriteBit(address, coildindex, value );
       
        ok = true;
        ReadCoilResult.Text = "Write operation sucessfull"; 
        cmd += " SUCCESS";
      }
      catch (Exception ex)
      {
        ReadCoilResult.Text = "Write operation failed (" + ex.Message + ")";
        cmd += " FAILED (" + ex.Message + ")";
      }
      log(cmd);
      if (ok) readCoil_btn_Click(null, null);
    }

    private void readInputBits_btn_Click(object sender, EventArgs e)
    {
      int address = inputValue(slaveAddr, 1, 247);
      int inputBit = inputValue(inputbitsIndex, 0, 9999);
      string cmd = "calling YSerialPort.modbusReadInputBits(" + address.ToString() + "," + inputBit.ToString() + ",1) ";
      try
      {
        List<int> values = ((PortItem)interfaceChooser.SelectedItem).port.modbusReadInputBits(address, inputBit, 1);
        inputBitsValue.Text = values[0].ToString();
        ReadInputBitResult.Text = "Read operation sucessfull";
        cmd += " SUCCESS (" + values[0].ToString() + ")";
      }
      catch (Exception ex)
      { ReadInputBitResult.Text = "Read operation failed (" + ex.Message + ")";
        cmd += " FAILED (" + ex.Message + ")";
       
      }
      log(cmd);
    }

    private void readInputRegisters_btn_Click(object sender, EventArgs e)
    {
      int address = inputValue(slaveAddr, 1, 247);
      int inputregisters = inputValue(InputRegistersIndex, 0, 9999);
      string cmd = "calling YSerialPort.modbusReadInputRegisters(" + address.ToString() + "," + inputregisters.ToString() + ",1) ";
     
      try
      {
        List<int> values = ((PortItem)interfaceChooser.SelectedItem).port.modbusReadInputRegisters(address, inputregisters, 1);
        inputRegistersValues.Text = values[0].ToString();
        ReadInputRegisterResult.Text = "Read operation sucessfull";
        cmd += " SUCCESS (" + values[0].ToString() + ")";
      }
      catch (Exception ex)
      { ReadInputRegisterResult.Text = "Read operation failed (" + ex.Message + ")";
        cmd += " FAILED (" + ex.Message + ")";
      }
      log(cmd);

    }

    private void readHoldingRegisters_btn_Click(object sender, EventArgs e)
    {
      int address = inputValue(slaveAddr, 1, 247);
      int HoldingRegisters = inputValue(HoldingRegistersIndex, 0, 9999);
      string cmd = "calling YSerialPort.modbusReadRegisters(" + address.ToString() + "," + HoldingRegisters.ToString() + ",1) ";
      try
      {
        List<int> values = ((PortItem)interfaceChooser.SelectedItem).port.modbusReadRegisters(address, HoldingRegisters, 1);
        HoldingRegistersReadValue.Text = values[0].ToString();
        HoldingRegistersOperationResult.Text = "Read operation sucessfull";
        cmd += " SUCCESS (" + values[0].ToString() + ")";
      }
      catch (Exception ex)
      {
        HoldingRegistersOperationResult.Text = "Read operation failed (" + ex.Message + ")";
        cmd += " FAILED (" + ex.Message + ")";
      }
      log(cmd);
    }

    private void writeHoldingRegisters_btn_Click(object sender, EventArgs e)
    {

      int address = inputValue(slaveAddr, 1, 247);
      int HoldingRegisters = inputValue(HoldingRegistersIndex, 0, 9999);
      int value = inputValue(HoldingRegistersWriteValue, 0, 65535);
      string cmd = "calling YSerialPort.modbusWriteRegisters(" + address.ToString() +"," + HoldingRegisters .ToString()+  ", new List<int>{" + value + "}) ";
      Boolean ok = false;
      try
      {
        ((PortItem)interfaceChooser.SelectedItem).port.modbusWriteRegisters(address, HoldingRegisters, new List<int> { value });
        ok = true;
        HoldingRegistersOperationResult.Text = "Write operation sucessfull";
        cmd += " SUCCESS";
      }
      catch (Exception ex)
      { HoldingRegistersOperationResult.Text = "Write operation failed (" + ex.Message + ")";
        cmd += " FAILED (" + ex.Message + ")";
      }
      log(cmd);
      if (ok) readHoldingRegisters_btn_Click(null, null);
    }

    // Convert a textbox contents to ant integer and checks if this integer
    // is within a specified range. Accepts both decimal and hexadecimal syntax
    private int inputValue(TextBox source, int minValue, int maxValue)
    {
      Boolean ok = true;
      int value = -1;
      String v = source.Text;
      if ((v.Length > 2) && (v.Substring(0, 2).ToUpper() == "0X"))
      {
        try
        {
          value = int.Parse(v.Substring(2), System.Globalization.NumberStyles.HexNumber);

        }
        catch (Exception ) { ok = false; }
      }
      else
      {
        try
        { value = int.Parse(v); }
        catch (Exception ) { ok = false; }
      }
      if ((value < minValue) || (value > maxValue)) { ok = false; value = -1; }

      source.BackColor = ok ? SystemColors.Window : Color.Pink;
      return value;
    }

    // automatically enable / disable coils controls
    private void checkCoilControls(Boolean overideToFalse)
    {
      Boolean validIndex = (inputValue(CoilNumber, 0, 9999) >= 0);
      CoilNumber.Enabled = !overideToFalse;
      StartCoilsScan_btn.Enabled = !overideToFalse;
      readCoil_btn.Enabled = (!overideToFalse) && (validIndex);

      Boolean validValue = (inputValue(coilWriteValue, 0, 1) >= 0);
      coilWriteValue.Enabled = !overideToFalse && validIndex;
      writeCoil2_btn.Enabled = (!overideToFalse) && (validIndex) && validValue;
      StartCoilsScan_btn.Enabled = (!overideToFalse) && (validIndex);


    }
    // automatically enable / disable input bits controls
    private void checkInputBitsControls(Boolean overideToFalse)
    {
      Boolean validInput = (inputValue(inputbitsIndex, 0, 9999) >= 0);
      inputbitsIndex.Enabled = !overideToFalse;
      readInputBit.Enabled = (!overideToFalse) && (validInput);
      StartInputBitsScan_btn.Enabled = !overideToFalse;
    }

    // automatically enable / disable input register  controls
    private void checkInputRegistersControls(Boolean overideToFalse)
    {
      Boolean validInput = (inputValue(inputbitsIndex, 0, 9999) >= 0);
      inputbitsIndex.Enabled = !overideToFalse;
      readInputRegisters_btn.Enabled = (!overideToFalse) && (validInput);
      StartInputRegistersScan_btn.Enabled = !overideToFalse;
    }

    // automatically enable / disable holding register controls

    private void checkHolderRegistersControls(Boolean overideToFalse)
    {
      Boolean validIndex = (inputValue(HoldingRegistersIndex, 0, 9999) >= 0);
      Boolean validValue = (inputValue(HoldingRegistersWriteValue, 0, 65535) >= 0);
      Boolean startvalueValue = (inputValue(HoldingRegistersScanFrom, 0, 65535) >= 0);
      HoldingRegistersIndex.Enabled = !overideToFalse;
      HoldingRegistersWriteValue.Enabled = !overideToFalse && validIndex;
      readHoldingRegisters_btn.Enabled = (!overideToFalse) && (validIndex);
      writeHoldingRegisters_btn.Enabled = (!overideToFalse) && (validIndex) && validValue;
      HoldingRegistersScanFrom.Enabled = !overideToFalse && startvalueValue;
      StartHoldingRegistersScan_btn.Enabled = !overideToFalse;
    }

    // someone chenged the slave's address parameter, enable/disable
    // the ui accordindly
    private void slaveAddr_TextChanged(object sender, EventArgs e)
    { bool ok = (inputValue(slaveAddr, 1, 247) >= 0);
      StopScan = true;
      slaveAddr.BackColor = ok ? SystemColors.Window : Color.Pink;
      checkCoilControls(!ok);
      checkInputBitsControls(!ok);
      checkInputRegistersControls(!ok);
      checkHolderRegistersControls(!ok);
    }  

    private void CoilNumber_TextChanged(object sender, EventArgs e)
    {
      checkCoilControls(false);
    }

    private void inputbitsIndex_TextChanged(object sender, EventArgs e)
    {
      checkInputBitsControls(false);
    }

    private void InputRegistersIndex_TextChanged(object sender, EventArgs e)
    {
      checkInputRegistersControls(false);
    }

    private void HoldingRegistersIndex_TextChanged(object sender, EventArgs e)
    {
      checkHolderRegistersControls(false);

    }

    private void HoldingRegistersWriteValue_TextChanged(object sender, EventArgs e)
    {
      checkHolderRegistersControls(false);
    }

    bool StopScan = true;

    private enum scanTypes  { COILS, INPUTBITS, INPUTREGISTERS, HOLDINGREGISTERS };
    // start a scan for coils contents
    private void StartCoilsScan_btn_Click(object sender, EventArgs e)
    {
      GenericScan(scanTypes.COILS, CoilValuesGrid, coilScanProgess, coilScanMessage, StartCoilsScan_btn,null);
    }
    // start a scan for holding registers contents
    private void StartHoldingRegistersScan_btn_Click(object sender, EventArgs e)
    {
      GenericScan(scanTypes.HOLDINGREGISTERS, HoldingRegistersValuesGrid, HoldingRegistersScanProgess, HoldingRegistersMessage, StartHoldingRegistersScan_btn, HoldingRegistersScanFrom);
    }
    // start a scan for inputbit  contents
    private void StartInputBitsScan_btn_Click(object sender, EventArgs e)
    {
      GenericScan (scanTypes.INPUTBITS, InputBitsValuesGrid,  inputBitsScanProgess, InputBitsScanMessage, StartInputBitsScan_btn,null);
    }
    // start a scan for input registers  contents
    private void StartInputRegistersScan_btn_Click(object sender, EventArgs e)
    {
      GenericScan(scanTypes.INPUTREGISTERS, InputRegistersValuesGrid, InputRegistersScanProgess, InputRegistersScanMessage, StartInputRegistersScan_btn, null);
    }

    // Generic scan function for contents of coils, holding registers , input bits, input registers .

    private void GenericScan(scanTypes scantype, DataGridView resultGrid, ProgressBar scanProgess, Label ScanMsg, Button ScanButton, TextBox startFrom )
    {
      
      if  (!StopScan)
      {
        StopScan = true;
        return;
      }
      int address = inputValue(slaveAddr, 1, 247);

      int i = startFrom!=null ? inputValue(startFrom, 0, 65535) : 0;

      string Errmsg="";
      resultGrid.Rows.Clear();
      StopScan = false;
      
      int count = 0;
      ScanButton.Text = "Stop";
      scanProgess.Visible = true;
      String item = "";
      while ((i < 9999) && (!StopScan))
      {
       
        try
        {
          List<int> values =null;
          ScanMsg.Text = "Scanning"+item+ " " + i.ToString();
          
          switch (scantype)
          { case scanTypes.COILS:
              item = "coil";
              values = ((PortItem)interfaceChooser.SelectedItem).port.modbusReadBits(address, i, 1); break;
            case scanTypes.INPUTBITS: item = "input bit";
              values = ((PortItem)interfaceChooser.SelectedItem).port.modbusReadInputBits(address, i, 1);
              break; 
            case scanTypes.INPUTREGISTERS:
              item = "input register";
              values = ((PortItem)interfaceChooser.SelectedItem).port.modbusReadInputRegisters(address, i, 1);
              break;
            case scanTypes.HOLDINGREGISTERS:
              item = "holding register";
              values = ((PortItem)interfaceChooser.SelectedItem).port.modbusReadRegisters(address, i, 1);
              break; 
          }
      
          int index = resultGrid.Rows.Add();
          resultGrid.Rows[index].Cells[0].Value = i.ToString();
          resultGrid.Rows[index].Cells[1].Value = values[0].ToString();

          resultGrid.Rows[index].Cells[2].Value = i.ToString("X4");
          resultGrid.Rows[index].Cells[3].Value = values[0].ToString("X4");
          count++;

        }
        catch (Exception e)
        { String msg = e.Message; }
        i++;
        scanProgess.Value = (100 * i / 9999);
        YAPI.Sleep(1, ref Errmsg);
        Application.DoEvents();
      }
      scanProgess.Visible = false;
      String line = StopScan ? "Scan stopped, " : "Scan done, ";
      if (count == 0) line += "no ";
      else if (count == 1) line += "one ";
      else line += count.ToString() + " ";
      line += item + ((count > 1) ? "s" : "" )+" found";
      if (StopScan) line += " so far";
      line += ".";
      ScanMsg.Text = line;

    ScanButton.Text = "Start";
    }
    // opens a web browser on Yoctopuce's web site.
    private void label21_Click(object sender, EventArgs e)
    {
      System.Diagnostics.Process.Start("http://www.yoctopuce.com/EN/article/a-quick-tutorial-on-rs485-and-modbus");
    }

   
  }

  
  
}



