Modbus Reply Script

Hello. I'm evaluating IO Ninja to see if I can use it to simulate some Modbus RTU devices. What I'm currently trying to do is write a simple script that will reply to a particular Modbus Master request of Read Holding Registers. Is there a script example for using the ModbusRtuReadReplyPacket template to send a response to a Read Holding Registers request?

Hello,

Here's a script that receives Read Holding Register (or Read Input Register) requests and replies with ever-increasing values (1, 2, 3,...):

import "io_Modbus.jnc"
import "crc16.jnc"

pragma(Alignment, 1)

struct Request:
	io.ModbusRtuAduHdr,
	io.ModbusReadPdu {
	uint16_t m_crc;
}
	
struct Reply: 
	io.ModbusRtuAduHdr,
	io.ModbusReadReplyPdu {
	uint16_t m_data[128]; // big enough
}

void main() {
	Request request;
	Reply reply;	

	int value = 0;

	for (;;) {
		receiveAll(&request, sizeof(request)); // read request

		// prepare reply

		size_t dataSize = request.m_count * sizeof(uint16_t);
		reply.m_func = request.m_func;
		reply.m_deviceAddress = request.m_deviceAddress;
		reply.m_size = dataSize;

		bigendian uint16_t* p = reply.m_data; // Modbus values are bigendian
		for (size_t i = 0; i < request.m_count; i++)
			p[i] = ++value; // adjust values accordingly

		size_t size = offsetof(Reply.m_data) + dataSize; // not including Modbus checksum
		reply.m_data[request.m_count] = crc16_ansi(reply, size, 0xffff); 

		// transmit reply
		
		transmit(reply, size + sizeof(uint16_t)); // including Modbus checksum
	}
}

The result might look like this:

f7d227b6-48f0-4e34-9f61-13aafaf1b018-image.png

Above, I used a pair of TCP Server and TCP Connection sessions to issue sample Read Holding Register requests, but in your case, you most likely want to use a Serial session to talk to a real device.

Thank you for the example. This is exactly what I was looking for.

I have just started using ioNinja (evaluation) as well. Where is the documentation for the libraries that are imported in the above script.

I found the manual at

https://ioninja.com/doc/developer-manual/group_proto-modbus.html?srsltid=AfmBOoqJfrT4qjexVkCA_l9euYmY_8MPLPnjL9Qix4fknICMRvOxF7aS

not quite enough detail for programming for example

size_t errorcode receiveAll(
void* p,
size_t size,
uint_t timeout
);

what it timeout in milliseconds? and what are the values or error code and what do they mean - I could not find that

That said above this looks super interesting to handle responses

https://ioninja.com/doc/developer-manual/tutorial-ias-server.html

I guess the above just goes into the scripting panes. One problem I have is how do I debug the program I write. Not sure how to be able to do that, for example, look at the contents of a variable.

Hi Gary,

Sorry, the API documentation is currently just a placeholder; it's auto-generated from the script sources (you can also check those sources directly at $IONINA_DIR/scripts/api/). However, we also have some scripting tutorials in the Developer Manual (https://ioninja.com/doc/developer-manual/tutorials.html) -- those should be helpful.

For the in-app-scripting inside the "Script" pane, all the available function declarations can be found at scripts/api/ias.jnc

To answer your question, the receive and receiveAll functions are used to collect raw data bytes that appear in your log as the RX (incoming) stream.

receive is the most basic method; it accepts the buffer and timeout and works as such:

  1. if there are any incoming data bytes at the moment of call -- those data bytes are returned immediately;
  1. otherwise, receive waits for incoming data for at most timeout milliseconds before returning. If a chunk of incoming data arrives before timeout expires, it will be returned as soon as received. If timeout expires and no data arrives -- 0 will be returned. timeout defaults to -1 which is equivalent to infinite wait for data (receive will not return until at least one byte of incoming data arrives).

receiveAll is a helper wrapper around receive and is used to fill the supplied buffer entirely (you can see its implementation in ias.jnc). This is convenient when your script needs to fill a fixed-size packet header before proceeding. If a packet arrives in chunks, the receiveAll will wait until the buffer is complete and only then return. The overloaded version of receiveAll with the timeout parameter will attempt to fill the buffer entirely -- but will bail after timeout milliseconds.

Hope this helps; feel free to ask more.

@gary-biagioni

That said above this looks super interesting to handle responses

https://ioninja.com/doc/developer-manual/tutorial-ias-server.html

At the moment of writing this tutoiral, onLogRecord was the only approach to reading incoming data bytes.

Of course, it still works, but we now have receive and receiveAll functions. I believe they are much easier to use.

@gary-biagioni

I guess the above just goes into the scripting panes. One problem I have is how do I debug the program I write. Not sure how to be able to do that, for example, look at the contents of a variable.

You can use the good old printf debugging.

import "hexEncoding.jnc"

void main() {
	int a = 10;
	char s[] = "abcdef";
	char buf[] = 0x"01 02 03 04 05";
	
	string_t msg = $"a: %1/0x%(1;02x) s: %2 buf: %3"(a, s, encodeHexString(buf, sizeof(buf), ' '));
	// string_t msg = $"a: %d/0x%02x s: %s buf: %s"(a, a, s, encodeHexString(buf, sizeof(buf), ' ')); // same
	printf($"This will go to the system log: $msg\n");
	g_logWriter.write(log.StdRecordCode.PlainText, $"This will go to the normal log: $msg\n");
}

The system log can be viewed via Menu->View->System Log