Skip to content

Add modbus-tcp support#3482

Merged
cpq merged 1 commit intomasterfrom
modbus
Apr 6, 2026
Merged

Add modbus-tcp support#3482
cpq merged 1 commit intomasterfrom
modbus

Conversation

@cpq
Copy link
Copy Markdown
Member

@cpq cpq commented Mar 27, 2026

No description provided.

Copy link
Copy Markdown
Collaborator

@scaprile scaprile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a lot of stuff, I will continue checking after we agree on the fundamentals

src/modbus.c Outdated
Comment on lines +60 to +61
case MG_MODBUS_COMMAND_READ_COILS:
case MG_MODBUS_COMMAND_READ_DISCRETE_INPUTS:
Copy link
Copy Markdown
Collaborator

@scaprile scaprile Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this work ?
Modbus has two register spaces and two bit spaces.

  • Inputs are read-only bit addressable, and can be read as several bits starting on any one bit
  • Coils are read/write, same addressing
  • Input registers are read-only
  • Holding registers are read/write

A read coils request is a request to read from the coil space
A read inputs request is a request to read from the inputs space, a different read-only space
A read input registers request is a request to read from the input registers space, a read-only 16-bit register space
A read holding registers request is a request to read from the holding registers space, a different read/write 16-bit register space

Copy link
Copy Markdown
Collaborator

@robertc2000 robertc2000 Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The formats for both the response and request of each of these 2 commands is identical, so the handling logic done by us is the same. It should be up to the user to handle the distinction between inputs and coils at the user handler level.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are 8 modbus commands. We pass the command to the user handler as-is
We also pass uint16_t *values array. For reads, user must fill those. For writes, user must use those.
Whatever coils and inputs are. Should we care at all ?

Copy link
Copy Markdown
Collaborator

@scaprile scaprile Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, yes, I get it, so we have a nomenclature problem. I was confused by the naming and so might be any Modbus-savvy user. I even saw struct fields with that names.
IMHO, we should use generic names avoiding the words inputs, coils, and holding. Clearly indicate that is pointer to bits or registers, and that will avoid my confusion.
I'll look at the code again

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, yeah, the problem is with names...
@cpq No, we don't need to care, though those who know what they're doing may get confused if we use these names.
I would remove "coil" from these function names, as said: we should use generic names avoiding the words inputs, coils, and holding. Clearly indicate that is pointer to bit units or 16-bit registers, and that will avoid my confusion (and any other Modbus-savvy user).
Perhaps with a bit of API documentation... also, there's always a confusion on whether the data is already in host format or in Modbus native format (big endian), I understand it is host format, that should be clearly stated.

test/unit_test.c Outdated
Comment on lines +4066 to +4071
case MG_MODBUS_COMMAND_READ_COILS:
case MG_MODBUS_COMMAND_READ_DISCRETE_INPUTS:
mc->data.coils = coil_arr;
for (i = 0; i < mc->quantity; i++) {
mg_modbus_set_coil(mc, i, mc->quantity == 1 ? coil_1[i] : coil_7[i]);
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

READ --> set ?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is responding to a READ request in the user handler. First, the user should set the data pointer (to coil_arr in our case). Then, we fill that array with the actual response (bool coil_1[] or bool coil_7[] depending on the test case).

My intention was to provide an API function (mg_modbus_set_coil) that should make it much easier for user to fill the coil/inputs buffer that we include in the response.

test/unit_test.c Outdated
break;
case MG_MODBUS_COMMAND_READ_HOLDING_REGISTERS:
case MG_MODBUS_COMMAND_READ_INPUT_REGISTERS:
if (mc->txid == 0x1c) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be TCP stuff, it does not belong here, rejection must be done before attempting to switch on the command, otherwise the server code is dependent on TCP and can't be used for serial

Copy link
Copy Markdown
Collaborator

@robertc2000 robertc2000 Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's useful to include the Transaction Identifier field (txid) in the structure. It's part of Modbus after all, even though it's just a field in the header. Main use of it is that the txid in the response must match the one in the request.

The only reason I use this field here in this test is to uniquely identify each test case. For example, the test with txid = 0x1c that you mentioned, is supposed to return a SERVER DEVICE FAILURE error. This can happen on the user side, the user handler logic can choose to trigger such an error, depending on their logic, so it's not a protocol-related error, such as an illegal function name or illegal size (which we handle in the server protocol handler BTW). Basically, this test is just a simulation of an user deciding to reply with SERVER DEVICE FAILURE to this request.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, txid is not part of Modbus... it is Modbus-TCP, it is another layer. Serial Modbus transactions do not have that field nor anything else in the TCP stuff. Yeah, this can be done later if necessary... just trying to avoid weeks of work as what decoupling from Ethernet was...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is @cpq wants only Modbus-TCP so far, we already assume the header is 7 bytes and the serial version does not have such header.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The serial version has a different framing, but the application protocol is the same.
This is what I've done years ago, so details are clear:

struct mbmessage_s {
	uint8_t address;
	uint8_t function;
	union funcdata_u funcdata;
};
#define MBTCP_ADU_MAXLEN	260	// (MODBUS_MAXPDU_LEN+1+sizeof(struct mbtcpmbap_s))

struct mbtcpmbap_s {			// values are in network order, Modbus order, big endian
	uint16_t transaction;
	uint16_t protocol;
	uint16_t length;
	uint8_t unit;
};

struct mbtcpadu_s { 
	struct mbtcpmbap_s mbap;
	struct mbmessage_s message;		// with some bytes
};
#define MBSERIAL_ADU_MAXLEN	256	// (MODBUS_MAXPDU_LEN+1+sizeof(CRC))

union mbserialadu_u {
	struct mbmessage_s message;		// with some bytes, plus CRC
	uint8_t bytes[MBSERIAL_ADU_MAXLEN];
};

@robertc2000 robertc2000 force-pushed the modbus branch 5 times, most recently from 0c7386d to 89f86b9 Compare April 3, 2026 06:48
@cpq cpq removed the request for review from robertc2000 April 3, 2026 11:34
@cpq cpq merged commit 60e98e7 into master Apr 6, 2026
63 of 64 checks passed
@cpq cpq deleted the modbus branch April 6, 2026 08:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants