03_仿Modbus工具案例

野码發表於2024-05-07
花了6個小時邊學邊寫的Modbus通訊案例,通訊方式包括RTU,ASCII,TCP,UTP。
案例圖:

using Modbus.Data;
using Modbus.Device;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO.Ports;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using static System.Windows.Forms.AxHost;

namespace MyModbusPS
{
    public partial class Form1 : Form
    {
        private ModbusMaster master;//主站
        private ModbusSlave slave;//從站

        public delegate void Logdelegate(string log);
        private Logdelegate Log;


        public Form1()
        {
            InitializeComponent();

            InotMasterUI();
            InotSlaveUI();

            Log = new Logdelegate((s) =>
            {
                string time = DateTime.Now.ToString("HH:mm:ss");
                Slave_RZ.AppendText($"{time}:{s} \r\n");
                Slave_RZ.SelectionStart = Slave_RZ.Text.Length;
                Slave_RZ.ScrollToCaret();
            });
        }

        #region 初始化主站UI資料
        /// <summary>
        /// 初始化主站UI資料
        /// </summary>
        private void InotMasterUI()
        {
            Master_TXFS.Items.Add("SerialIPort");
            Master_TXFS.Items.Add("TCP");
            Master_TXFS.Items.Add("UDP");
            Master_TXFS.SelectedIndex = 0;
            Master_IP.Text = "COM1";
            Master_DK.Text = "";
            Master_RTU.Checked = true;

            Master_dataGridView.AllowUserToAddRows = false;
            Master_dataGridView.RowHeadersWidth = 60;
            Master_dataGridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;

            Master_dataGridView.AllowUserToOrderColumns = false;
            //Master_dataGridView.Columns[1].SortMode=DataGridViewColumnSortMode.NotSortable;

            Master_dataGridView.Columns.Add("Alias", "Alias");
            Master_dataGridView.Columns.Add("00000", "00000");

            for (int i = 0; i < 10; i++)
            {
                Master_dataGridView.Rows.Add();
                Master_dataGridView.Rows[i].HeaderCell.Value = i.ToString();
            }

            //從站地址
            Master_CZDZ.Text = "1";
        }
        #endregion

        #region 主站通訊方式選擇事件
        /// <summary>
        /// 主站通訊方式選擇事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Master_TXFS_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (Master_TXFS.SelectedIndex == 0)
            {
                Master_IP.Text = "COM1";
                Master_DK.Text = "";
                Master_RTU.Visible = true;
                Master_ASCII.Visible = true;
                Master_RTU.Checked = true;
            }
            else
            {
                Master_IP.Text = "127.0.0.1";
                Master_DK.Text = "8080";
                Master_RTU.Visible = false;
                Master_ASCII.Visible = false;
                Master_RTU.Checked = false;
                Master_ASCII.Checked = false;
            }
        }
        #endregion

        #region 連線
        /// <summary>
        /// 連線
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Master_LJ_Click(object sender, EventArgs e)
        {
            if (Master_LJ.Text == "連線")
            {
                StartMaster();

            }
            else
            {
                StopMaster();
            }
        }

        /// <summary>
        /// 連線
        /// </summary>
        private void StartMaster()
        {
            try
            {
                if (Master_TXFS.SelectedIndex == 0)//開啟RTU或Ascii
                {
                    SerialPort port = new SerialPort();
                    port.PortName = Master_IP.Text;//通訊方式
                    port.BaudRate = 9600;//波特率
                    port.DataBits = 8;//資料位
                    port.Parity = Parity.Even;//奇偶校驗
                    port.StopBits = StopBits.One;//停止位
                    port.Open();
                    if (Master_RTU.Checked)
                    {
                        master = ModbusSerialMaster.CreateRtu(port);
                    }
                    else if (Master_ASCII.Checked)
                    {
                        master = ModbusSerialMaster.CreateAscii(port);
                    }
                }
                else if (Master_TXFS.SelectedIndex == 1)//開啟TCP
                {
                    string ip = Master_IP.Text;
                    ushort port = ushort.Parse(Master_DK.Text);
                    TcpClient tcpClient = new TcpClient(ip, port);

                    master = ModbusIpMaster.CreateIp(tcpClient);
                }
                else if (Master_TXFS.SelectedIndex == 2)//開啟UTP
                {
                    string ip = Master_IP.Text;
                    ushort port = ushort.Parse(Master_DK.Text);
                    UdpClient udpClient = new UdpClient(ip, port);
                    master = ModbusIpMaster.CreateIp(udpClient);
                }
                master.Transport.ReadTimeout = 100;//讀取資料超時100ms
                master.Transport.WriteTimeout = 100;//寫入資料超時100ms
                master.Transport.Retries = 3;//重試次數
                master.Transport.WaitToRetryMilliseconds = 10;//重試間隔
                Master_LJ.Text = "停止";
            }
            catch (Exception e)
            {
                MessageBox.Show($"連線失敗:{e.Message}");
            }

        }

