Page 1 of 1
Writing Continuous Modbus Registers
Posted: Mon Feb 26, 2018 11:04 am
by 99bobster99
Hello,
I would like to do the following, but so far have not had any success (using scripting);
-> I am communicating to a servo controller, over modbus tcp. The trick is, I need to be able to write all (8) INT16 modbus registers at once (within the same modbus packet). The device requires all (8) registers to be written at once, or else it faults out on an Illegal request error.
-> ability to manipulate the above (8) INT16 modbus registers at the bit, byte and word level (hmi pushbuttons, position values and byte values).
What is the most efficient way of doing the above? A sample program that I could test would be ideal, since I need to get this working for our client.
Thank you!
Re: Writing Continuous Modbus Registers
Posted: Wed Feb 28, 2018 8:12 am
by 99bobster99
It looks like I can do this a simpler way, by moving bytes and/or words into each of the (8) INT16 registers.
Unfortunately, I am new at doing this within the IX development and C# world.
I know how to trigger a bit, within the IX scripting language (^1 = << bitnumber), but how do you move a Byte into any location within a INT16 register? Also, how do you move a double Word into (2) INT16 registers?
Any help would be greatly appreciated.
Re: Writing Continuous Modbus Registers
Posted: Wed Feb 28, 2018 8:52 am
by AMitchneck
99bobster99,
How are you connecting to Modbus TCP server? Are you using iX built-in Modbus TCP master?
I don't know how to manipulate built-in driver, but I can give you code to make your own driver with which you can do this.
Hope this helps.
Code: Select all
using System;
using System.Net;
using System.Net.Sockets;
public class ModClient : IDisposable
{
private volatile Socket sock;
private ushort transID;
public ModClient()
{
sock = null;
transID = 0;
}
// connect to Modbus TCP server at IP 'host' and port 'port'
// return true if connection successful
public bool Connect(string host, int port)
{
Socket _sock = null;
IPEndPoint ModAddr;
Close(); // clean up any previous connection
transID = 0;
try { ModAddr = new IPEndPoint(IPAddress.Parse(host), port); }
catch (ArgumentNullException) { return false; }
catch (ArgumentOutOfRangeException) { return false; }
catch (FormatException) { return false; }
try
{
_sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
AsyncState asyncState = new AsyncState(_sock);
_sock.BeginConnect(ModAddr, ProcessConnect, asyncState);
if (asyncState.WaitForReturn(20000))
{
if (_sock.Connected)
{
sock = _sock;
return true;
}
}
}
catch (SocketException) { }
catch (ObjectDisposedException) { }
catch
{
// unknown error, close socket and throw error
if (_sock != null) _sock.Close();
_sock = null;
throw;
}
// connection failed, close socket
if (_sock != null) _sock.Close();
return false;
}
private void ProcessConnect(IAsyncResult asyncResult)
{
AsyncState asyncState = (AsyncState)(asyncResult.AsyncState);
try { asyncState.socket.EndConnect(asyncResult); }
catch (SocketException) { }
catch (ArgumentException) { }
catch (InvalidOperationException) { }
finally { asyncState.Set(); }
}
// get connection status
// NOTE: this property does not test if connection lost
public bool Connected
{
get
{
if (sock == null) return false;
return sock.Connected;
}
}
// close connection
public void Close()
{
Socket _sock = sock;
if (_sock == null) return;
sock = null;
try { if (_sock.Connected) _sock.Shutdown(SocketShutdown.Both); }
catch (SocketException) { }
catch (ObjectDisposedException) { }
catch (PlatformNotSupportedException) { }
finally { _sock.Close(); }
}
public void Dispose()
{
Close();
}
// read holding registers starting at address (0-based) 'address' and word-length of 'registers' and store result in 'buffer'
// return -1 if underlying connection failure
// return 0 if Modbus error response - error code stored in first byte of 'buffer' (0 = unknown, 1 = function code not supported, 2 = illegal address, 3 = illegal quantity of registers, 4 = server error)
// return quantity of read reqisters if successful
public int ReadHoldingRegisters(ushort address, ushort registers, out byte[] buffer)
{
return ReadHoldingRegisters(1, address, registers, out buffer);
}
// read holding registers from slave ID 'slave' starting at address (0-based) 'address' and word-length of 'registers' and store result in 'buffer'
// return -1 if underlying connection failure
// return 0 if Modbus error response - error code stored in first byte of 'buffer' (0 = unknown, 1 = function code not supported, 2 = illegal address, 3 = illegal quantity of registers, 4 = server error)
// return quantity of read reqisters if successful
public int ReadHoldingRegisters(byte slave, ushort address, ushort registers, out byte[] buffer)
{
if (!Connected) return -1;
if ((registers == 0) || (registers > 125))
{
buffer = new byte[1] { 3 };
return 0;
}
byte[] header, inbuff, outbuff;
int length, i;
ushort thisID = transID++;
outbuff = new byte[12];
buffer = new byte[registers * 2];
outbuff[0] = (byte)((thisID & 0xFF00) >> 8);
outbuff[1] = (byte)(thisID & 0x00FF);
outbuff[2] = 0;
outbuff[3] = 0;
outbuff[4] = 0;
outbuff[5] = 6;
outbuff[6] = slave;
outbuff[7] = 3;
outbuff[8] = (byte)((address & 0xFF00) >> 8);
outbuff[9] = (byte)(address & 0x00FF);
outbuff[10] = (byte)((registers & 0xFF00) >> 8);
outbuff[11] = (byte)(registers & 0x00FF);
if (!SendAll(outbuff)) return -1;
for (;;)
{
if (!ReceiveAll(out header, 8)) return -1;
length = (int)((((ushort)header[4]) << 8) | (ushort)header[5]) - 2;
if (length < 0)
{
Close();
return -1;
}
if (!ReceiveAll(out inbuff, length)) return -1;
if ((outbuff[0] == header[0]) && (outbuff[1] == header[1]) && (outbuff[7] == (header[7] & 0x7F)))
{
if ((header[7] & 0x80) != 0)
{
if (length == 0)
buffer[0] = 0;
else
buffer[0] = inbuff[0];
return 0;
}
if ((length - 1) != buffer.Length)
{
buffer[0] = 0;
return 0;
}
for (i = 0; i < buffer.Length; i++)
{
buffer[i] = inbuff[i + 1];
}
return (int)registers;
}
}
}
// write holding registers from 'buffer' starting at address (0-based) 'address' and word-length of 'registers'
// return -1 if underlying connection failure or buffer of insufficient size or null
// return 0 if Modbus error response - error code stored in 'errorcode' (0 = unknown, 1 = function code not supported, 2 = illegal address, 3 = illegal quantity of registers, 4 = server error)
// return quantity of read reqisters if successful
public int WriteHoldingRegisters(ushort address, ushort registers, byte[] buffer, out byte errorcode)
{
return WriteHoldingRegisters(1, address, registers, buffer, out errorcode);
}
// write holding registers to slave ID 'slave' from 'buffer' starting at address (0-based) 'address' and word-length of 'registers'
// return -1 if underlying connection failure or buffer of insufficient size or null
// return 0 if Modbus error response - error code stored in 'errorcode' (0 = unknown, 1 = function code not supported, 2 = illegal address, 3 = illegal quantity of registers, 4 = server error)
// return quantity of read reqisters if successful
public int WriteHoldingRegisters(byte slave, ushort address, ushort registers, byte[] buffer, out byte errorcode)
{
errorcode = 0;
if ((!Connected) || (buffer == null) || (buffer.Length < ((int)registers * 2))) return -1;
if ((registers == 0) || (registers > 123))
{
errorcode = 3;
return 0;
}
byte[] header, inbuff, outbuff;
int length, i;
ushort thisID = transID++;
length = registers * 2;
outbuff = new byte[length + 13];
outbuff[0] = (byte)((thisID & 0xFF00) >> 8);
outbuff[1] = (byte)(thisID & 0x00FF);
outbuff[2] = 0;
outbuff[3] = 0;
outbuff[6] = slave;
outbuff[7] = 16;
outbuff[8] = (byte)((address & 0xFF00) >> 8);
outbuff[9] = (byte)(address & 0x00FF);
outbuff[10] = (byte)((registers & 0xFF00) >> 8);
outbuff[11] = (byte)(registers & 0x00FF);
outbuff[12] = (byte)(registers * 2);
for (i = 0; i < length; i++)
{
outbuff[i + 13] = buffer[i];
}
length += 7;
outbuff[4] = (byte)((length & 0xFF00) >> 8);
outbuff[5] = (byte)(length & 0x00FF);
if (!SendAll(outbuff)) return -1;
for (;;)
{
if (!ReceiveAll(out header, 8)) return -1;
length = (int)((((ushort)header[4]) << 8) | (ushort)header[5]) - 2;
if (length < 0)
{
Close();
return -1;
}
if (!ReceiveAll(out inbuff, length)) return -1;
if ((outbuff[0] == header[0]) && (outbuff[1] == header[1]) && (outbuff[7] == (header[7] & 0x7F)))
{
if ((header[7] & 0x80) != 0)
{
if (length == 0)
errorcode = 0;
else
errorcode = inbuff[0];
return 0;
}
if ((length != 4) || (inbuff[0] != outbuff[8]) || (inbuff[1] != outbuff[9]) || (inbuff[2] != outbuff[10]) || (inbuff[3] != outbuff[11]))
{
errorcode = 0;
return 0;
}
return (int)registers;
}
}
}
private bool SendAll(byte[] buffer)
{
int i, bytesSent;
if (buffer == null) throw new ArgumentNullException("buffer");
if (!Connected) return false;
AsyncState asyncState = new AsyncState(sock);
for (i = 0; i < buffer.Length; i += bytesSent)
{
try
{
if (sock.Poll(5000000, SelectMode.SelectWrite)) // use send timeout of 5 seconds
{
asyncState.Reset();
sock.BeginSend(buffer, i, buffer.Length - i, SocketFlags.None, ProcessSend, asyncState);
if (asyncState.WaitForReturn(10000)) // use send timeout of 10 seconds
bytesSent = asyncState.result;
else
bytesSent = 0; // send timed out
}
else
{
bytesSent = 0; // send timed out
}
}
catch (SocketException) { bytesSent = 0; }
catch (ObjectDisposedException) { bytesSent = 0; }
if (bytesSent == 0)
{
// no data sent -> connection lost
Close(); // close socket
return false;
}
}
return true;
}
private void ProcessSend(IAsyncResult asyncResult)
{
AsyncState asyncState = (AsyncState)(asyncResult.AsyncState);
try { asyncState.result = asyncState.socket.EndSend(asyncResult); }
catch (SocketException) { asyncState.result = 0; }
catch (ArgumentException) { asyncState.result = 0; }
catch (InvalidOperationException) { asyncState.result = 0; }
finally { asyncState.Set(); }
}
private bool ReceiveAll(out byte[] buffer, int size)
{
int i, bytesRecv;
if (size < 0)
{
buffer = null;
return false;
}
else
{
buffer = new byte[size];
if (!Connected) return false;
}
for (i = 0; i < size; i += bytesRecv)
{
try
{
if (sock.Poll(5000000, SelectMode.SelectRead)) // use receive timeout of 5 seconds
bytesRecv = sock.Receive(buffer, i, size - i, SocketFlags.None);
else
bytesRecv = 0; // receive timed out
}
catch (SocketException) { bytesRecv = 0; }
catch (ObjectDisposedException) { bytesRecv = 0; }
if (bytesRecv == 0)
{
// no data received -> connection lost
Close(); // close socket
return false;
}
}
return true;
}
private class AsyncState
{
private Socket sock;
private System.Threading.ManualResetEvent evnt = new System.Threading.ManualResetEvent(false);
public int result = 0;
public AsyncState(Socket socket)
{
sock = socket;
}
public Socket socket
{
get { return sock; }
}
public void Set()
{
evnt.Set();
}
public void Reset()
{
evnt.Reset();
}
public bool WaitForReturn(int timeout)
{
return evnt.WaitOne(timeout, false);
}
}
}
Re: Writing Continuous Modbus Registers
Posted: Wed Feb 28, 2018 1:49 pm
by 99bobster99
Adam,
Thank you for the code snippet. At this point I think mine is working well enough to not need a complete driver. But I am missing the details on the fundamental ability to copy from one variable format to another;
-> How do you copy a BYTE variable to any location within an INT16 variable?
-> How do you copy a DOUBLE WORD variable to (2) INT16 variables?
I am assuming this should be a fundamental ability for the IX Developer scripting environment? I am trying to search through C# commands, but have not found a solution yet.
Re: Writing Continuous Modbus Registers
Posted: Wed Feb 28, 2018 2:32 pm
by AMitchneck
99bobster99,
The answer is actually buried in the driver code
.
To copy byte into lower byte of word:
Code: Select all
byte a;
short b;
b = (short)((b & 0xFF00) | (short)a);
To copy byte into upper byte of word:
Code: Select all
byte a;
short b;
b = (short)((b & 0x00FF) | (((short)a) << 8));
To split double word into words:
Code: Select all
int a;
short[] b = new short[2];
b[0] = (short)(a && 0x0000FFFF);
b[1] = (short)((a && 0xFFFF0000) >> 16);
Be careful about big vs little Endian; above is for little Endian. Swap b[0] and b[1] for big Endian.
Re: Writing Continuous Modbus Registers
Posted: Thu Mar 01, 2018 8:29 am
by 99bobster99
Adam,
Ah, sneaky!
I started looking at all the driver logic and got a bit dizzy ...
Excellent, thank you for the feedback, I will give this a try now!!
Question, the driver code you mentioned, does this read and write all (8) modbus registers at the same time? If so, what do I need to do to implement this driver into my project and try it?
Also, going the other way, when reading an IX variable, with scripting, how do you "read" an individual bit, byte or extract from an INT16 variable back to (2) words?
Re: Writing Continuous Modbus Registers
Posted: Thu Mar 01, 2018 9:54 am
by AMitchneck
99bobster99,
Getting a double word from a two words:
Code: Select all
ushort[] a = new ushort[2] { 1, 2 };
uint b;
b = ((uint)(a[0])) | (((uint)(a[1])) << 16);
Getting a bytes from a word:
Code: Select all
ushort a;
byte[] b = new byte[2];
b[0] = (byte)(a & 0x00FF);
b[1] = (byte)((a & 0xFF00) >> 8);
Getting bit from word: (bitposition: 0 = LSb, 15 = MSb)
Code: Select all
ushort a;
bool b;
b = (a & (1 << bitposition)) != 0;
My Modbus driver will allow you to read/write any number of holding registers as allowed by the Modbus spec. The send/recieve functions take byte arrays that are sent raw over Modbus. What this means is if you are sending words or double words you will need to make sure they are stored in the array in Big Endian order as the Modbus protocol uses big Endian. I will explain how to use the driver.
In a scripting module, copy the using statements to the head of the file and somewhere in the namespace definition copy the ModClient class code. To connect to a Modbus server you would create a Modbus client and tell it to connect to the server and then do transactions (read/write). The following code example opens connection to the Modbus server at 192.168.0.10:502, writes eight words to specified address, then disconnects. Note, address is 0-based so if you want to write to address 40011 (1-based notation), you would set address to 10.
Code: Select all
public bool WriteWords(ushort address, ushort word1, ushort word2, ushort word3, ushort word4, ushort word5, ushort word6, ushort word7, ushort word8)
{
// create instance of ModClient
// using block is handy tool to clean up resources used by ModClient class
using (ModClient client = new ModClient())
{
// return false if unable to connect to server
if (!client.connect("192.168.0.10", 502)) return false;
// prepare words to be sent - first word is word1, second word is word2
byte[] buffer = new byte[16];
buffer[0] = (byte)((word1 & 0xFF00) >> 8);
buffer[1] = (byte)(word1 & 0x00FF);
buffer[2] = (byte)((word2 & 0xFF00) >> 8);
buffer[3] = (byte)(word2 & 0x00FF);
buffer[4] = (byte)((word3 & 0xFF00) >> 8);
buffer[5] = (byte)(word3 & 0x00FF);
buffer[6] = (byte)((word4 & 0xFF00) >> 8);
buffer[7] = (byte)(word4 & 0x00FF);
buffer[8] = (byte)((word5 & 0xFF00) >> 8);
buffer[9] = (byte)(word5 & 0x00FF);
buffer[10] = (byte)((word6 & 0xFF00) >> 8);
buffer[11] = (byte)(word6 & 0x00FF);
buffer[12] = (byte)((word7 & 0xFF00) >> 8);
buffer[13] = (byte)(word7 & 0x00FF);
buffer[14] = (byte)((word8 & 0xFF00) >> 8);
buffer[15] = (byte)(word8 & 0x00FF);
// send words to Modbus server
byte errorcode;
int result = WriteHoldingRegisters(address, 8, buffer, out errorcode);
// return true if words successfully written
return (result > 0);
}
}
Re: Writing Continuous Modbus Registers
Posted: Thu Mar 01, 2018 11:02 am
by 99bobster99
Thank you for the excellent information Adam!
Your assistance is greatly appreciated!
I am working on this now, I will let you know if I get stuck along the way.