// Modbus TCP client by Pierre-Marie Baty <pm@pmbaty.com>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <limits.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#ifdef _WIN32 // POSIX shims for that Microsoft joke pretending to be an OS
typedef ptrdiff_t ssize_t;
static const char *basename (const char *pathname)
{
for (const char *p
= pathname
+ strlen (pathname
); p
!= pathname
; p
--)
if ((*p == '\\') || (*p == '/'))
return (p + 1);
return (pathname);
}
#define fputc SetConsoleOutputCP(CP_UTF8), fputc
#define fputs SetConsoleOutputCP(CP_UTF8), fputs
#define printf SetConsoleOutputCP(CP_UTF8), printf
#define fprintf SetConsoleOutputCP(CP_UTF8), fprintf
#include <winsock2.h>
static int _socket (int domain, int type, int protocol)
{
static WSADATA wsa_data = { 0 };
if (wsa_data.wVersion == 0)
WSAStartup (0x0202, &wsa_data);
return (socket (domain, type, protocol));
}
#define socket(domain,type,protocol) _socket ((domain), (type), (protocol))
#ifdef _MSC_VER
#pragma comment(lib,"ws2_32.lib")
#endif // _MSC_VER
#else // !_WIN32
#include <libgen.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif // _WIN32
int main (int argc, char **argv)
{
const char *modbus_function_strings[] =
{
"", // not defined
"Read Coils",
"Read Discrete Inputs",
"Read Multiple Holding Registers",
"Read Input Registers",
"Write Single Coil",
"Write Single Holding Register",
"Read Exception Status [serial only]", // serial only
"Diagnostic [serial only]", // serial only
"", // not defined
"", // not defined
"Get Com Event Counter [serial only]", // serial only
"Get Com Event Log [serial only]", // serial only
"", // not defined
"", // not defined
"Write Multiple Coils",
"Write Multiple Holding Registers",
};
const char *modbus_exception_strings[] =
{
"undefined exception", // not defined
"Illegal Function",
"Illegal Data Address",
"Illegal Data Value",
"Server Device Failure",
"Acknowledge",
"Server Device Busy",
"Negative Acknowledge",
"Memory Parity Error",
"undefined exception", // not defined
"Gateway Path Unavailable",
"Gateway Target Device Failed to Respond",
};
struct sockaddr_in server = { 0 };
unsigned int address_bytes[4] = { 0 };
unsigned int port = 502;
#ifdef _MSC_VER
#pragma pack(push,1)
#define __attribute__(...)
#endif // _MSC_VER
struct __attribute__((packed))
{
struct __attribute__((packed))
{
uint16_t message_id; // big endian
uint16_t protocol_id; // big endian
uint16_t payload_len; // big endian
uint8_t unit_id;
} header;
union
{
struct __attribute__((packed))
{
uint8_t function_code;
uint16_t start_address; // big endian
uint16_t count_or_val; // big endian
uint8_t byte_len; // optional
uint8_t bytes[UINT8_MAX]; // optional
} as_query;
struct __attribute__((packed))
{
uint8_t function_code;
union
{
struct __attribute__((packed))
{
uint8_t byte_len;
uint8_t bytes[UINT8_MAX];
} as_bytelen_plus_data; // function codes 1-4
struct __attribute__((packed))
{
uint16_t start_address;
uint16_t count_or_val;
} as_address_plus_value; // function codes 5-6
struct __attribute__((packed))
{
uint8_t bytes[0xffff];
} as_data; // unknown function codes
} u;
} as_reply;
struct __attribute__((packed))
{
uint8_t function_code;
uint8_t exception_code;
} as_error;
} u;
} modbus_message = { 0 };
#ifdef _MSC_VER
#undef __attribute__
#pragma pack(pop)
#endif // _MSC_VER
ssize_t message_len;
size_t array_index;
int client_socket;
if (argc < 6)
{
fputs ("usage:\n", stderr
);
fprintf (stderr
, " %s <ip address[:port]> <unit ID> <function code> <start address> <count|value> [<hexbyte> [...]]\n", basename
(argv
[0]));
fputs ("Modbus function codes:\n", stderr
);
for (array_index = 0; array_index < sizeof (modbus_function_strings) / sizeof (modbus_function_strings[0]); array_index++)
if ((modbus_function_strings
[array_index
][0] != 0) && (strstr (modbus_function_strings
[array_index
], " [serial only]") == NULL
))
fprintf (stderr
, " %zd\t%s\n", array_index
, modbus_function_strings
[array_index
]);
}
if ((sscanf (argv
[1], "%u.%u.%u.%u:%u", &address_bytes
[0], &address_bytes
[1], &address_bytes
[2], &address_bytes
[3], &port
) < 4)
|| ((address_bytes[0] | address_bytes[1] | address_bytes[2] | address_bytes[3]) > 255))
{
fprintf (stderr
, "error: invalid IPv4 address\n");
}
server.sin_family = AF_INET;
server.sin_addr.s_addr = htonl ((address_bytes[0] << 24) | (address_bytes[1] << 16) | (address_bytes[2] << 8) | (address_bytes[3] << 0));
server.sin_port = htons ((uint16_t) port);
client_socket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (client_socket == -1)
{
fprintf (stderr
, "error: socket() call failed with errno %d: %s\n", errno
, strerror (errno
));
}
else if (connect (client_socket, (struct sockaddr *) &server, sizeof (server)) == -1)
{
fprintf (stderr
, "error: connect() call failed with errno %d: %s\n", errno
, strerror (errno
));
}
printf ("Connected to %u.%u.%u.%u on port %u\n", address_bytes
[0], address_bytes
[1], address_bytes
[2], address_bytes
[3], port
);
modbus_message.
header.
message_id = ntohs
((uint16_t) rand ());
modbus_message.header.protocol_id = ntohs (0);
modbus_message.
header.
unit_id = (uint8_t) strtol (argv
[2], NULL
, 0);
modbus_message.
u.
as_query.
function_code = (uint8_t) strtol (argv
[3], NULL
, 0);
modbus_message.
u.
as_query.
start_address = ntohs
((uint16_t) strtol (argv
[4], NULL
, 0));
modbus_message.
u.
as_query.
count_or_val = ntohs
((uint16_t) strtol (argv
[5], NULL
, 0));
modbus_message.u.as_query.byte_len = 0;
for (array_index = 6; array_index < argc; array_index++)
modbus_message.
u.
as_query.
bytes[modbus_message.
u.
as_query.
byte_len++] = (uint8_t) strtol (argv
[array_index
], NULL
, 0);
message_len = sizeof (modbus_message.header) + sizeof (modbus_message.u.as_query.function_code);
if (strchr ("\x01\x02\x03\x04\x05\x06", (char) modbus_message.
u.
as_query.
function_code) != NULL
)
message_len += sizeof (modbus_message.u.as_query.start_address) + sizeof (modbus_message.u.as_query.count_or_val);
else if (modbus_message.u.as_query.byte_len > 0)
message_len += sizeof (modbus_message.u.as_query.start_address) + sizeof (modbus_message.u.as_query.count_or_val) + sizeof (modbus_message.u.as_query.byte_len) + modbus_message.u.as_query.byte_len;
modbus_message.header.payload_len = ntohs (message_len - ((size_t) &modbus_message.header.unit_id - (size_t) &modbus_message));
fputs ("Modbus query:", stdout
);
for (array_index = 0; array_index < message_len; array_index++)
printf (" %02x", ((uint8_t *) &modbus_message
)[array_index
]);
fputs ("├ MBAP header\n", stdout
);
printf ("│ ├ Message ID: %d\n", ntohs
(modbus_message.
header.
message_id));
printf ("│ ├ Protocol ID: %d\n", ntohs
(modbus_message.
header.
protocol_id));
printf ("│ ├ Payload length: %d bytes\n", ntohs
(modbus_message.
header.
payload_len));
printf ("│ └ Unit ID: %d\n", modbus_message.
header.
unit_id);
fputs ("└ Payload\n", stdout
);
if (message_len == sizeof (modbus_message.header) + sizeof (modbus_message.u.as_query.function_code))
printf (" └ Function code: %d (%s)\n", modbus_message.
u.
as_query.
function_code, (modbus_message.
u.
as_query.
function_code < sizeof (modbus_function_strings
) / sizeof (modbus_function_strings
[0]) ? modbus_function_strings
[modbus_message.
u.
as_query.
function_code] : "unknown function code"));
else
{
printf (" ├ Function code: %d (%s)\n", modbus_message.
u.
as_query.
function_code, (modbus_message.
u.
as_query.
function_code < sizeof (modbus_function_strings
) / sizeof (modbus_function_strings
[0]) ? modbus_function_strings
[modbus_message.
u.
as_query.
function_code] : "unknown function code"));
printf (" ├ Start address: %d\n", ntohs
(modbus_message.
u.
as_query.
start_address));
if (message_len == sizeof (modbus_message.header) + sizeof (modbus_message.u.as_query.function_code) + sizeof (modbus_message.u.as_query.start_address) + sizeof (modbus_message.u.as_query.count_or_val))
printf (" └ Count / value: %d\n", ntohs
(modbus_message.
u.
as_query.
count_or_val));
else
{
printf (" ├ Count / value: %d\n", ntohs
(modbus_message.
u.
as_query.
count_or_val));
printf (" ├ Data length: %d bytes\n", modbus_message.
u.
as_query.
byte_len);
fputs (" └ Data: ", stdout
);
for (array_index = 0; array_index < modbus_message.u.as_query.byte_len; array_index++)
printf (" %02x", modbus_message.
u.
as_query.
bytes[array_index
]);
}
}
if (send (client_socket, (void *) &modbus_message, message_len, 0) != message_len)
{
fprintf (stderr
, "error: send() call failed with errno %d: %s\n", errno
, strerror (errno
));
}
message_len = recv (client_socket, (void *) &modbus_message, sizeof (modbus_message), 0);
if (message_len <= 0)
{
fprintf (stderr
, "error: recv() call failed with return value %zd and errno %d: %s\n", message_len
, errno
, strerror (errno
));
}
fputs ("Modbus reply:", stdout
);
for (array_index = 0; array_index < message_len; array_index++)
printf (" %02x", ((uint8_t *) &modbus_message
)[array_index
]);
printf ("│ ├ Message ID: %d\n", ntohs
(modbus_message.
header.
message_id));
printf ("│ ├ Protocol ID: %d\n", ntohs
(modbus_message.
header.
protocol_id));
printf ("│ ├ Payload length: %d bytes\n", ntohs
(modbus_message.
header.
payload_len));
printf ("│ └ Unit ID: %d\n", modbus_message.
header.
unit_id);
printf (" ├ Function code: %d (%s)\n", modbus_message.
u.
as_reply.
function_code & 0x7f, (modbus_message.
u.
as_reply.
function_code & 0x80 ? "error bit set" : "ok"));
if (modbus_message.u.as_reply.function_code & 0x80)
printf (" └ Exception code: %d (%s)\n", modbus_message.
u.
as_error.
exception_code, (modbus_message.
u.
as_error.
exception_code < sizeof (modbus_exception_strings
) / sizeof (modbus_exception_strings
[0]) ? modbus_exception_strings
[modbus_message.
u.
as_error.
exception_code] : "unknown exception"));
else if ((modbus_message.u.as_reply.function_code >= 1) && (modbus_message.u.as_reply.function_code <= 4))
{
printf (" ├ Data length: %d bytes\n", modbus_message.
u.
as_reply.
u.
as_bytelen_plus_data.
byte_len);
fputs (" └ Data: ", stdout
);
for (array_index = 0; array_index < modbus_message.u.as_reply.u.as_bytelen_plus_data.byte_len; array_index++)
printf (" %02x", modbus_message.
u.
as_reply.
u.
as_bytelen_plus_data.
bytes[array_index
]);
}
else if ( ((modbus_message.u.as_reply.function_code >= 4) && (modbus_message.u.as_reply.function_code <= 5))
|| ((modbus_message.u.as_reply.function_code >= 15) && (modbus_message.u.as_reply.function_code <= 16)))
{
printf (" ├ Start address: %d\n", modbus_message.
u.
as_reply.
u.
as_address_plus_value.
start_address);
printf (" └ Count / value: %d\n", modbus_message.
u.
as_reply.
u.
as_address_plus_value.
count_or_val);
}
else
{
fputs (" └ Data: ", stdout
);
for (array_index = 0; array_index < message_len - ((size_t) modbus_message.u.as_reply.u.as_data.bytes - (size_t) &modbus_message); array_index++)
printf (" %02x", modbus_message.
u.
as_reply.
u.
as_data.
bytes[array_index
]);
}
exit (modbus_message.
u.
as_reply.
function_code & 0x80 ? 1 : 0);
}