        private void StopMaster()
        {

            master.Dispose();
            master = null;
            Master_LJ.Text = "連線";
        }

        #endregion

        #region 03讀一個或多個報持暫存器
        /// <summary>
        /// 03讀一個或多個報持暫存器
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Master_03_Click(object sender, EventArgs e)
        {
            if (master == null) return;
            try
            {
                byte slaveld = byte.Parse(Master_CZDZ.Text);//從站位置 
                ushort StartAddress = 0; //從0開始讀
                ushort EndAddress = 10; //讀10個
                ushort[] data = master.ReadHoldingRegisters(slaveld, StartAddress, EndAddress);
                for (int i = 0; i < 10; i++)
                {
                    Master_dataGridView.Rows[i].Cells[1].Value = data[i];
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
        #endregion

        #region 06寫單個保持暫存器
        /// <summary>
        /// 06寫單個保持暫存器
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Master_06_Click(object sender, EventArgs e)
        {
            if (master == null) return;
            try
            {
                byte slaveld = byte.Parse(Master_CZDZ.Text);//從站位置 
                ushort Address = 9;//寫入值的地址
                ushort value = 99;//寫入的值(寫死了)
                master.WriteSingleRegister(slaveld, Address, value);//寫單個保持暫存器
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
        #endregion

        #region 16寫多個保持暫存器
        /// <summary>
        /// 16寫多個保持暫存器
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Master_16_Click(object sender, EventArgs e)
        {
            if (master == null) return;
            try
            {
                byte slaveld = byte.Parse(Master_CZDZ.Text);//從站位置 
                ushort Address = 1;//起始位置
                ushort[] value = { 11, 22, 33 };//寫入的值
                master.WriteMultipleRegisters(slaveld, Address, value);//寫多個保持暫存器
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
        #endregion

        #region 01讀多個線圈狀態
        /// <summary>
        /// 01讀多個線圈狀態
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Master_01_Click(object sender, EventArgs e)
        {
            if (master == null) return;
            try
            {
                byte slaveld = byte.Parse(Master_CZDZ.Text);//從站位置 
                ushort Address = 0;//起始位置
                ushort value = 10;//讀取的值
                bool[] data = master.ReadCoils(slaveld, Address, value);//讀多個線圈狀態
                for (int i = 0; i < data.Length; i++)
                {
                    Master_dataGridView.Rows[i].Cells[1].Value = data[i] == true ? 1 : 0;
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
        #endregion

        #region 05寫單個線圈狀態
        /// <summary>
        /// 05寫單個線圈狀態
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, EventArgs e)
        {
            if (master == null) return;
            try
            {
                byte slaveld = byte.Parse(Master_CZDZ.Text);//從站位置 
                ushort Address = 1;//寫入值的地址
                bool value = true;//寫入的值
                master.WriteSingleCoil(slaveld, Address, value);//寫單個線圈狀態
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
        #endregion

        #region 15寫多個線圈狀態
        /// <summary>
        /// 15寫多個線圈狀態
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Master_15_Click(object sender, EventArgs e)
        {
            if (master == null) return;
            try
            {
                byte slaveld = byte.Parse(Master_CZDZ.Text);//從站位置 
                ushort Address = 1;//起始位置
                bool[] value = { true, true, true };//寫入的值
                master.WriteMultipleCoils(slaveld, Address, value);//寫多個線圈狀態
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
        #endregion

        #region 初始化從站UI資料
        /// <summary>
        /// 初始化主站UI資料
        /// </summary>
        private void InotSlaveUI()
        {
            Slave_TXFS.Items.Add("SerialIPort");
            Slave_TXFS.Items.Add("TCP");
            Slave_TXFS.Items.Add("UDP");
            Slave_TXFS.SelectedIndex = 0;
            Slave_IP.Text = "COM2";
            Slave_DK.Text = "";

            Slave_dataGridView.AllowUserToAddRows = false;
            Slave_dataGridView.RowHeadersWidth = 60;
            Slave_dataGridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;

            Slave_dataGridView.AllowUserToOrderColumns = false;
            //Master_dataGridView.Columns[1].SortMode=DataGridViewColumnSortMode.NotSortable;

            Slave_dataGridView.Columns.Add("Alias", "Alias");
            Slave_dataGridView.Columns.Add("00000", "00000");

            for (int i = 0; i < 10; i++)
            {
                Slave_dataGridView.Rows.Add();
                Slave_dataGridView.Rows[i].HeaderCell.Value = i.ToString();
            }

            //從站地址
            Slave_CZDZ.Text = "1";

            //表型別
            Slave_BLX.Items.Add(ModbusDataType.HoldingRegister);//保持暫存器,讀寫
            Slave_BLX.Items.Add(ModbusDataType.InputRegister);//輸入暫存器,只讀
            Slave_BLX.Items.Add(ModbusDataType.Coil);//線圈,讀寫
            Slave_BLX.Items.Add(ModbusDataType.Input);//離撒輸入,只讀
            Slave_BLX.SelectedIndex = 0;
        }
        #endregion

        #region 從站通訊方式選擇事件
        /// <summary>
        /// 從站通訊方式選擇事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Slave_TXFS_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (Slave_TXFS.SelectedIndex == 0)
            {
                Slave_IP.Text = "COM2";
                Slave_DK.Text = "";
                Slave_RTU.Visible = true;
                Slave_ASCII.Visible = true;
                Slave_RTU.Checked = true;
            }
            else
            {
                Slave_IP.Text = "127.0.0.1";
                Slave_DK.Text = "8080";
                Slave_RTU.Visible = false;
                Slave_ASCII.Visible = false;
                Slave_RTU.Checked = false;
                Slave_ASCII.Checked = false;
            }
        }







        #endregion

        #region 開啟
        /// <summary>
        /// 開啟
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Slave_KQ_Click(object sender, EventArgs e)
        {
            if (Slave_KQ.Text == "開啟")
            {
                StartSlave();
            }
            else
            {
                StopSlave();
            }
        }

        private void StartSlave()
        {
            try
            {
                byte slaveld = byte.Parse(Slave_CZDZ.Text);//從站位置 
                if (Slave_TXFS.SelectedIndex == 0)//開啟RTU或Ascii
                {
                    SerialPort port = new SerialPort();
                    port.PortName = Slave_IP.Text;//通訊方式
                    port.BaudRate = 9600;//波特率
                    port.DataBits = 8;//資料位
                    port.Parity = Parity.Even;//奇偶校驗
                    port.StopBits = StopBits.One;//停止位
                    port.Open();
                    if (Slave_RTU.Checked)
                    {
                        slave = ModbusSerialSlave.CreateRtu(slaveld, port);
                    }
                    else if (Slave_ASCII.Checked)
                    {
                        slave = ModbusSerialSlave.CreateAscii(slaveld, port);
                    }
                }
                else if (Slave_TXFS.SelectedIndex == 1)//開啟TCP
                {
                    string ip = Slave_IP.Text;
                    ushort port = ushort.Parse(Slave_DK.Text);
                    TcpListener tcpListener = new TcpListener(IPAddress.Parse(ip),port);
                    //tcpListener.Start();
                    slave = ModbusTcpSlave.CreateTcp(slaveld, tcpListener);
                }
                else if (Slave_TXFS.SelectedIndex == 2)//開啟UTP
                {
                    string ip = Slave_IP.Text;
                    ushort port = ushort.Parse(Slave_DK.Text);
                    IPEndPoint iPEnd=new IPEndPoint(IPAddress.Parse(ip), port);
                    UdpClient udpClient = new UdpClient(iPEnd);
                    slave = ModbusUdpSlave.CreateUdp(slaveld,udpClient);
                }
               // slave.Transport.ReadTimeout = 100;//讀取資料超時100ms
                //slave.Transport.WriteTimeout = 100;//寫入資料超時100ms
                slave.Transport.Retries = 3;//重試次數
                slave.Transport.WaitToRetryMilliseconds = 10;//重試間隔

                Task.Factory.StartNew(() =>
                {
                    slave.Listen();//開啟監聽
                });

                Slave_KQ.Text = "停止";

                //訂閱一些事件
                //從站收到主站請求時
                slave.ModbusSlaveRequestReceived += new EventHandler<ModbusSlaveRequestEventArgs>((s, e) =>
                {
                    this.BeginInvoke(Log, "ModbusSlaveRequestReceived");

                });
                //從站寫完資料之後的事件
                slave.WriteComplete += new EventHandler<ModbusSlaveRequestEventArgs>((s, e) =>
                {
                    this.BeginInvoke(Log, "WriteComplete");
                });

                //資料寫入事件
                slave.DataStore.DataStoreWrittenTo += new EventHandler<DataStoreEventArgs>((s, e) =>
                {
                    this.BeginInvoke(Log, "DataStoreWrittenTo");
                });
                //資料讀取事件
                slave.DataStore.DataStoreReadFrom += new EventHandler<DataStoreEventArgs>((s, e) =>
                {
                    this.BeginInvoke(Log, "DataStoreReadFrom");
                });

            }
            catch (Exception e)
            {
                MessageBox.Show($"開啟失敗:{e.Message}");
            }
        }

        private void StopSlave()
        {
            slave.Dispose();
            slave = null;
            Slave_KQ.Text = "開啟";
        }

        #endregion

        #region 寫入暫存器或線圈
        /// <summary>
        /// 寫入暫存器或線圈
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Slave_dataGridView_CellEndEdit(object sender, DataGridViewCellEventArgs e)
        {
            string table = Slave_BLX.SelectedItem.ToString();//獲取從站儲存表
            object value = Slave_dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value;//獲取當前dataGrid單後設資料
            if (value != null)
            {
                ushort v = ushort.Parse(value.ToString());
                ModbusDataType dataType = (ModbusDataType)Enum.Parse(typeof(ModbusDataType), table);
                switch (dataType)
                {
                    case ModbusDataType.HoldingRegister:
                        slave.DataStore.HoldingRegisters[e.RowIndex + 1] = v;
                        break;
                    case ModbusDataType.InputRegister:
                        slave.DataStore.InputRegisters[e.RowIndex + 1] = v;
                        break;
                    case ModbusDataType.Coil:
                        slave.DataStore.CoilDiscretes[e.RowIndex + 1] = v != 0;
                        break;
                    case ModbusDataType.Input:
                        slave.DataStore.InputDiscretes[e.RowIndex + 1] = v != 0;
                        break;
                    default:
                        break;

                }
            }
        }
        #endregion

        #region 表型別選擇
        /// <summary>
        /// 表型別選擇
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Slave_BLX_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (slave == null) return;
            string table = Slave_BLX.SelectedItem.ToString();
            ModbusDataType t = (ModbusDataType)Enum.Parse(typeof(ModbusDataType), table);
            switch (t)
            {
                case ModbusDataType.HoldingRegister:
                    for (int i = 0; i < 10; i++)
                    {
                        Slave_dataGridView.Rows[i].Cells[1].Value = slave.DataStore.HoldingRegisters[i + 1];
                    }
                    break;
                case ModbusDataType.InputRegister:
                    for (int i = 0; i < 10; i++)
                    {
                        Slave_dataGridView.Rows[i].Cells[1].Value = slave.DataStore.InputRegisters[i + 1];
                    }
                    break;
                case ModbusDataType.Coil:
                    for (int i = 0; i < 10; i++)
                    {
                        int v = slave.DataStore.CoilDiscretes[i + 1] == true ? 1 : 0;
                        Slave_dataGridView.Rows[i].Cells[1].Value = v;
                    }
                    break;
                case ModbusDataType.Input:
                    for (int i = 0; i < 10; i++)
                    {
                        int v= slave.DataStore.InputDiscretes[i + 1]==true ? 1 : 0;
                        Slave_dataGridView.Rows[i].Cells[1].Value = v;
                    }
                    break;
            }
        }
        #endregion

        #region 重新整理
        /// <summary>
        /// 重新整理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Slave_SX_Click(object sender, EventArgs e)
        {
            Slave_BLX_SelectedIndexChanged(null, null);
        }
        #endregion

        #region 窗體關閉
        /// <summary>
        /// 窗體關閉
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if(master!=null)
            {
                master.Dispose();
            }
            if(slave!=null)
            {
                slave.Dispose();
            }
        }
        #endregion
    }
}

相關文章