diff --git a/doc/modbus_mapping_new_extend.txt b/doc/modbus_mapping_new_extend.txt new file mode 100644 index 000000000..fe9b487d6 --- /dev/null +++ b/doc/modbus_mapping_new_extend.txt @@ -0,0 +1,63 @@ +modbus_mapping_new_extend(3) +===================== + + +NAME +---- +modbus_mapping_new_extend - allocate four arrays of bits and registers and file records + + +SYNOPSIS +-------- +*modbus_mapping_t* modbus_mapping_new_extend(int 'nb_bits', int 'nb_input_bits', int 'nb_registers', int 'nb_input_registers', uint16_t nb_file_register[MODBUS_MAX_RECORD_FILESMODBUS_MAX_RECORD_FILES]);* + + +DESCRIPTION +----------- +The *modbus_mapping_new_extend()* calls linkmb:modbus_mapping_new[3] and additionally allocates up to +MODBUS_MAX_RECORD_FILES (current specification limit it to 10) arrays for file records. + +Each _value_ in the array, which is not 0 allocates _value_ registers in the file of _index_ -1 . +( The file registers start with "1" for the first file ) + +RETURN VALUE +------------ +The function shall return the new allocated structure if successful. Otherwise +it shall return NULL and set errno. + + +ERRORS +------ +*ENOMEM*:: +Not enough memory + + +EXAMPLE +------- +[source,c] +------------------- +/* The first value of each array is accessible from the 0 address. */ +mb_mapping = modbus_mapping_new_extend(BITS_ADDRESS + BITS_NB, + INPUT_BITS_ADDRESS + INPUT_BITS_NB, + REGISTERS_ADDRESS + REGISTERS_NB, + INPUT_REGISTERS_ADDRESS + INPUT_REGISTERS_NB, + FILE_REGISTERS_NB[MODBUS_MAX_RECORD_FILES]); +if (mb_mapping == NULL) { + fprintf(stderr, "Failed to allocate the mapping: %s\n", + modbus_strerror(errno)); + modbus_free(ctx); + return -1; +} +------------------- + +SEE ALSO +-------- +linkmb:modbus_mapping_new[3] +linkmb:modbus_mapping_free[3] +linkmb:modbus_mapping_new_start_address[3] + + +AUTHORS +------- +The libmodbus documentation was written by Stéphane Raimbault + diff --git a/doc/modbus_mapping_new_start_address_extend.txt b/doc/modbus_mapping_new_start_address_extend.txt new file mode 100644 index 000000000..4027f79ef --- /dev/null +++ b/doc/modbus_mapping_new_start_address_extend.txt @@ -0,0 +1,81 @@ +modbus_mapping_new_start_address_extend(3) +=================================== + + +NAME +---- +modbus_mapping_new_start_address_extend - allocate four arrays of bits and registers accessible from their starting addresses and file registers + + +SYNOPSIS +-------- +*modbus_mapping_t* modbus_mapping_new_start_address(int 'start_bits', int 'nb_bits', + int 'start_input_bits', int 'nb_input_bits', + int 'start_registers', int 'nb_registers', + int 'start_input_registers', int 'nb_input_registers', + 'uint16_t nb_file_register[MODBUS_MAX_RECORD_FILES]');* + + +DESCRIPTION +----------- +The *modbus_mapping_new_extend()* calls linkmb:modbus_mapping_new_start_address[3] and additionally allocates up to +MODBUS_MAX_RECORD_FILES (current specification limit it to 10) arrays for file registers. + +Each _value_ in the array, which is not 0 allocates _value_ registers in the file of _index_ -1 . +( The file registers start with "1" for the first file ) + +[source,c] +------------------- +mb_mapping = modbus_mapping_new_start_address_extend(0, 0, 0, 0, 10000, 10, 0, 0,{0,100,0,20,0,0,10000,0,0,0,0}); +------------------- + +With this code, 3 file registers ( 2, 4 and 7 ) with the size of 100 , 20 and 10000 registers are allocated. + +If it isn't necessary to allocate an array for a specific type of data, you can +pass the zero value in argument, the associated pointer will be NULL. + +This function is convenient to handle requests in a Modbus server/slave. + + +RETURN VALUE +------------ +The _modbus_mapping_new_start_address_extend()_ function shall return the new allocated structure if +successful. Otherwise it shall return NULL and set errno. + + +ERRORS +------ +ENOMEM:: +Not enough memory + + +EXAMPLE +------- +[source,c] +------------------- +/* The first value of each array is accessible at the defined address. + The end address is ADDRESS + NB - 1. */ +mb_mapping = modbus_mapping_new_start_address_extend(BITS_ADDRESS, BITS_NB, + INPUT_BITS_ADDRESS, INPUT_BITS_NB, + REGISTERS_ADDRESS, REGISTERS_NB, + INPUT_REGISTERS_ADDRESS, INPUT_REGISTERS_NB, + FILE_REGISTERS_NB[MODBUS_MAX_RECORD_FILES]); +if (mb_mapping == NULL) { + fprintf(stderr, "Failed to allocate the mapping: %s\n", + modbus_strerror(errno)); + modbus_free(ctx); + return -1; +} +------------------- + +SEE ALSO +-------- +linkmb:modbus_mapping_new_start_address[3] +linkmb:modbus_mapping_new[3] +linkmb:modbus_mapping_free[3] + + +AUTHORS +------- +The libmodbus documentation was written by Stéphane Raimbault + diff --git a/doc/modbus_read_file_record.txt b/doc/modbus_read_file_record.txt new file mode 100644 index 000000000..d105a26d7 --- /dev/null +++ b/doc/modbus_read_file_record.txt @@ -0,0 +1,84 @@ +modbus_read_file_register(3) +======================== + + +NAME +---- +modbus_read_file_register - read from file registers + + +SYNOPSIS +-------- +*int modbus_read_file_register(modbus_t *ctx, int file_no, int read_addr, int read_nb, uint16_t *dest);* + + +DESCRIPTION +----------- +The *modbus_read_file_register()* function shall read the number of _read_nb_ registers of the _file_no_ +reference file at the address _read_addr_ of the remote device. The result of +reading is stored in _dest_ array as word values (16 bits). + +_file_no_ has to be >= 1 and <= 10 . + +You must take care to allocate enough memory to store the results in _dest_ +(at least (_read_nb_ + 1 ) * sizeof(uint16_t)). + +The function uses the Modbus function code 0x14 (read file record). + + +RETURN VALUE +------------ +The function shall return the number of read registers + 1 +if successful. Otherwise it shall return -1 and set errno. + + +ERRORS +------ +*EMBMDATA*:: +Too many registers requested +*EMBXILVAL*:: +_file_no_ out of range , or file is not found. + + +EXAMPLE +------- +[source,c] +------------------- +modbus_t *ctx; +uint16_t tab_reg[64+1]; // one additional reg for filesize and reftype +int rc; +int i; + +ctx = modbus_new_tcp("127.0.0.1", 1502); +if (modbus_connect(ctx) == -1) { + fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno)); + modbus_free(ctx); + return -1; +} + + rc = modbus_read_file_register(ctx, 1, 10 , 64, tab_reg ); + if (rc == -1) { + fprintf(stderr, "%s\n", modbus_strerror(errno)); + return -1; +} + +printf("size=%d subtype_ref=%d\n", i, tab_reg[0]>>8, tab_reg[0] &0xff); +for (i=1; i < rc; i++) { + printf("reg[%d]=%d (0x%X)\n", i, tab_reg[i], tab_reg[i]); +} + +modbus_close(ctx); +modbus_free(ctx); +------------------- + + +SEE ALSO +-------- +linkmb:modbus_write_file_register[3] +linkmb:modbus_mapping_new_start_address_extend[3] +linkmb:modbus_mapping_new_extend[3] + +AUTHORS +------- +The libmodbus documentation was written by Stéphane Raimbault + diff --git a/doc/modbus_write_file_record.txt b/doc/modbus_write_file_record.txt new file mode 100644 index 000000000..97f5fc9c4 --- /dev/null +++ b/doc/modbus_write_file_record.txt @@ -0,0 +1,80 @@ +modbus_write_file_register(3) +======================== + + +NAME +---- +modbus_write_file_register - write to file registers + + +SYNOPSIS +-------- +*int modbus_write_file_register(modbus_t *ctx, int file_no, int write_addr, int write_nb, const uint16_t *src);* + + +DESCRIPTION +----------- +The *modbus_read_general_reference()* function shall write the number of _write_nb_ registers of the _file_no_ +reference file at the address _write_addr_ of the remote device. The content for +writing is delivered in _src_ array as word values (16 bits). + +_file_no_ has to be >= 1 and <= 10 . + + +The function uses the Modbus function code 0x15 (write file record). + + +RETURN VALUE +------------ +The function shall return the number of written registers + 1 +if successful. Otherwise it shall return -1 and set errno. + + +ERRORS +------ +*EMBMDATA*:: +Too many registers requested +*EMBXILVAL*:: +_file_no_ out of range , or file is not found. + + +EXAMPLE +------- +[source,c] +------------------- +modbus_t *ctx; +uint16_t tab_reg[64]; +int rc; +int i; + +ctx = modbus_new_tcp("127.0.0.1", 1502); +if (modbus_connect(ctx) == -1) { + fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno)); + modbus_free(ctx); + return -1; +} + +for (i=0; i < 64; i++){ + tab_reg[i]=i+i*0x100; +} + rc = modbus_write_file_register(ctx, 1, 10 , 64, tab_reg ); + if (rc == -1) { + fprintf(stderr, "%s\n", modbus_strerror(errno)); + return -1; +} + +modbus_close(ctx); +modbus_free(ctx); +------------------- + + +SEE ALSO +-------- +linkmb:modbus_read_file_register[3] +linkmb:modbus_mapping_new_start_address_extend[3] +linkmb:modbus_mapping_new_extend[3] + +AUTHORS +------- +The libmodbus documentation was written by Stéphane Raimbault + diff --git a/src/modbus-private.h b/src/modbus-private.h index 2c601c495..b38cd7086 100644 --- a/src/modbus-private.h +++ b/src/modbus-private.h @@ -39,6 +39,10 @@ MODBUS_BEGIN_DECLS #define _RESPONSE_TIMEOUT 500000 #define _BYTE_TIMEOUT 500000 +/* Reference-Type for File-Record Read + Write */ +#define _SUB_REQ_HDR_LENGTH 0x07 +#define _SUB_REQUEST_REF_TYPE 0x06 + typedef enum { _MODBUS_BACKEND_TYPE_RTU=0, _MODBUS_BACKEND_TYPE_TCP diff --git a/src/modbus.c b/src/modbus.c index 03d8da248..655070532 100644 --- a/src/modbus.c +++ b/src/modbus.c @@ -152,6 +152,11 @@ static unsigned int compute_response_length_from_request(modbus_t *ctx, uint8_t /* The response is device specific (the header provides the length) */ return MSG_LENGTH_UNDEFINED; + case MODBUS_FC_WRITE_FILE_RECORD: + length = 2 + req[offset + 1]; + break; + case MODBUS_FC_READ_FILE_RECORD: + return MSG_LENGTH_UNDEFINED; /* Length is dedepnding of the subrequests */ case MODBUS_FC_MASK_WRITE_REGISTER: length = 7; break; @@ -264,6 +269,9 @@ static uint8_t compute_meta_length_after_function(int function, length = 6; } else if (function == MODBUS_FC_WRITE_AND_READ_REGISTERS) { length = 9; + } else if (function == MODBUS_FC_READ_FILE_RECORD || + function == MODBUS_FC_WRITE_FILE_RECORD) { + length = 1; // After the function, the number of bytes is transmitted } else { /* MODBUS_FC_READ_EXCEPTION_STATUS, MODBUS_FC_REPORT_SLAVE_ID */ length = 0; @@ -280,6 +288,11 @@ static uint8_t compute_meta_length_after_function(int function, case MODBUS_FC_MASK_WRITE_REGISTER: length = 6; break; + case MODBUS_FC_WRITE_FILE_RECORD: + case MODBUS_FC_READ_FILE_RECORD: + length = 1; /* After the function, the number of bytes is transmitted + and at least one SUB_REQUEST*/ + break; default: length = 1; } @@ -304,6 +317,10 @@ static int compute_data_length_after_meta(modbus_t *ctx, uint8_t *msg, case MODBUS_FC_WRITE_AND_READ_REGISTERS: length = msg[ctx->backend->header_length + 9]; break; + case MODBUS_FC_WRITE_FILE_RECORD: + case MODBUS_FC_READ_FILE_RECORD: + length = msg[ctx->backend->header_length + 1]; + break; default: length = 0; } @@ -313,6 +330,9 @@ static int compute_data_length_after_meta(modbus_t *ctx, uint8_t *msg, function == MODBUS_FC_REPORT_SLAVE_ID || function == MODBUS_FC_WRITE_AND_READ_REGISTERS) { length = msg[ctx->backend->header_length + 1]; + } else if (function == MODBUS_FC_READ_FILE_RECORD || + function == MODBUS_FC_WRITE_FILE_RECORD) { + length = msg[ctx->backend->header_length + 1]; } else { length = 0; } @@ -596,6 +616,15 @@ static int check_confirmation(modbus_t *ctx, uint8_t *req, req_nb_value = (req[offset + 3] << 8) + req[offset + 4]; rsp_nb_value = (rsp[offset + 3] << 8) | rsp[offset + 4]; break; + case MODBUS_FC_WRITE_FILE_RECORD: + /* Check for Bytes recevied, response is copy of the request */ + req_nb_value = req[offset + 1]; + rsp_nb_value = rsp[offset + 1]; + break; + case MODBUS_FC_READ_FILE_RECORD: + /* Check for Bytes recevied */ + req_nb_value = rsp_nb_value = rsp[offset + 1]; + break; case MODBUS_FC_REPORT_SLAVE_ID: /* Report slave ID (bytes received) */ req_nb_value = rsp_nb_value = rsp[offset + 1]; @@ -985,8 +1014,146 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req, rsp[rsp_length++] = mb_mapping->tab_registers[i] & 0xFF; } } - } - break; + } break; + case MODBUS_FC_WRITE_FILE_RECORD: { + /* Each "Write_File_Record", can consists of several Subrequests */ + uint8_t nb = req[offset + 1]; + uint16_t i; + int nsr = 0; + uint8_t ref_type = 0; + + /* Each Subrequest has at least 7 Bytes */ + if (nb < _SUB_REQ_HDR_LENGTH) { + rsp_length = response_exception( + ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, TRUE, + "Illegal nb of subrequests %d in write_general_reference \n", nb); + break; + } + + rsp_length = ctx->backend->build_response_basis(&sft, rsp); + if (rsp_length + nb >= MODBUS_MAX_READ_REGISTERS) { + rsp_length = response_exception( + ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, TRUE, + "Responselength exceeds telegram-size %d in subrequests %d in " + "write_file_record \n", + rsp_length + nb, nsr); + + } else { + /* Response is a simple copy of the request */ + memcpy(rsp + rsp_length, req + offset + 1, + 1 + nb); /* number of bytes + the place for the nb itself */ + rsp_length += nb + 1; + + do { + offset += 2; + ref_type = req[offset]; + if (ref_type != _SUB_REQUEST_REF_TYPE) { + rsp_length = response_exception( + ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, TRUE, + "Illegal reference Type %d in subrequests %d in " + "write_file_record \n", + ref_type, nsr); + } else { + uint16_t file_no = (req[offset + 1] << 8) + req[offset + 2]; + uint16_t f_address = (req[offset + 3] << 8) + req[offset + 4]; + uint16_t nb_write = (req[offset + 5] << 8) + req[offset + 6]; + + offset += _SUB_REQ_HDR_LENGTH; + nb -= _SUB_REQ_HDR_LENGTH; + + if ((file_no == 0) || + (mb_mapping->file_registers[file_no - 1] == NULL)) { + rsp_length = response_exception( + ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, TRUE, + "Illegal parameter file %d in subrequests %d in " + "write_file_record \n", + file_no, nsr); + break; + } else { + /* Write Registers to file at position adress */ + for (i = f_address; i < f_address + nb_write; i++) { + mb_mapping->file_registers[file_no - 1][i] = + (req[offset] << 8) + req[offset + 1]; + offset += 2; + nb -= 2; + } + } + } + nsr++; + } while (nb > 0); + } + } break; + case MODBUS_FC_READ_FILE_RECORD: { + /* Each "Read_FielRecord", can consists of several Subrequests */ + uint8_t nb = req[offset + 1]; + uint16_t i; + int nsr = 0; + uint8_t ref_type = 0; + int rsp_length_byte_count = 0; + + /* Each Subrequest has 7 Bytes */ + if (nb % _SUB_REQ_HDR_LENGTH != 0) { + rsp_length = response_exception( + ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, TRUE, + "Illegal nb of subrequests %d in read_read_file \n", nb); + break; + } + + rsp_length = ctx->backend->build_response_basis(&sft, rsp); + + rsp_length_byte_count = + rsp_length; /* Place-holder of overall message-size. */ + rsp[rsp_length++] = 0; + + do { + offset += 2; + ref_type = req[offset]; + + if (ref_type != _SUB_REQUEST_REF_TYPE) { + rsp_length = response_exception( + ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, TRUE, + "Illegal reference Type %d in subrequests %d in " + "read_file_record \n", + ref_type, nsr); + } else { + uint16_t file_no = (req[offset + 1] << 8) + req[offset + 2]; + uint16_t f_address = (req[offset + 3] << 8) + req[offset + 4]; + uint16_t nb_read = (req[offset + 5] << 8) + req[offset + 6]; + + if ((file_no == 0) || + (mb_mapping->file_registers[file_no - 1] == NULL)) { + rsp_length = response_exception( + ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, TRUE, + "Illegal parameter file %d in subrequests %d in " + "read_general_reference \n", + file_no, nsr); + + } else if (rsp_length + nb_read >= MODBUS_MAX_READ_REGISTERS) { + rsp_length = response_exception( + ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, TRUE, + "Responselength exceeds telegram-size %d in subrequests %d in " + "read_file_record \n", + rsp_length + nb_read, nsr); + + } else { + rsp[rsp_length++] = nb_read; + rsp[rsp_length++] = _SUB_REQUEST_REF_TYPE; + /* and read the data for the response */ + for (i = f_address; i < f_address + nb_read; i++) { + rsp[rsp_length++] = + mb_mapping->file_registers[file_no - 1][i] >> 8; + rsp[rsp_length++] = + mb_mapping->file_registers[file_no - 1][i] & 0xFF; + } + } + } + offset += _SUB_REQ_HDR_LENGTH; + nsr++; + } while (nsr < (nb / _SUB_REQ_HDR_LENGTH)); + /* put overall length of message at the reserved space in the beginning. */ + rsp[rsp_length_byte_count] = (rsp_length - rsp_length_byte_count) - 1; + + } break; default: rsp_length = response_exception( @@ -1559,6 +1726,131 @@ int modbus_report_slave_id(modbus_t *ctx, int max_dest, uint8_t *dest) return rc; } +/* Read File Record reads nb registers (16-bit) from + * an offset ( also 16-bit stepping ) of a given filenumber + * This implements only the simple case with one subrequest. More complex can be + * created with raw-message. + * !!! Take care, that dest array must have the size of at least read_nb +1 !!! */ + +int modbus_read_file_record(modbus_t *ctx, int file_no, int read_addr, + int read_nb, uint16_t *dest) + +{ + int rc; + int req_length; + int i; + int byte_count; + uint8_t req[MAX_MESSAGE_LENGTH]; + uint8_t rsp[MAX_MESSAGE_LENGTH]; + + if (read_nb > MODBUS_MAX_READ_REGISTERS) { + if (ctx->debug) { + fprintf(stderr, "ERROR Too many registers requested (%d > %d)\n", read_nb, + MODBUS_MAX_READ_REGISTERS); + } + errno = EMBMDATA; + return -1; + } + byte_count = _SUB_REQ_HDR_LENGTH; + req_length = ctx->backend->build_request_basis( + ctx, MODBUS_FC_READ_FILE_RECORD, ((byte_count << 8) | 0x06), + file_no, req); + + req[req_length++] = read_addr >> 8; + req[req_length++] = read_addr & 0x00ff; + req[req_length++] = read_nb >> 8; + req[req_length++] = read_nb & 0x00ff; + + rc = send_msg(ctx, req, req_length); + if (rc > 0) { + int offset; + + rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION); + if (rc == -1) + return -1; + + rc = check_confirmation(ctx, req, rsp, rc); + if (rc == -1) + return -1; + + rc /= 2; /* rc is in byte, we count in uint16-steps */ + offset = ctx->backend->header_length; + if (rc > (read_nb + 1)) { /* One register is extra for size and ref-type */ + if (ctx->debug) { + fprintf(stderr, "ERROR Too many data for destination (%d > %d)\n", rc, + read_nb); + } + rc = read_nb; + } + + /* If rc is negative, the loop is jumped ! */ + for (i = 0; i < rc; i++) { + /* shift reg hi_byte to temp OR with lo_byte */ + dest[i] = (rsp[offset + 2 + (i << 1)] << 8) | rsp[offset + 3 + (i << 1)]; + } + } + + return rc; +} + +/* Write File Record writes nb registers (16-bit) + * from an offset ( also 16-bit stepping ) of a given filenumber + * This implements only the simple case with one subrequest. More complex can be + * created with raw-message. */ + +int modbus_write_file_record(modbus_t *ctx, int file_no, int write_addr, + int write_nb, const uint16_t *src) + +{ + int rc; + int req_length; + int i; + int byte_count; + uint8_t req[MAX_MESSAGE_LENGTH]; + uint8_t rsp[MAX_MESSAGE_LENGTH]; + + if (write_nb > MODBUS_MAX_READ_REGISTERS) { + if (ctx->debug) { + fprintf(stderr, "ERROR Too many registers requested (%d > %d)\n", + write_nb, MODBUS_MAX_READ_REGISTERS); + } + errno = EMBMDATA; + return -1; + } + byte_count = _SUB_REQ_HDR_LENGTH + (write_nb * 2); + req_length = ctx->backend->build_request_basis( + ctx, MODBUS_FC_WRITE_FILE_RECORD, ((byte_count << 8) | 0x06), + file_no, req); + + req[req_length++] = write_addr >> 8; + req[req_length++] = write_addr & 0x00ff; + req[req_length++] = write_nb >> 8; + req[req_length++] = write_nb & 0x00ff; + + for (i = 0; i < write_nb; i++) { + req[req_length++] = src[i] >> 8; + req[req_length++] = src[i] & 0xff; + } + + rc = send_msg(ctx, req, req_length); + if (rc > 0) { + + rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION); + if (rc == -1) + return -1; + + rc = check_confirmation(ctx, req, rsp, rc); + + if (rc == -1) + return -1; + + rc /= 2; /* rc is in byte, we count in uint16-steps */ + + /* It does not make sense to copy back the response to the caller, so + it is left out */ + } + return rc; +} void _modbus_init_common(modbus_t *ctx) { /* Slave and socket are initialized to -1 */ @@ -1771,6 +2063,7 @@ modbus_mapping_t* modbus_mapping_new_start_address( unsigned int start_input_registers, unsigned int nb_input_registers) { modbus_mapping_t *mb_mapping; + int i; mb_mapping = (modbus_mapping_t *)malloc(sizeof(modbus_mapping_t)); if (mb_mapping == NULL) { @@ -1845,6 +2138,10 @@ modbus_mapping_t* modbus_mapping_new_start_address( nb_input_registers * sizeof(uint16_t)); } + for (i = 0; i < MODBUS_MAX_RECORD_FILES; i++) { + mb_mapping->file_registers[i] = NULL; + } + return mb_mapping; } @@ -1855,13 +2152,83 @@ modbus_mapping_t* modbus_mapping_new(int nb_bits, int nb_input_bits, 0, nb_bits, 0, nb_input_bits, 0, nb_registers, 0, nb_input_registers); } +/* Allocates 4 arrays to store bits, input bits, registers and inputs + registers. The pointers are stored in modbus_mapping structure. + Aditionally the array for the file-reference acces are allocated, where the + size for the files is >0 + + The modbus_mapping_new() function shall return the new allocated structure if + successful. Otherwise it shall return NULL and set errno to ENOMEM. */ +modbus_mapping_t *modbus_mapping_new_extend( + int nb_bits, int nb_input_bits, int nb_registers, int nb_input_registers, + uint16_t nb_file_register[MODBUS_MAX_RECORD_FILES]) +{ + int i; + + modbus_mapping_t *mb_mapping = modbus_mapping_new( + nb_bits, nb_input_bits, nb_registers, nb_input_registers); + + if (mb_mapping) { + for (i = 0; i < MODBUS_MAX_RECORD_FILES; i++) { + if (nb_file_register[i]) { + mb_mapping->file_registers[i] = + (uint16_t *)malloc(nb_file_register[i] * sizeof(uint16_t)); + memset(mb_mapping->file_registers[i], 0, + nb_file_register[i] * sizeof(uint16_t)); + } + } + } + + return mb_mapping; +} + +/* Allocates 4 arrays to store bits, input bits, registers and inputs + registers. The pointers are stored in modbus_mapping structure. + Aditionally the array for the file-reference acces are allocated, where the + size for the files is >0 + + The modbus_mapping_new() function shall return the new allocated structure if + successful. Otherwise it shall return NULL and set errno to ENOMEM. */ +MODBUS_API modbus_mapping_t *modbus_mapping_new_start_address_extend( + unsigned int start_bits, unsigned int nb_bits, + unsigned int start_input_bits, unsigned int nb_input_bits, + unsigned int start_registers, unsigned int nb_registers, + unsigned int start_input_registers, unsigned int nb_input_registers, + uint16_t nb_file_registers[MODBUS_MAX_RECORD_FILES]) +{ + + int i; + + modbus_mapping_t *mb_mapping = modbus_mapping_new_start_address( + start_bits, nb_bits, start_input_bits, nb_input_bits, start_registers, + nb_registers, start_input_registers, nb_input_registers); + + if (mb_mapping) { + for (i = 0; i < MODBUS_MAX_RECORD_FILES; i++) { + if (nb_file_registers[i]) { + mb_mapping->file_registers[i] = + (uint16_t *)malloc(nb_file_registers[i] * sizeof(uint16_t)); + memset(mb_mapping->file_registers[i], 0, + nb_file_registers[i] * sizeof(uint16_t)); + } + } + } + return mb_mapping; +} /* Frees the 4 arrays */ void modbus_mapping_free(modbus_mapping_t *mb_mapping) { + int i; + if (mb_mapping == NULL) { return; } + for (i = 0; i < MODBUS_MAX_RECORD_FILES; i++) { + if (mb_mapping->file_registers[i]) { + free(mb_mapping->file_registers[i]); + } + } free(mb_mapping->tab_input_registers); free(mb_mapping->tab_registers); free(mb_mapping->tab_input_bits); diff --git a/src/modbus.h b/src/modbus.h index fda3f02b7..108a06388 100644 --- a/src/modbus.h +++ b/src/modbus.h @@ -68,6 +68,8 @@ MODBUS_BEGIN_DECLS #define MODBUS_FC_WRITE_MULTIPLE_COILS 0x0F #define MODBUS_FC_WRITE_MULTIPLE_REGISTERS 0x10 #define MODBUS_FC_REPORT_SLAVE_ID 0x11 +#define MODBUS_FC_READ_FILE_RECORD 0x14 +#define MODBUS_FC_WRITE_FILE_RECORD 0x15 #define MODBUS_FC_MASK_WRITE_REGISTER 0x16 #define MODBUS_FC_WRITE_AND_READ_REGISTERS 0x17 @@ -93,6 +95,13 @@ MODBUS_BEGIN_DECLS #define MODBUS_MAX_WR_WRITE_REGISTERS 121 #define MODBUS_MAX_WR_READ_REGISTERS 125 +/* While it is allowed for the File Number to be in the range 1 to 0xFFFF, it should be noted that + * interoperability with legacy equipment may be compromised if the File Number is greater than + * 10 (0x0A). + * (chapter 6 section 15 page 35) + */ +#define MODBUS_MAX_RECORD_FILES 0xffff + /* The size of the MODBUS PDU is limited by the size constraint inherited from * the first MODBUS implementation on Serial Line network (max. RS485 ADU = 256 * bytes). Therefore, MODBUS PDU for serial line communication = 256 - Server @@ -167,6 +176,7 @@ typedef struct { uint8_t *tab_input_bits; uint16_t *tab_input_registers; uint16_t *tab_registers; + uint16_t *file_registers[MODBUS_MAX_RECORD_FILES]; } modbus_mapping_t; typedef enum @@ -215,6 +225,13 @@ MODBUS_API int modbus_mask_write_register(modbus_t *ctx, int addr, uint16_t and_ MODBUS_API int modbus_write_and_read_registers(modbus_t *ctx, int write_addr, int write_nb, const uint16_t *src, int read_addr, int read_nb, uint16_t *dest); +MODBUS_API int modbus_read_file_record(modbus_t *ctx, int file_no, + int read_addr, int read_nb, + uint16_t *dest); +MODBUS_API int modbus_write_file_record(modbus_t *ctx, int file_no, + int write_addr, int write_nb, + const uint16_t *src); + MODBUS_API int modbus_report_slave_id(modbus_t *ctx, int max_dest, uint8_t *dest); MODBUS_API modbus_mapping_t* modbus_mapping_new_start_address( @@ -223,8 +240,19 @@ MODBUS_API modbus_mapping_t* modbus_mapping_new_start_address( unsigned int start_registers, unsigned int nb_registers, unsigned int start_input_registers, unsigned int nb_input_registers); +MODBUS_API modbus_mapping_t *modbus_mapping_new_start_address_extend( + unsigned int start_bits, unsigned int nb_bits, + unsigned int start_input_bits, unsigned int nb_input_bits, + unsigned int start_registers, unsigned int nb_registers, + unsigned int start_input_registers, unsigned int nb_input_registers, + uint16_t nb_file_registers[MODBUS_MAX_RECORD_FILES]); + MODBUS_API modbus_mapping_t* modbus_mapping_new(int nb_bits, int nb_input_bits, int nb_registers, int nb_input_registers); +MODBUS_API modbus_mapping_t *modbus_mapping_new_extend( + int nb_bits, int nb_input_bits, int nb_registers, int nb_input_registers, + uint16_t nb_file_registers[MODBUS_MAX_RECORD_FILES]); + MODBUS_API void modbus_mapping_free(modbus_mapping_t *mb_mapping); MODBUS_API int modbus_send_raw_request(modbus_t *ctx, uint8_t *raw_req, int raw_req_length); diff --git a/tests/unit-test-client.c b/tests/unit-test-client.c index fdf5c26ed..c293d798d 100644 --- a/tests/unit-test-client.c +++ b/tests/unit-test-client.c @@ -49,9 +49,10 @@ int main(int argc, char *argv[]) const int NB_REPORT_SLAVE_ID = 10; uint8_t *tab_rp_bits = NULL; uint16_t *tab_rp_registers = NULL; + uint16_t *tab_rp_file = NULL; uint16_t *tab_rp_registers_bad = NULL; modbus_t *ctx = NULL; - int i; + int i, j; uint8_t value; int nb_points; int rc; @@ -66,6 +67,10 @@ int main(int argc, char *argv[]) int success = FALSE; int old_slave; + char devicename[MAX_DEVICENAME_LENGHT] = { + 0, + }; + int baudrate = 115200; if (argc > 1) { if (strcmp(argv[1], "tcp") == 0) { use_backend = TCP; @@ -74,7 +79,9 @@ int main(int argc, char *argv[]) } else if (strcmp(argv[1], "rtu") == 0) { use_backend = RTU; } else { - printf("Usage:\n %s [tcp|tcppi|rtu] - Modbus client for unit testing\n\n", argv[0]); + printf("Usage:\n %s [tcp|tcppi|rtu] ('device-name for rtu') " + "(baudrate for rtu) - Modbus client for unit testing\n\n", + argv[0]); exit(1); } } else { @@ -82,12 +89,21 @@ int main(int argc, char *argv[]) use_backend = TCP; } + if (argc > 2) { + strncpy(devicename, argv[2], MAX_DEVICENAME_LENGHT); + } else { + strncpy(devicename, "/dev/ttyUSB1", MAX_DEVICENAME_LENGHT); + } + if (argc > 3) { + baudrate = atoi(argv[3]); + } + if (use_backend == TCP) { ctx = modbus_new_tcp("127.0.0.1", 1502); } else if (use_backend == TCP_PI) { ctx = modbus_new_tcp_pi("::1", "1502"); } else { - ctx = modbus_new_rtu("/dev/ttyUSB1", 115200, 'N', 8, 1); + ctx = modbus_new_rtu(devicename, baudrate, 'N', 8, 1); } if (ctx == NULL) { fprintf(stderr, "Unable to allocate libmodbus context\n"); @@ -127,6 +143,9 @@ int main(int argc, char *argv[]) tab_rp_registers = (uint16_t *) malloc(nb_points * sizeof(uint16_t)); memset(tab_rp_registers, 0, nb_points * sizeof(uint16_t)); + tab_rp_file = + (uint16_t *)malloc((MAX_REGISTER_PER_QUERY + 2) * sizeof(uint16_t)); + memset(tab_rp_file, 0, (MAX_REGISTER_PER_QUERY + 2) * sizeof(uint16_t)); printf("\nTEST WRITE/READ:\n"); /** COIL BITS **/ @@ -657,6 +676,95 @@ int main(int argc, char *argv[]) printf("* modbus_read_registers at special address: "); ASSERT_TRUE(rc == -1 && errno == EMBXSBUSY, ""); + /** Read File*/ + printf("\nTEST READ FILE RECORD\n"); + { + + rc = modbus_read_file_record(ctx, 1, 10, MAX_REGISTER_PER_QUERY, + tab_rp_file); + printf("modbus_read_file_record: "); + + if (rc < (MAX_REGISTER_PER_QUERY + + 1)) { /* 2 bytes per register + 2 bytes header */ + printf("FAILED (nb points %d)\n", rc); + goto close; + } + + if (((tab_rp_file[0]) >> 8) != MAX_REGISTER_PER_QUERY) { + printf("FAILED Received size wrong(%0X != %0X)\n", + (tab_rp_file[0]) >> 8, MAX_REGISTER_PER_QUERY); + goto close; + } + + if ((tab_rp_file[0] & 0xff) != 0x06) { + printf("FAILED Received Subrequest Reference(%0X != %0X)\n", + tab_rp_file[0] & 0xff, 0x06); + goto close; + } + + for (i = 0; i < MAX_REGISTER_PER_QUERY; i++) { + if (tab_rp_file[i + 1] != 0) { + printf("FAILED (%0X != %0X)\n", tab_rp_file[i + 1], 0); + goto close; + } + } + printf("OK\n"); + } + + printf("\nTEST WRITE FILE RECORD\n"); + { + + for (i = 1; i < 5; i++) { + rc = modbus_write_file_record( + ctx, i, i * 7, UT_FILE_REGISTER_NB - i, &UT_FILE_REGISTER_TAB[i]); + + printf("modbus_write_file_record File_no %d: ", i); + + if (rc < (UT_FILE_REGISTER_NB + 1 - + i)) { /* 2 bytes per register + 2 bytes header*/ + printf("FAILED (nb points %d)\n", rc); + goto close; + } + printf("OK\n"); + } + + for (i = 1; i < 5; i++) { + rc = modbus_read_file_record( + ctx, i, i * 7, UT_FILE_REGISTER_NB - 1, tab_rp_file); + + printf("modbus_read_file_record File_no %d: ", i); + + if (rc < (UT_FILE_REGISTER_NB + 1 - + i)) { /* 2 bytes per register + 2 bytes header */ + printf("FAILED (nb points %d)\n", rc); + goto close; + } + + printf("OK , Verify : "); + + for (j = 0; j < (UT_FILE_REGISTER_NB - i); j++) { + if (tab_rp_file[j + 1] != UT_FILE_REGISTER_TAB[j + i]) { + printf("FAILED (%0X != %0X) at %d \n", tab_rp_file[j + 1], + UT_FILE_REGISTER_TAB[j + i], j); + goto close; + } + } + printf("OK\n"); + } + } + + /* Writing to a non existing file */ + rc = modbus_write_file_record(ctx, 6, 0, UT_FILE_REGISTER_NB, + UT_FILE_REGISTER_TAB); + + printf("modbus_write_file_record File_no 6 "); + + if (rc == -1 && errno == EMBXILVAL) { /* This should fail */ + printf("OK\n"); + } else { + printf("FAILED (nb points %d) errno %d \n", rc, errno); + goto close; + } /** Run a few tests to challenge the server code **/ if (test_server(ctx, use_backend) == -1) { @@ -685,6 +793,7 @@ int main(int argc, char *argv[]) /* Free the memory */ free(tab_rp_bits); free(tab_rp_registers); + free(tab_rp_file); /* Close the connection */ modbus_close(ctx); diff --git a/tests/unit-test-server.c b/tests/unit-test-server.c index 7002b10c6..ca36021f4 100644 --- a/tests/unit-test-server.c +++ b/tests/unit-test-server.c @@ -39,6 +39,10 @@ int main(int argc, char*argv[]) int use_backend; uint8_t *query; int header_length; + char devicename[MAX_DEVICENAME_LENGHT] = { + 0, + }; + int baudrate = 115200; if (argc > 1) { if (strcmp(argv[1], "tcp") == 0) { @@ -48,7 +52,9 @@ int main(int argc, char*argv[]) } else if (strcmp(argv[1], "rtu") == 0) { use_backend = RTU; } else { - printf("Usage:\n %s [tcp|tcppi|rtu] - Modbus server for unit testing\n\n", argv[0]); + printf("Usage:\n %s [tcp|tcppi|rtu] ('device-name for rtu') " + "(baudrate for rtu) - Modbus server for unit testing\n\n", + argv[0]); return -1; } } else { @@ -56,6 +62,15 @@ int main(int argc, char*argv[]) use_backend = TCP; } + if (argc > 2) { + strncpy(devicename, argv[2], MAX_DEVICENAME_LENGHT); + } else { + strncpy(devicename, "/dev/ttyUSB0", MAX_DEVICENAME_LENGHT); + } + if (argc > 3) { + baudrate = atoi(argv[3]); + } + if (use_backend == TCP) { ctx = modbus_new_tcp("127.0.0.1", 1502); query = malloc(MODBUS_TCP_MAX_ADU_LENGTH); @@ -63,7 +78,7 @@ int main(int argc, char*argv[]) ctx = modbus_new_tcp_pi("::0", "1502"); query = malloc(MODBUS_TCP_MAX_ADU_LENGTH); } else { - ctx = modbus_new_rtu("/dev/ttyUSB0", 115200, 'N', 8, 1); + ctx = modbus_new_rtu(devicename, baudrate, 'N', 8, 1); modbus_set_slave(ctx, SERVER_ID); query = malloc(MODBUS_RTU_MAX_ADU_LENGTH); } @@ -71,11 +86,10 @@ int main(int argc, char*argv[]) modbus_set_debug(ctx, TRUE); - mb_mapping = modbus_mapping_new_start_address( - UT_BITS_ADDRESS, UT_BITS_NB, - UT_INPUT_BITS_ADDRESS, UT_INPUT_BITS_NB, - UT_REGISTERS_ADDRESS, UT_REGISTERS_NB_MAX, - UT_INPUT_REGISTERS_ADDRESS, UT_INPUT_REGISTERS_NB); + mb_mapping = modbus_mapping_new_start_address_extend( + UT_BITS_ADDRESS, UT_BITS_NB, UT_INPUT_BITS_ADDRESS, UT_INPUT_BITS_NB, + UT_REGISTERS_ADDRESS, UT_REGISTERS_NB_MAX, UT_INPUT_REGISTERS_ADDRESS, + UT_INPUT_REGISTERS_NB, UT_FILE_REGISTERS_NB); if (mb_mapping == NULL) { fprintf(stderr, "Failed to allocate the mapping: %s\n", modbus_strerror(errno)); diff --git a/tests/unit-test.h.in b/tests/unit-test.h.in index dca826f46..2a295b2ba 100644 --- a/tests/unit-test.h.in +++ b/tests/unit-test.h.in @@ -25,6 +25,9 @@ #define SERVER_ID 17 #define INVALID_SERVER_ID 18 +#define MAX_DEVICENAME_LENGHT 256 +#define MAX_REGISTER_PER_QUERY 100 + const uint16_t UT_BITS_ADDRESS = 0x130; const uint16_t UT_BITS_NB = 0x25; const uint8_t UT_BITS_TAB[] = { 0xCD, 0x6B, 0xB2, 0x0E, 0x1B }; @@ -55,6 +58,9 @@ const uint16_t UT_REGISTERS_NB_SPECIAL = 0x2; const uint16_t UT_INPUT_REGISTERS_ADDRESS = 0x108; const uint16_t UT_INPUT_REGISTERS_NB = 0x1; const uint16_t UT_INPUT_REGISTERS_TAB[] = { 0x000A }; +#define UT_FILE_REGISTER_NB 20 +const uint16_t UT_FILE_REGISTER_TAB[UT_FILE_REGISTER_NB] = { 0x0001,0x0101,0xFF55,0x1234,0x6789,0xABCD,0xDEFF,0x2222,0x3333,0x4444,0x5555,0x0505,0x6060,0xcafe, }; +uint16_t UT_FILE_REGISTERS_NB[MODBUS_MAX_RECORD_FILES] = { 200,500,100,700,50 }; const float UT_REAL = 123456.00;