diff --git a/.gitignore b/.gitignore index 9e9a23911..68dcfd5e9 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ tests/bandwidth-client tests/bandwidth-server-many-up tests/bandwidth-server-one tests/random-test-client +tests/dev-id-test-client tests/random-test-server tests/unit-test-client tests/unit-test.h diff --git a/doc/modbus_read_device_id.txt b/doc/modbus_read_device_id.txt new file mode 100644 index 000000000..d3ec2c8e3 --- /dev/null +++ b/doc/modbus_read_device_id.txt @@ -0,0 +1,121 @@ +modbus_read_device_id(3) +======================== + + +NAME +---- +modbus_read_device_id - read device identification objects + + +SYNOPSIS +-------- + +*modbus_read_device_id(modbus_t *ctx, int read_code, int object_id, + int max_objects, uint8_t obj_ids[], + uint8_t *obj_values[], int obj_lengths[], + int *conformity, int *next_object_id);* + +DESCRIPTION +----------- +The *modbus_read_device_id()* function shall retrieve the values of at most +_max_objects_ objects from the object address space of the remote device, +beginning at the object with id _object_id_ . The results will be stored in the +buffers provided (_obj_ids_, _obj_values_, _obj_lengths_) which must be able to +hold _max_objects_ elements each. Each element of _obj_values_ must be a byte +buffer whose size must be given in the corresponding element of _obj_lengths_. + +Objects values are byte arrays. Per the Modbus specification, the maximum +allowed object id is 255. Since an object must fit into a single response, the +maximum size of a single object is bounded by the PDU size. + +The function uses the Modbus function code 0x2B (Encapsulated Interface +Transport) with sub-function 0x0E (Read Device Identification). The method +used (i.e the "Read Device ID code") is given by the _read_code_ argument and +can take any of the following values::: +* _MODBUS_FC_READ_DEV_ID_BASIC_STREAM_ read the Basic (mandatory) parameters in stream mode +* _MODBUS_FC_READ_DEV_ID_REGULAR_STREAM_ read the Regular (optional, device independent) parameters in stream mode +* _MODBUS_FC_READ_DEV_ID_EXT_STREAM_ read the Extended (optional, device dependant) parameters in stream mode +* _MODBUS_FC_READ_DEV_ID_INDIVIDUAL_ read any parameter individually + +In case of stream access, the device may return many objects and it might not +return all the objects (because of PDU limitations). In that case the device +will indicate that additional requests are necessary and the next object id that +must be requested (whose value will be greated than zero) will be returned in +the location pointed at by _next_object_id_ (if it is not null). If there are +no more object, that value will be zero. + +The conformity level as reported by the device will be stored in the location +pointed to by _conformity_ (if it is not null). This value indicates the +category (Basic, Regular or Extended) of objects supported by the device as +well as the type of access (Stream-only or Stream and Individual). The following +macros are provided to assis in parsing these values::: +* _MODBUS_READ_DEV_ID_CONFORMITY_CAT(conformity)_ Get the category of object + supported (_MODBUS_FC_READ_DEV_ID_BASIC_STREAM_, _MODBUS_FC_READ_DEV_ID_REGULAR_STREAM_ + or _MODBUS_FC_READ_DEV_ID_EXT_STREAM_) +* _MODBUS_READ_DEV_ID_SUPPORTS_INDIVIDUAL(conformity)_ Evaluates to a true value + if individual access is supported. + +RETURN VALUE +------------ +On success, the function shall return the number of objects retrieved from the +device, which may be larger than the number of object returned to the user if +the device returns more than _max_objects_ objects if successful. In case of +errors, it shall return -1 and set errno. Note that it is also possible that +less objects are retrieved than requested. + +For each object retrieved, its id will be stored in _obj_ids_ array, its value +in the corresponding entry in the _obj_values_ array and its full size (as +reported by the device) will be placed in the corresponding item of the +_obj_lenghts_ array. If the length value after this function call is greater +than what it was before, it means that the object was truncated. + +Note that the object values are copied verbatim from the device and in the case +of strings, may not be null-terminated. + +ERRORS +------ +*EINVAL*:: +The requested objects are outside the valid range. + +EXAMPLE +------- + +This example shows how to call the function repeatedly to retrieve as many +objects are possible. + +[source,c] +------------------- +static int read_dev_id_repeatedly(modbus_t *ctx, int read_code, int object_id, + int max_objects, uint8_t *obj_ids, + uint8_t **obj_values, int *obj_lengths, + int *conformity) +{ + int total_retrieved = 0, n_retrieved; + + do { + n_retrieved = modbus_read_device_id(ctx, read_code, object_id, + max_objects, obj_ids, obj_values, obj_lengths, conformity, + &object_id); + + total_retrieved += n_retrieved; + obj_ids += n_retrieved; + obj_values += n_retrieved; + obj_lengths += n_retrieved; + max_objects -= n_retrieved; + } while (n_retrieved > 0 && object_id > 0 + && object_id < MODBUS_DEVID_MAX_OBJ_ID && max_objects > 0); + + return total_retrieved; +} +------------------- + +SEE ALSO +-------- +linkmb:modbus_read_device_id_single[3] +linkmb:report_slave_id[3] +Section 6.21 of the Modbus Specification V1.1b + +AUTHORS +------- +The libmodbus documentation was written by Stéphane Raimbault + diff --git a/doc/modbus_read_device_id_single.txt b/doc/modbus_read_device_id_single.txt new file mode 100644 index 000000000..1379f2790 --- /dev/null +++ b/doc/modbus_read_device_id_single.txt @@ -0,0 +1,72 @@ +modbus_read_device_id_single(3) +======================== + + +NAME +---- +modbus_read_device_id - read device identification objects + + +SYNOPSIS +-------- + +*int modbus_read_device_id_single(modbus_t *ctx, int object_id, uint8_t *obj_id, + uint8_t *obj_value, int obj_length, + int *conformity);* + +DESCRIPTION +----------- +*modbus_read_device_id_single(3)* shall retrieve a single object from the +object address space of the remote device. It uses the Modbus function code 0x2B +(Encapsulated Interface Transport) with sub-function 0x0E (Read Device +Identification) and "Read Device Id Code" 0x04 ("Individual Access"). It +requires a device with conformity level greater than 0x80. + +This function is a wrapper around *modbus_read_device_id(3)*. Refer to the +documentation of that function for more information. + +RETURN VALUE +------------ +On success, the function shall return the length of objects retrieved as +reported by device, which may be larger than the value specified in obj_length, +in which case it means that the objecs is truncated. In case of errors, it shall +return -1 and set errno. + +Note that the object values are copied verbatim from the device and in the case +of strings, may not be null-terminated. + +ERRORS +------ +*EINVAL*:: +The requested objects are outside the valid range. + +EXAMPLE +------- + +This function can be implemented in terms of *modbus_read_device_id(3)* + +[source,c] +------------------- +int modbus_read_device_id_single(modbus_t *ctx, int object_id, uint8_t *obj_id, + uint8_t *obj_value, int obj_length, + int *conformity) +{ + if (modbus_read_device_id(ctx, MODBUS_FC_READ_DEV_ID_INDIVIDUAL, object_id, + 1, obj_id, &obj_value, &obj_length, conformity, + NULL)) { + return -1; + } + return obj_length; +} +------------------- + +SEE ALSO +-------- +linkmb:modbus_read_device_id[3] +linkmb:report_slave_id[3] +Section 6.21 of the Modbus Specification V1.1b + +AUTHORS +------- +The libmodbus documentation was written by Stéphane Raimbault + diff --git a/src/modbus.c b/src/modbus.c index 69aedf245..9cff52bbd 100644 --- a/src/modbus.c +++ b/src/modbus.c @@ -34,6 +34,32 @@ const unsigned int libmodbus_version_micro = LIBMODBUS_VERSION_MICRO; /* Max between RTU and TCP max adu length (so TCP) */ #define MAX_MESSAGE_LENGTH 260 +/* Max between RTU and TCP header lenght */ +#define MODBUS_PRESET_RSP_LENGTH 8 + +/* offset of the "MEI Type / Subfunction" field in encapsulated interface transport*/ +#define MODBUS_FC_2B_SUBFUNC 1 + +/* Offsets of the various fields in "Read Device ID Code" request and responses */ +enum MODBUS_FC_2B0E { + MODBUS_FC_2B0E_SUBFUNC = MODBUS_FC_2B_SUBFUNC, + MODBUS_FC_2B0E_READ_DEVID_CODE, /* "Read Device ID Code" */ + MODBUS_FC_2B0E_OBJ_ID, /* "Object Id" (request only) */ + MODBUS_FC_2B0E_CONFORMITY_LEVEL = MODBUS_FC_2B0E_OBJ_ID, /* "Conformity Level" (response only) */ + MODBUS_FC_2B0E_REQ_SIZE, /* Size of a request (4). The remaining symbols are related to the response */ + MODBUS_FC_2B0E_MORE_FOLLOWS = MODBUS_FC_2B0E_REQ_SIZE, /* "More Follows */ + MODBUS_FC_2B0E_NEXT_OBJ_ID, /* "Next Object Id" */ + MODBUS_FC_2B0E_NUMBER_OF_OBJECTS, /* "Number of objects" */ + MODBUS_FC_2B0E_FIRST_OBJECT /* offset of the beginning of the first object */ +}; + +enum MODBUS_FC_2B0E_ENTRY { + MODBUS_FC_2B0E_ENTRY_OID, + MODBUS_FC_2B0E_ENTRY_OBJLEN, + MODBUS_FC_2B0E_ENTRY_MIN_LEN, /* Minimum size of an object entry on "Read Device Id", excludes the obj data */ + MODBUS_FC_2B0E_ENTRY_VALUE = MODBUS_FC_2B0E_ENTRY_MIN_LEN +}; + /* 3 steps are used to parse the query */ typedef enum { _STEP_FUNCTION, @@ -149,6 +175,7 @@ static unsigned int compute_response_length_from_request(modbus_t *ctx, uint8_t length = 3; break; case MODBUS_FC_REPORT_SLAVE_ID: + case MODBUS_FC_ENCAPSULATED_TRANSPORT: /* The response is device specific (the header provides the length) */ return MSG_LENGTH_UNDEFINED; @@ -309,7 +336,10 @@ static int compute_data_length_after_meta(modbus_t *ctx, uint8_t *msg, } } else { /* MSG_CONFIRMATION */ - if (function <= MODBUS_FC_READ_INPUT_REGISTERS || + if (function == MODBUS_FC_ENCAPSULATED_TRANSPORT && + msg[ctx->backend->header_length + MODBUS_FC_2B_SUBFUNC] == MODBUS_FC_READ_DEVICE_ID) { + length = MODBUS_FC_2B0E_NUMBER_OF_OBJECTS - MODBUS_FC_2B_SUBFUNC; + } else if (function <= MODBUS_FC_READ_INPUT_REGISTERS || function == MODBUS_FC_REPORT_SLAVE_ID || function == MODBUS_FC_WRITE_AND_READ_REGISTERS) { length = msg[ctx->backend->header_length + 1]; @@ -323,6 +353,35 @@ static int compute_data_length_after_meta(modbus_t *ctx, uint8_t *msg, return length; } +/* Computes the remaining data to be read. For some operations (eg "Read Device + Id"), the size is embedded in the data. */ +static int compute_additional_data_length(modbus_t *ctx, uint8_t *msg, + msg_type_t msg_type, int msg_length) +{ + int function = msg[ctx->backend->header_length]; + int length; + + if (msg_type == MSG_CONFIRMATION && function == MODBUS_FC_ENCAPSULATED_TRANSPORT + && msg[ctx->backend->header_length + MODBUS_FC_2B_SUBFUNC] == MODBUS_FC_READ_DEVICE_ID) { + int num_objects = msg[ctx->backend->header_length + MODBUS_FC_2B0E_NUMBER_OF_OBJECTS]; + int found_objects = 0; + int last_item_idx = ctx->backend->header_length + MODBUS_FC_2B0E_NUMBER_OF_OBJECTS; + if (num_objects > 0) { + last_item_idx += MODBUS_FC_2B0E_ENTRY_MIN_LEN; + } + while (last_item_idx < msg_length && found_objects < num_objects) { + int this_entry_size = msg[last_item_idx]; + found_objects++; + last_item_idx += this_entry_size + + ((found_objects < num_objects)? MODBUS_FC_2B0E_ENTRY_MIN_LEN : 0); + } + length = last_item_idx + 1 - msg_length + ctx->backend->checksum_length;; + } else { + length = 0; + } + + return length; +} /* Waits a response from a modbus server or a request from a modbus client. This function blocks if there is no replies (3 timeouts). @@ -455,6 +514,12 @@ int _modbus_receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type) step = _STEP_DATA; break; default: + length_to_read = compute_additional_data_length(ctx, msg, msg_type, msg_length); + if ((msg_length + length_to_read) > (int)ctx->backend->max_adu_length) { + errno = EMBBADDATA; + _error_print(ctx, "too many data"); + return -1; + } break; } } @@ -618,6 +683,13 @@ static int check_confirmation(modbus_t *ctx, uint8_t *req, /* 1 Write functions & others */ req_nb_value = rsp_nb_value = 1; break; + case MODBUS_FC_ENCAPSULATED_TRANSPORT: + if (req[offset + MODBUS_FC_2B_SUBFUNC] == MODBUS_FC_READ_DEVICE_ID) { + rsp_nb_value = req_nb_value = rsp[offset + MODBUS_FC_2B0E_NUMBER_OF_OBJECTS]; + } else { + rsp_nb_value = req_nb_value = 1; + } + break; default: /* 1 Write functions & others */ req_nb_value = rsp_nb_value = 1; @@ -1580,6 +1652,101 @@ int modbus_report_slave_id(modbus_t *ctx, int max_dest, uint8_t *dest) return rc; } +static int imin(int a, int b) +{ + return (a < b)? a : b; +} + +static int imax(int a, int b) +{ + return (a > b)? a : b; +} + +static int make_read_device_id_req(modbus_t *ctx, int read_code, int object_id, + uint8_t *req) +{ + sft_t sft = {.slave = ctx->slave, .function = MODBUS_FC_ENCAPSULATED_TRANSPORT, + .t_id = 0}; + + int offset = ctx->backend->build_response_basis(&sft, req) - 1; + req[offset + MODBUS_FC_2B_SUBFUNC] = MODBUS_FC_READ_DEVICE_ID; + req[offset + MODBUS_FC_2B0E_READ_DEVID_CODE] = read_code; + req[offset + MODBUS_FC_2B0E_OBJ_ID] = object_id; + + return offset + MODBUS_FC_2B0E_REQ_SIZE; +} + +/* Read Device Id objects */ +int modbus_read_device_id(modbus_t *ctx, int read_code, int object_id, + int max_objects, uint8_t obj_ids[], + uint8_t *obj_values[], int obj_lengths[], + int *conformity, int *next_object_id) +{ + if (ctx == NULL || object_id < 0 || max_objects < 0 + || object_id + max_objects > MODBUS_DEVID_MAX_OBJECTS) { + errno = EINVAL; + return -1; + } + + uint8_t req[MODBUS_PRESET_RSP_LENGTH + MODBUS_FC_2B0E_REQ_SIZE]; + int req_length = make_read_device_id_req(ctx, read_code, object_id, req); + + if (send_msg(ctx, req, req_length) == -1) { + return -1; + } + + uint8_t rsp[MAX_MESSAGE_LENGTH]; + int offset; + + int resp_len = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION); + if (resp_len == -1) { + return -1; + } + + int n_objects = check_confirmation(ctx, req, rsp, resp_len); + if (n_objects == -1) { + return -1; + } + + offset = ctx->backend->header_length; + + int more_follows = rsp[offset + MODBUS_FC_2B0E_MORE_FOLLOWS]; + int _next_object_id = rsp[offset + MODBUS_FC_2B0E_NEXT_OBJ_ID]; + if (next_object_id != NULL) { + /* This makes it less likely that a buggy device will send the user program + into an infinite loop. */ + *next_object_id = more_follows? imax(object_id + 1, _next_object_id): 0; + } + if (conformity != NULL) { + *conformity = rsp[offset + MODBUS_FC_2B0E_CONFORMITY_LEVEL]; + } + + int obj_offset = offset + MODBUS_FC_2B0E_FIRST_OBJECT; + + for (int i = 0; i < n_objects && i < max_objects; i++) { + obj_ids[i] = rsp[obj_offset + MODBUS_FC_2B0E_ENTRY_OID]; + int element_size = rsp[obj_offset + MODBUS_FC_2B0E_ENTRY_OBJLEN]; + int min_size = imin(element_size, obj_lengths[i]); + memcpy(obj_values[i], rsp + obj_offset + MODBUS_FC_2B0E_ENTRY_VALUE, min_size); + obj_lengths[i] = element_size; + obj_offset += element_size + MODBUS_FC_2B0E_ENTRY_MIN_LEN; + } + + return n_objects; +} + +int modbus_read_device_id_single(modbus_t *ctx, int object_id, uint8_t *obj_id, + uint8_t *obj_value, int obj_length, + int *conformity) +{ + if (modbus_read_device_id(ctx, MODBUS_FC_READ_DEV_ID_INDIVIDUAL, object_id, + 1, obj_id, &obj_value, &obj_length, conformity, + NULL)) { + return -1; + } + return obj_length; +} + void _modbus_init_common(modbus_t *ctx) { /* Slave and socket are initialized to -1 */ diff --git a/src/modbus.h b/src/modbus.h index 24808ead5..859e0e059 100644 --- a/src/modbus.h +++ b/src/modbus.h @@ -70,6 +70,8 @@ MODBUS_BEGIN_DECLS #define MODBUS_FC_REPORT_SLAVE_ID 0x11 #define MODBUS_FC_MASK_WRITE_REGISTER 0x16 #define MODBUS_FC_WRITE_AND_READ_REGISTERS 0x17 +#define MODBUS_FC_ENCAPSULATED_TRANSPORT 0x2B +#define MODBUS_FC_READ_DEVICE_ID 0x0E #define MODBUS_BROADCAST_ADDRESS 0 @@ -110,6 +112,19 @@ MODBUS_BEGIN_DECLS */ #define MODBUS_MAX_ADU_LENGTH 260 +/* Access types for (0x2B / 0x0E) Read Device Identification */ +#define MODBUS_FC_READ_DEV_ID_BASIC_STREAM 0x01 +#define MODBUS_FC_READ_DEV_ID_REGULAR_STREAM 0x02 +#define MODBUS_FC_READ_DEV_ID_EXT_STREAM 0x03 +#define MODBUS_FC_READ_DEV_ID_INDIVIDUAL 0x04 + +/* Maximum valid object id for (0x2B / 0x0E) Read Device Identification. + * Modbus_Application_Protocol_V1_1b.pdf (chapter 6 section 21 page 44) + */ +#define MODBUS_DEVID_MAX_OBJ_ID 0xFF +/* Maximum number of objects in the object address space */ +#define MODBUS_DEVID_MAX_OBJECTS (MODBUS_DEVID_MAX_OBJ_ID + 1) + /* Random number to avoid errno conflicts */ #define MODBUS_ENOBASE 112345678 @@ -216,6 +231,13 @@ MODBUS_API int modbus_write_and_read_registers(modbus_t *ctx, int write_addr, in const uint16_t *src, int read_addr, int read_nb, uint16_t *dest); MODBUS_API int modbus_report_slave_id(modbus_t *ctx, int max_dest, uint8_t *dest); +MODBUS_API int modbus_read_device_id(modbus_t *ctx, int read_code, int object_id, + int max_objects, uint8_t obj_ids[], + uint8_t *obj_values[], int obj_lengths[], + int *conformity, int *next_object_id); +MODBUS_API int modbus_read_device_id_single(modbus_t *ctx, int object_id, uint8_t *obj_id, + uint8_t *obj_value, int obj_length, + int *conformity); MODBUS_API modbus_mapping_t* modbus_mapping_new_start_address( unsigned int start_bits, unsigned int nb_bits, @@ -242,6 +264,9 @@ MODBUS_API int modbus_reply_exception(modbus_t *ctx, const uint8_t *req, * UTILS FUNCTIONS **/ +#define MODBUS_READ_DEV_ID_CONFORMITY_CAT(conformity) ((conformity)&0xF) +#define MODBUS_READ_DEV_ID_SUPPORTS_INDIVIDUAL(conformity) (!!((conformity)&0xF0)) + #define MODBUS_GET_HIGH_BYTE(data) (((data) >> 8) & 0xFF) #define MODBUS_GET_LOW_BYTE(data) ((data) & 0xFF) #define MODBUS_GET_INT64_FROM_INT16(tab_int16, index) \ diff --git a/src/win32/Make-tests b/src/win32/Make-tests index d4a569e63..049b7d4db 100644 --- a/src/win32/Make-tests +++ b/src/win32/Make-tests @@ -17,7 +17,7 @@ INCLUDES:=-I../.. -I.. -I. ifeq ($(CC),cl) DEFS+=-D_CRT_SECURE_NO_DEPRECATE=1 -D_CRT_NONSTDC_NO_DEPRECATE=1 CFLAGS=-Zi -W3 -MT -ID:/include/msvc_std -LDOPTS=-link -incremental:NO +LDOPTS=-link -incremental:NO LDLIBS=-Fe$@ ws2_32.lib modbus.lib $(LDOPTS) RES:=res RCOUT= @@ -43,11 +43,15 @@ CFLAGS+=-DHAVE_CONFIG_H $(DEFS) $(INCLUDES) vpath %.c ../../tests vpath %.h ../src -all: random-test-client random-test-server bandwidth-client bandwidth-server-one +all: random-test-client random-test-server bandwidth-client bandwidth-server-one \ + dev-id-test-client random-test-client: random-test-client.c $(LINK.c) $^ $(LDLIBS) +dev-id-test-client: dev-id-test-client.c + $(LINK.c) $^ $(LDLIBS) + random-test-server: random-test-server.c $(LINK.c) $^ $(LDLIBS) diff --git a/tests/Makefile.am b/tests/Makefile.am index 7302c8d73..97c30c5d3 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -6,6 +6,7 @@ noinst_PROGRAMS = \ bandwidth-client \ random-test-server \ random-test-client \ + dev-id-test-client \ unit-test-server \ unit-test-client \ version @@ -28,6 +29,9 @@ random_test_server_LDADD = $(common_ldflags) random_test_client_SOURCES = random-test-client.c random_test_client_LDADD = $(common_ldflags) +dev_id_test_client_SOURCES = dev-id-test-client.c +dev_id_test_client_LDADD = $(common_ldflags) + unit_test_server_SOURCES = unit-test-server.c unit-test.h unit_test_server_LDADD = $(common_ldflags) diff --git a/tests/dev-id-test-client.c b/tests/dev-id-test-client.c new file mode 100644 index 000000000..25decea73 --- /dev/null +++ b/tests/dev-id-test-client.c @@ -0,0 +1,169 @@ +/* + * Copyright © 2022 Ebee Smart GmBH + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include + +#include + +/* One more than 255 so that we can test the library limit */ +#define N_BUFFERS 256 + +static const char usage[] + = "Test client for Modbus function (0x2B / 0x0E) Read Device Identification\n" + "Usage:\n" + "dev-id-test-client [ [ " + "[...]]]\n"; + +static int read_dev_id_repeatedly(modbus_t *ctx, int read_code, int object_id, + int max_objects, uint8_t *obj_ids, + uint8_t **obj_values, int *obj_lengths, + int *conformity) +{ + int total_retrieved = 0, n_retrieved; + + do { + n_retrieved = modbus_read_device_id(ctx, read_code, object_id, + max_objects, obj_ids, obj_values, obj_lengths, conformity, + &object_id); + + total_retrieved += n_retrieved; + obj_ids += n_retrieved; + obj_values += n_retrieved; + obj_lengths += n_retrieved; + max_objects -= n_retrieved; + } while (n_retrieved > 0 && object_id > 0 + && object_id < MODBUS_DEVID_MAX_OBJ_ID && max_objects > 0); + + return total_retrieved; +} + +static const char *cat2string(int conformity) +{ + switch (MODBUS_READ_DEV_ID_CONFORMITY_CAT(conformity)) { + default: + return "unknown"; + case MODBUS_FC_READ_DEV_ID_BASIC_STREAM: + return "Basic"; + case MODBUS_FC_READ_DEV_ID_REGULAR_STREAM: + return "Regular"; + case MODBUS_FC_READ_DEV_ID_EXT_STREAM: + return "Extended"; + } +} + +static const char *at2string(int conformity) +{ + if (MODBUS_READ_DEV_ID_SUPPORTS_INDIVIDUAL(conformity)) { + return "Stream+Individual"; + } else { + return "Stream only"; + } +} + +int main(int argc, char* argv[]) +{ + if (argc < 2) { + puts(usage); + return -1; + } + + char* conv_end; + + int read_code = strtol(argv[1], &conv_end, 0); + if (*conv_end != '\0') { + puts("Unable to parse the read id code"); + return -1; + } + + int starting_object = strtol(argv[2], &conv_end, 0); + if (*conv_end != '\0') { + puts("Unable to parse the starting id"); + return -1; + } + + int n_reqd_objs = argc - 3; + if (n_reqd_objs > N_BUFFERS) { + puts("Too many objects requested"); + return -1; + } + + uint8_t obj_ids[N_BUFFERS]; + uint8_t* obj_buffers[N_BUFFERS]; + int buffer_lengths[N_BUFFERS]; + int i = 0; + + for (; i < n_reqd_objs; i++) { + buffer_lengths[i] = strtol(argv[i + 3], &conv_end, 0); + if (*conv_end != '\0') { + printf("Unable to parse the size of buffer %d\n", i); + return -1; + } + obj_buffers[i] = malloc(buffer_lengths[i]); + assert(obj_buffers[i] != NULL); + } + + for (; i < N_BUFFERS; i++) { + obj_buffers[i] = NULL; + buffer_lengths[i] = 0; + } + + modbus_t* ctx; + + ctx = modbus_new_tcp("127.0.0.1", 1502); + //ctx = modbus_new_rtu("/dev/ttyUSB0", 9600, 'N', 8, 1); + //modbus_set_slave(ctx, 1); + modbus_set_debug(ctx, TRUE); + + if (modbus_connect(ctx) == -1) { + fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno)); + modbus_free(ctx); + return -1; + } + + int orig_buffer_lengths[N_BUFFERS]; + memcpy(orig_buffer_lengths, buffer_lengths, sizeof(buffer_lengths)); + + + int conformity; + int n_read_objs = read_dev_id_repeatedly(ctx, read_code, starting_object, + n_reqd_objs, obj_ids, obj_buffers, buffer_lengths, &conformity); + + if (n_read_objs == -1) { + fprintf(stderr, "Read Device ID failed: %s\n", modbus_strerror(errno)); + } else { + int actual_avail_objs = (n_read_objs < n_reqd_objs)? n_read_objs : n_reqd_objs; + printf("Requested: %d, Read: %d\n", n_reqd_objs, actual_avail_objs); + printf("Reported conformity level: %s %s\n", cat2string(conformity), + at2string(conformity)); + puts("Obj ID\tLength\tTrunc?\tValue"); + for (i = 0; i < actual_avail_objs; i++) { + char trunc = orig_buffer_lengths[i] < buffer_lengths[i]; + char data_length = trunc? orig_buffer_lengths[i] : buffer_lengths[i]; + printf("%.2X\t%3d\t%c\t%.*s\t", obj_ids[i], buffer_lengths[i], + trunc? 'T' : ' ', data_length, (char*)obj_buffers[i]); + for (int b = 0; b < data_length; b++) { + printf("[%X]", obj_buffers[i][b]); + } + putchar('\n'); + } + } + + for (i = n_reqd_objs; i < N_BUFFERS; i++) { + assert(buffer_lengths[i] == 0); + } + + for (i = 0; i < n_reqd_objs; i++) { + free(obj_buffers[i]); + } + + modbus_free(ctx); + + return (n_read_objs > 0) ? 0 : 1; +}