This demonstration illustrates how to use the ComfilePi as a Modbus RTU master, using NModbus4, to monitor and control a PLC.
NOTE: Unfotuantely, The Mono Framework has not yet implemented the SerialPort
's DataReceived
event (See Mono Bug 2075). However, this is easily resolved using a background thread that polls the receive buffer. A similar example, using a background thread to read the from the serial port, is illustrated in the 2nd example on this page.
In this demonstration, we connect the ComfilePi to one of Comfile Technology's MSB612RA-DC PLC. The PLC's RS232 Channel 1 is connected to COM0 (i.e. /dev/serial0
) on the ComfilePi.
Environment.OsVersion.Platform
enumeration. string portName = Environment.OSVersion.Platform == PlatformID.Win32NT ? "COM1" : "/dev/serial0"; SerialPort port = new SerialPort(portName, 115200); port.ReadTimeout = 100; port.WriteTimeout = 100; port.Open();
ModbusSerialMaster
utilizing the SerialPort
used in the previous step. ModbusSerialMaster master = ModbusSerialMaster.CreateRtu(port);
ModbusSerialMaster
type to read from or write to the PLC. bool[] ReadCoils(byte slaveAddress, ushort startAddress, ushort numberOfPoints); ushort[] ReadHoldingRegisters(byte slaveAddress, ushort startAddress, ushort numberOfPoints); ushort[] ReadInputRegisters(byte slaveAddress, ushort startAddress, ushort numberOfPoints); bool[] ReadInputs(byte slaveAddress, ushort startAddress, ushort numberOfPoints); ushort[] ReadWriteMultipleRegisters(byte slaveAddress, ushort startReadAddress, ushort numberOfPointsToRead, ushort startWriteAddress, ushort[] writeData); void WriteMultipleCoils(byte slaveAddress, ushort startAddress, bool[] data); void WriteMultipleRegisters(byte slaveAddress, ushort startAddress, ushort[] data); void WriteSingleCoil(byte slaveAddress, ushort coilAddress, bool value); void WriteSingleRegister(byte slaveAddress, ushort registerAddress, ushort value);
The following example illustrates the general usage of NModbus4 in a sample WinForms application.
using Modbus.Device; using System; using System.IO.Ports; using System.Windows.Forms; namespace SimpleModbusExample { public partial class Form1 : Form { const int SLAVE_ADDRESS = 1; const int COIL_ADDRESS = 32; public Form1() { InitializeComponent(); } SerialPort _port; ModbusSerialMaster _master; private void Form1_Load(object sender, EventArgs e) { // Intialize serial port string portName = Environment.OSVersion.Platform == PlatformID.Win32NT ? "COM1" : "/dev/serial0"; _port = new SerialPort(portName, 115200); _port.ReadTimeout = 100; _port.WriteTimeout = 100; _port.Open(); // Initialize Modbus master _master = ModbusSerialMaster.CreateRtu(_port); // Read the current state of the output ReadState(); } private void Form1_FormClosed(object sender, FormClosedEventArgs e) { // Destroy Modbus master _master.Dispose(); _master = null; // Destroy serial port _port.Close(); _port.Dispose(); _port = null; } private void OnButton_Click(object sender, EventArgs e) { // Turn output ON _master.WriteSingleCoil(SLAVE_ADDRESS, COIL_ADDRESS, true); } private void OffButton_Click(object sender, EventArgs e) { // Turn output OFF _master.WriteSingleCoil(SLAVE_ADDRESS, COIL_ADDRESS, false); } void ReadState() { // Read the current state of the output var state = _master.ReadCoils(SLAVE_ADDRESS, COIL_ADDRESS, 1); // Update the UI if (state[0]) { StateLabel.Text = "On"; } else { StateLabel.Text = "Off"; } } private void ReadStateButton_Click(object sender, EventArgs e) { // Read the current state of the output ReadState(); } } }
The following example is a more real-world demonstration with the Modbus master running in a background thread repeatedly monitoring the state of the PLC in real-time.
using System; using System.Windows.Forms; using System.IO.Ports; using Modbus.Device; using System.Threading; namespace ModbusExample { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private volatile bool _stopModbus; private Thread _modbusThread; private void RunModbus() { string portName = Environment.OSVersion.Platform == PlatformID.Win32NT ? "COM1" : "/dev/serial0"; SerialPort port = new SerialPort(portName, 115200); port.ReadTimeout = 100; port.WriteTimeout = 100; port.Open(); _stopModbus = false; ModbusSerialMaster master = ModbusSerialMaster.CreateRtu(port); IAsyncResult result = null; while(!_stopModbus) { // Read UI's button states and assign to device's outputs bool[] outputs = new bool[4]; result = BeginInvoke(new Action(() => { outputs[0] = button1.IsOn; outputs[1] = button2.IsOn; outputs[2] = button3.IsOn; outputs[3] = button4.IsOn; })); while (!_stopModbus && !result.IsCompleted) { Thread.Yield(); } if (!_stopModbus) { try { master.WriteMultipleCoils(1, 32, outputs); } catch (Exception ex) { Console.WriteLine(ex.Message); } } // Read inputs and assign to UI's Lamps if (!_stopModbus) { try { var inputs = master.ReadCoils(1, 8, 4); result = BeginInvoke(new Action(() => { lamp8.IsOn = inputs[3]; lamp9.IsOn = inputs[2]; lamp10.IsOn = inputs[1]; lamp11.IsOn = inputs[0]; })); } catch (Exception ex) { Console.WriteLine(ex.Message); } } while (!_stopModbus && !result.IsCompleted) { Thread.Yield(); } } master.Dispose(); port.Close(); port.Dispose(); } private void Form1_Load(object sender, EventArgs e) { _modbusThread = new Thread(RunModbus); _modbusThread.IsBackground = true; _modbusThread.Start(); } private void Form1_FormClosed(object sender, FormClosedEventArgs e) { _stopModbus = true; _modbusThread.Join(); } } }