Writing Continuous Modbus Registers

A forum devoted to the discussion of all topics having to do with scripting and other advanced programming using iX Developer.
Post Reply
99bobster99
Posts: 5
Joined: Mon Feb 26, 2018 9:27 am

Writing Continuous Modbus Registers

Post 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!

99bobster99
Posts: 5
Joined: Mon Feb 26, 2018 9:27 am

Re: Writing Continuous Modbus Registers

Post 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.

AMitchneck
Posts: 137
Joined: Mon Jun 11, 2012 2:10 pm

Re: Writing Continuous Modbus Registers

Post 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);
        }
    }
}
Adam M.
Controls Engineer
FlexEnergy

99bobster99
Posts: 5
Joined: Mon Feb 26, 2018 9:27 am

Re: Writing Continuous Modbus Registers

Post 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.

AMitchneck
Posts: 137
Joined: Mon Jun 11, 2012 2:10 pm

Re: Writing Continuous Modbus Registers

Post 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.
Adam M.
Controls Engineer
FlexEnergy

99bobster99
Posts: 5
Joined: Mon Feb 26, 2018 9:27 am

Re: Writing Continuous Modbus Registers

Post 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?

AMitchneck
Posts: 137
Joined: Mon Jun 11, 2012 2:10 pm

Re: Writing Continuous Modbus Registers

Post 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);
    }
}
Adam M.
Controls Engineer
FlexEnergy

99bobster99
Posts: 5
Joined: Mon Feb 26, 2018 9:27 am

Re: Writing Continuous Modbus Registers

Post 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.

Post Reply