====== Modbus with NModbus4 ====== This demonstration illustrates how to use the ComfilePi as a Modbus RTU master, using [[https://github.com/NModbus4/NModbus4|NModbus4]], to monitor and control a PLC. __NOTE__: Unfotuantely, The Mono Framework has not yet implemented the ''SerialPort'''s ''DataReceived'' event (See [[https://bugzilla.xamarin.com/show_bug.cgi?id=2075|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 [[#example_2|2nd example]] on this page. ===== Connecting the ComfilePi to a PLC ===== In this demonstration, we connect the ComfilePi to one of [[http://www.comfiletech.com/embedded-controller/cubloc/msb612ra-dc/|Comfile Technology's MSB612RA-DC PLC]]. The PLC's RS232 Channel 1 is connected to COM0 (i.e. ''/dev/serial0'') on the ComfilePi. | {{ :comfilepi:modbus_with_nmodbus4:comfilepi_connection.png?nolink&400 |}} | {{ :comfilepi:modbus_with_nmodbus4:msb_connection.png?nolink&400 |}} | ===== Procedure ===== - From your WinForms project, right-click on the project's node in //Solution Explorer// and select //Manage NuGet Packages...//. \\ {{ :comfilepi:modbus_with_nmodbus4:manage_nuget.png |}} \\ - Enter //NModbus// in the search field, select the //NModbus4// package, and then press the //Install// button. \\ {{ :comfilepi:modbus_with_nmodbus4:find_nmodbus4.png |}} \\ {{ :comfilepi:modbus_with_nmodbus4:nmodbus_install.png |}} \\ - In your project's code, open a serial port with the appropriate communication settings for the PLC. Note that the serial port names on the development PC differ from those on the ComfilePi. They can be distinguished at runtime using the ''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(); - Instantiate a ''ModbusSerialMaster'' utilizing the ''SerialPort'' used in the previous step. ModbusSerialMaster master = ModbusSerialMaster.CreateRtu(port); - Call any of the many methods in the ''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); ===== Example 1 ===== The following example illustrates the general usage of NModbus4 in a sample WinForms application. {{ :comfilepi:modbus_with_nmodbus4:simple_modbus_example.mp4?800x450 |}} 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(); } } } {{ :comfilepi:modbus_with_nmodbus4:simple_modbus_example.zip | Download Source Code }} ===== Example 2 ===== 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. {{ :comfilepi:modbus_with_nmodbus4:nmodbus_example.mp4?800x450 |}} 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(); } } } {{ :comfilepi:modbus_with_nmodbus4:modbus_example.zip | Download Source Code }}