diff --git a/docs/index.md b/docs/index.md index 4e6923642..fc979ecb4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -142,6 +142,7 @@ To read data: - [modbus_read_registers](modbus_read_registers.md) - [modbus_read_input_registers](modbus_read_input_registers.md) - [modbus_report_slave_id](modbus_report_slave_id.md) +- [modbus_read_file_record](modbus_read_file_record.md) To write data: @@ -149,6 +150,7 @@ To write data: - [modbus_write_register](modbus_write_register.md) - [modbus_write_bits](modbus_write_bits.md) - [modbus_write_registers](modbus_write_registers.md) +- [modbus_write_file_record](modbus_write_file_record.md) To write and read data in a single operation: diff --git a/docs/modbus_mapping_new.md b/docs/modbus_mapping_new.md index bedac63fd..cbc42c476 100644 --- a/docs/modbus_mapping_new.md +++ b/docs/modbus_mapping_new.md @@ -7,18 +7,22 @@ modbus_mapping_new - allocate four arrays of bits and registers ## Synopsis ```c -modbus_mapping_t* modbus_mapping_new(int nb_bits, int nb_input_bits, int nb_registers, int nb_input_registers); +modbus_mapping_t* modbus_mapping_new(int nb_bits, int nb_input_bits, + int nb_registers, int nb_input_registers, + int 'nb_files', int 'nb_records'); ``` ## Description -The *modbus_mapping_new()* function shall allocate four arrays to store bits, -input bits, registers and inputs registers. The pointers are stored in -*modbus_mapping_t* structure. All values of the arrays are initialized to zero. +The *modbus_mapping_new()* function shall allocate five arrays to store bits, +input bits, registers, inputs registers, files and file records. The pointers +are stored in *modbus_mapping_t* structure. All values of the arrays are +initialized to zero. This function is equivalent to a call of the [modbus_mapping_new_start_address](modbus_mapping_new_start_address.md) function -with all start addresses to `0`. +with all start addresses to `0` (except the file start address, which is 1, the +smallest value permitted). 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. @@ -42,7 +46,9 @@ mb_mapping = modbus_mapping_new( BITS_ADDRESS + BITS_NB, INPUT_BITS_ADDRESS + INPUT_BITS_NB, REGISTERS_ADDRESS + REGISTERS_NB, - INPUT_REGISTERS_ADDRESS + INPUT_REGISTERS_NB + INPUT_REGISTERS_ADDRESS + INPUT_REGISTERS_NB, + FILES_ADDRESS + FILES_NB, + RECORDS_NB ); if (mb_mapping == NULL) { fprintf( diff --git a/docs/modbus_mapping_new_start_address.md b/docs/modbus_mapping_new_start_address.md index c72cd3d49..90c6b2f49 100644 --- a/docs/modbus_mapping_new_start_address.md +++ b/docs/modbus_mapping_new_start_address.md @@ -11,14 +11,17 @@ 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); + int start_input_registers, int nb_input_registers, + int 'start_files', int 'nb_files', + int 'nb_records'); ``` ## Description -The `modbus_mapping_new_start_address()` function shall allocate four arrays to -store bits, input bits, registers and inputs registers. The pointers are stored -in *modbus_mapping_t* structure. All values of the arrays are initialized to zero. +The `modbus_mapping_new_start_address()` function shall allocate five arrays to +store bits, input bits, registers, inputs registers, files and file records. The +pointers are stored in *modbus_mapping_t* structure. All values of the arrays +are initialized to zero. The different starting addresses make it possible to place the mapping at any address in each address space. This way, you can give access to clients to @@ -26,7 +29,7 @@ values stored at high addresses without allocating memory from the address zero, for example to make available registers from 340 to 349, you can use: ```c -mb_mapping = modbus_mapping_new_start_address(0, 0, 0, 0, 340, 10, 0, 0); +mb_mapping = modbus_mapping_new_start_address(0, 0, 0, 0, 340, 10, 0, 0, 0, 0, 0); ``` The newly created `mb_mapping` will have the following arrays: @@ -35,6 +38,7 @@ The newly created `mb_mapping` will have the following arrays: - `tab_input_bits` set to NULL - `tab_registers` allocated to store 10 registers (`uint16_t`) - `tab_input_registers` set to NULL +- `files` set to NULL The clients can read the first register by using the address 340 in its request. On the server side, you should use the first index of the array to set the value @@ -67,7 +71,8 @@ mb_mapping = modbus_mapping_new_start_address( BITS_ADDRESS, BITS_NB, INPUT_BITS_ADDRESS, INPUT_BITS_NB, REGISTERS_ADDRESS, REGISTERS_NB, - INPUT_REGISTERS_ADDRESS, INPUT_REGISTERS_NB + INPUT_REGISTERS_ADDRESS, INPUT_REGISTERS_NB, + FILES_ADDRESS, FILES_NB, RECORDS_NB ); if (mb_mapping == NULL) { fprintf( diff --git a/docs/modbus_read_file_record.md b/docs/modbus_read_file_record.md new file mode 100644 index 000000000..9c533716a --- /dev/null +++ b/docs/modbus_read_file_record.md @@ -0,0 +1,43 @@ +# modbus_read_file_record + +## Name + +modbus_read_file_record - Read records from a file + +## Synopsis + +```c +int modbus_read_file_record(modbus_t *'ctx', int 'addr', int 'sub_addr', int 'nb', uint16_t * 'dest'); +``` + +## Description + +The *modbus_read_file_record()* function reads `nb` records from file +number `addr`, starting from record position `sub_addr` in the file. +The result of reading is stored in `dest` array as word values (16 bits). You +must take care to allocate enough memory to store the results in `dest` +- (at least `nb * sizeof(uint16_t)`). + +A file is an array of records, each of 16 bits. + +* A ModBus device may have up to 65535 files, addressed 1 to 65535 decimal. +* Each file contains 10000 records, addressed 0000 to 9999 decimal. + +A maximum of 124 records (`nb`) may be retrieved in a single request. + +This function uses the ModBus function code 0x14 (Read File Record). Note that +although the ModBus Specification allows for multiple non-contiguous reads in +the same file to be made in a single request, this function only supports a +single contiguous read request. + + +## Return value + +The function shall return the number of records read (i.e. the value of `nb`) if successful. +Otherwise it shall return -1 and set errno. + + +## See also + +- [modbus_read_register](modbus_read_register.md) +- [modbus_write_file_record](modbus_write_file_record.md) diff --git a/docs/modbus_write_file_record.md b/docs/modbus_write_file_record.md new file mode 100644 index 000000000..1fab55247 --- /dev/null +++ b/docs/modbus_write_file_record.md @@ -0,0 +1,39 @@ +modbus_write_file_record + +## Name + +modbus_write_file_record - Write records to a file + +## Synopsis + +```c +int modbus_write_file_record(modbus_t *'ctx', int 'addr', int 'sub_addr', int 'nb', const uint16_t * 'src'); +``` + +## Description + +The *modbus_write_file_record()* function writes the content of `nb` +records from the `src` array to file number `addr`, starting from position +`sub_addr` in the file. + +A file is an array of records, each of 16 bits. + +* A ModBus device may have up to 65535 files, addressed 1 to 65535 decimal. +* Each file contains 10000 records, addressed 0000 to 9999 decimal. + +A maximum of 124 records (`nb`) may be written in a single request. + +This function uses the ModBus function code 0x15 (Write File Record). Note that +although the ModBus Specification allows for multiple non-contiguous writes in +the same file to be made in a single request, this function only supports a +single contiguous write request. + +## Return value + +The function shall return the number of records written (i.e. the value of `nb`) if successful. +Otherwise it shall return -1 and set errno. + +## See also + +- [modbus_write_register](modbus_write_register.md) +- [modbus_read_file_record](modbus_read_file_record.md) diff --git a/src/modbus-private.h b/src/modbus-private.h index ea83187f7..00cd75b56 100644 --- a/src/modbus-private.h +++ b/src/modbus-private.h @@ -111,6 +111,7 @@ struct _modbus { void _modbus_init_common(modbus_t *ctx); void _error_print(modbus_t *ctx, const char *context); int _modbus_receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type); +void _modbus_free_files(modbus_file_t *files, int nb_files); #ifndef HAVE_STRLCPY size_t strlcpy(char *dest, const char *src, size_t dest_size); diff --git a/src/modbus.c b/src/modbus.c index e3737bb2f..aa4513bd2 100644 --- a/src/modbus.c +++ b/src/modbus.c @@ -151,6 +151,12 @@ 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_READ_FILE_RECORD: + length = 4 + 2 * (req[offset + 7] << 8 | req[offset + 8]); + break; + case MODBUS_FC_WRITE_FILE_RECORD: + length = 9 + 2 * (req[offset + 7] << 8 | req[offset + 8]); + break; case MODBUS_FC_MASK_WRITE_REGISTER: length = 7; break; @@ -280,6 +286,9 @@ static uint8_t compute_meta_length_after_function(int function, msg_type_t msg_t } else if (function == MODBUS_FC_WRITE_MULTIPLE_COILS || function == MODBUS_FC_WRITE_MULTIPLE_REGISTERS) { length = 5; + } else if (function == MODBUS_FC_READ_FILE_RECORD || + function == MODBUS_FC_WRITE_FILE_RECORD) { + length = 8; } else if (function == MODBUS_FC_MASK_WRITE_REGISTER) { length = 6; } else if (function == MODBUS_FC_WRITE_AND_READ_REGISTERS) { @@ -300,6 +309,12 @@ static uint8_t compute_meta_length_after_function(int function, msg_type_t msg_t case MODBUS_FC_MASK_WRITE_REGISTER: length = 6; break; + case MODBUS_FC_READ_FILE_RECORD: + length = 3; + break; + case MODBUS_FC_WRITE_FILE_RECORD: + length = 8; + break; default: length = 1; } @@ -324,6 +339,10 @@ compute_data_length_after_meta(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type) case MODBUS_FC_WRITE_AND_READ_REGISTERS: length = msg[ctx->backend->header_length + 9]; break; + case MODBUS_FC_READ_FILE_RECORD: + case MODBUS_FC_WRITE_FILE_RECORD: + length = msg[ctx->backend->header_length + 1] - 7; + break; default: length = 0; } @@ -333,6 +352,10 @@ compute_data_length_after_meta(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type) 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) { + length = msg[ctx->backend->header_length + 2] - 1; + } else if (function == MODBUS_FC_WRITE_FILE_RECORD) { + length = msg[ctx->backend->header_length + 1] - 7; } else { length = 0; } @@ -655,6 +678,14 @@ static int check_confirmation(modbus_t *ctx, uint8_t *req, uint8_t *rsp, int rsp /* Report slave ID (bytes received) */ req_nb_value = rsp_nb_value = rsp[offset + 1]; break; + case MODBUS_FC_READ_FILE_RECORD: + req_nb_value = (req[offset + 7] << 8) + req[offset + 8]; + rsp_nb_value = (rsp[offset + 2] - 1) / 2; + break; + case MODBUS_FC_WRITE_FILE_RECORD: + req_nb_value = (req[offset + 7] << 8) + req[offset + 8]; + rsp_nb_value = (rsp[offset + 7] << 8) + rsp[offset + 8]; + break; case MODBUS_FC_WRITE_SINGLE_COIL: case MODBUS_FC_WRITE_SINGLE_REGISTER: /* address in request and response must be equal */ @@ -1170,7 +1201,80 @@ int modbus_reply(modbus_t *ctx, } } } break; + case MODBUS_FC_READ_FILE_RECORD: { + address = (req[offset + 3] << 8) + req[offset + 4]; + int mapping_address = address - mb_mapping->start_files; + uint16_t record_addr = (req[offset + 5] << 8) + req[offset + 6]; + uint16_t record_len = (req[offset + 7] << 8) + req[offset + 8]; + if (address < 1 || mapping_address < 0 || mapping_address >= mb_mapping->nb_files) { + rsp_length = response_exception( + ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE, + "Illegal file number %d in read_file_record\n", address); + } else if (record_addr > MODBUS_MAX_FILE_RECORD_NUMBER || + /* index of last record (zero-based) is beyond end of file */ + (record_addr + record_len) > mb_mapping->files[mapping_address].nb_records) { + rsp_length = response_exception( + ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, TRUE, + "Illegal request for record %d, len %d (in file len %d) in read_file_record\n", + record_addr, record_len, mb_mapping->files[mapping_address].nb_records); + } else { + int i; + + rsp_length = ctx->backend->build_response_basis(&sft, rsp); + rsp[rsp_length++] = record_len * 2 + 2; // data len + rsp[rsp_length++] = record_len * 2 + 1; // file resp len + rsp[rsp_length++] = 6; // reference type + for (i = 0; i < record_len; i++) { + rsp[rsp_length++] = mb_mapping->files[mapping_address].records[record_addr + i] >> 8; + rsp[rsp_length++] = mb_mapping->files[mapping_address].records[record_addr + i] & 0xFF; + } + } + } + break; + case MODBUS_FC_WRITE_FILE_RECORD: { + address = (req[offset + 3] << 8) + req[offset + 4]; + int mapping_address = address - mb_mapping->start_files; + uint16_t record_addr = (req[offset + 5] << 8) + req[offset + 6]; + uint16_t record_len = (req[offset + 7] << 8) + req[offset + 8]; + + if (address < 1 || mapping_address < 0 || mapping_address >= mb_mapping->nb_files) { + rsp_length = response_exception( + ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE, + "Illegal file number %d in write_file_record\n", address); + } else if (record_addr > MODBUS_MAX_FILE_RECORD_NUMBER || + record_addr >= mb_mapping->files[mapping_address].nb_records || + /* index of last record (zero-based) is beyond end of file */ + (record_addr + record_len) > mb_mapping->files[mapping_address].nb_records) { + rsp_length = response_exception( + ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, TRUE, + "Illegal record %d, len 0x%0X (max %d) in write_file_record\n", + record_addr, record_len, mb_mapping->files[mapping_address].nb_records); + } else { + int i; + + for (i = 0; i < record_len; i++) { + mb_mapping->files[mapping_address].records[record_addr + i] = (req[offset + 9 + (i *2)] << 8) | + (req[offset + 10 + (i *2)] & 0xFF); + } + + rsp_length = ctx->backend->build_response_basis(&sft, rsp); + rsp[rsp_length++] = record_len * 2 + 7; // data len + rsp[rsp_length++] = 6; // reference type + rsp[rsp_length++] = address >> 8; // file nb + rsp[rsp_length++] = address & 0xFF; + rsp[rsp_length++] = record_addr >> 8; // record nb + rsp[rsp_length++] = record_addr & 0xFF; + rsp[rsp_length++] = record_len >> 8; // record len + rsp[rsp_length++] = record_len & 0xFF; + + for (i = 0; i < record_len; i++) { + rsp[rsp_length++] = mb_mapping->files[mapping_address].records[record_addr + i] >> 8; + rsp[rsp_length++] = mb_mapping->files[mapping_address].records[record_addr + i] & 0xFF; + } + } + } + break; default: rsp_length = response_exception(ctx, &sft, @@ -1424,6 +1528,69 @@ int modbus_read_input_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest) return status; } +int modbus_read_file_record(modbus_t *ctx, int addr, int sub_addr, int nb, uint16_t *dest) +{ + int rc; + int req_length; + uint8_t req[_MIN_REQ_LENGTH + 4]; + uint8_t rsp[MAX_MESSAGE_LENGTH]; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + /* check if record number is within range */ + if (sub_addr > MODBUS_MAX_FILE_RECORD_NUMBER) { + if (ctx->debug) { + fprintf(stderr, + "ERROR Too big file record number (%d > %d)\n", + sub_addr, MODBUS_MAX_FILE_RECORD_NUMBER); + } + errno = EMBMDATA; + return -1; + } + + req_length = ctx->backend->build_request_basis(ctx, + MODBUS_FC_READ_FILE_RECORD, + 0, 0, req); + + req_length -= 4; + req[req_length++] = 7; /* one request, so 7 bytes */ + req[req_length++] = 6; + req[req_length++] = addr >> 8; + req[req_length++] = addr & 0x00ff; + req[req_length++] = sub_addr >> 8; + req[req_length++] = sub_addr & 0x00ff; + req[req_length++] = nb >> 8; + req[req_length++] = nb & 0x00ff; + + rc = send_msg(ctx, req, req_length); + if (rc > 0) { + int offset; + int i; + + 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; + + offset = ctx->backend->header_length; + /* if all went well, copy returned data to caller's pointer, converting + * bytes into 16-bit words. + */ + for (i = 0; i < rc; i++) { + + dest[i] = (rsp[offset + 4 + (i << 1)] << 8) | + rsp[offset + 5 + (i << 1)]; + } + } + + return rc; +} + /* Write a value to the specified register of the remote device. Used by write_bit and write_register */ static int write_single(modbus_t *ctx, int function, int addr, const uint16_t value) @@ -1588,6 +1755,68 @@ int modbus_write_registers(modbus_t *ctx, int addr, int nb, const uint16_t *src) return rc; } +int modbus_write_file_record(modbus_t *ctx, + int addr, + int sub_addr, + int nb, + const uint16_t *src) +{ + int rc; + int i; + int req_length; + int byte_count; + uint8_t req[MAX_MESSAGE_LENGTH]; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + /* check record number is valid */ + if (sub_addr > MODBUS_MAX_FILE_RECORD_NUMBER) { + if (ctx->debug) { + fprintf(stderr, + "ERROR Too big file record number (%d > %d)\n", + sub_addr, MODBUS_MAX_FILE_RECORD_NUMBER); + } + errno = EMBMDATA; + return -1; + } + + req_length = ctx->backend->build_request_basis(ctx, + MODBUS_FC_WRITE_FILE_RECORD, + 0, 0, req); + + byte_count = nb * 2; + req_length -= 4; + req[req_length++] = 7 + byte_count; /* one request header + bytes to write */ + req[req_length++] = 6; + req[req_length++] = addr >> 8; + req[req_length++] = addr & 0x00ff; + req[req_length++] = sub_addr >> 8; + req[req_length++] = sub_addr & 0x00ff; + req[req_length++] = nb >> 8; + req[req_length++] = nb & 0x00ff; + /* pack supplied data into request */ + for (i = 0; i < nb; i++) { + req[req_length++] = src[i] >> 8; + req[req_length++] = src[i] & 0x00FF; + } + + rc = send_msg(ctx, req, req_length); + if (rc > 0) { + uint8_t rsp[MAX_MESSAGE_LENGTH]; + + rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION); + if (rc == -1) + return -1; + + rc = check_confirmation(ctx, req, rsp, rc); + } + + return rc; +} + + int modbus_mask_write_register(modbus_t *ctx, int addr, uint16_t and_mask, @@ -1976,7 +2205,7 @@ int modbus_set_debug(modbus_t *ctx, int flag) return 0; } -/* Allocates 4 arrays to store bits, input bits, registers and inputs +/* Allocates 5 arrays to store bits, input bits, registers and inputs registers. The pointers are stored in modbus_mapping structure. The modbus_mapping_new_start_address() function shall return the new allocated @@ -1989,9 +2218,13 @@ modbus_mapping_t *modbus_mapping_new_start_address(unsigned int start_bits, unsigned int start_registers, unsigned int nb_registers, unsigned int start_input_registers, - unsigned int nb_input_registers) + unsigned int nb_input_registers, + unsigned int start_files, + unsigned int nb_files, + unsigned int nb_records) { modbus_mapping_t *mb_mapping; + unsigned int i; mb_mapping = (modbus_mapping_t *) malloc(sizeof(modbus_mapping_t)); if (mb_mapping == NULL) { @@ -2062,18 +2295,71 @@ modbus_mapping_t *modbus_mapping_new_start_address(unsigned int start_bits, memset(mb_mapping->tab_input_registers, 0, nb_input_registers * sizeof(uint16_t)); } + /* 5X */ + mb_mapping->nb_files = nb_files; + mb_mapping->start_files = start_files; + if (nb_files == 0) { + mb_mapping->files = NULL; + } else { + mb_mapping->files = (modbus_file_t *) malloc(nb_files * sizeof(modbus_file_t)); + if (mb_mapping->files == NULL) { + free(mb_mapping->tab_input_registers); + free(mb_mapping->tab_registers); + free(mb_mapping->tab_input_bits); + free(mb_mapping->tab_bits); + free(mb_mapping); + return NULL; + } + memset(mb_mapping->files, 0, + nb_files * sizeof(modbus_file_t)); + + for (i = 0; i < nb_files; i++) { /* for each file */ + mb_mapping->files[i].nb_records = nb_records; + mb_mapping->files[i].records = (uint16_t *) malloc(nb_records * sizeof(uint16_t)); + if (mb_mapping->files[i].records == NULL) { + free(mb_mapping->files); + free(mb_mapping->tab_input_registers); + free(mb_mapping->tab_registers); + free(mb_mapping->tab_input_bits); + free(mb_mapping->tab_bits); + free(mb_mapping); + return NULL; + } + /* zero the file records */ + memset(mb_mapping->files[i].records, 0, nb_records * sizeof(uint16_t)); + } + } + return mb_mapping; } modbus_mapping_t *modbus_mapping_new(int nb_bits, int nb_input_bits, int nb_registers, - int nb_input_registers) + int nb_input_registers, + int nb_files, + int nb_records) { return modbus_mapping_new_start_address( - 0, nb_bits, 0, nb_input_bits, 0, nb_registers, 0, nb_input_registers); + 0, nb_bits, 0, nb_input_bits, 0, nb_registers, 0, nb_input_registers, + /* Files are numbered from 1 in the specification, so 1 is the lowest + * start allowed. + */ + 1, nb_files, nb_records); } +void _modbus_free_files(modbus_file_t *files, int nb_files) +{ + int i; + + if (files == NULL) { + return; + } + /* Free the file contents records */ + for (i = 0; i < nb_files; i++) { + free(files[i].records); + } +} /* Frees the 4 arrays */ void modbus_mapping_free(modbus_mapping_t *mb_mapping) { @@ -2085,6 +2371,8 @@ void modbus_mapping_free(modbus_mapping_t *mb_mapping) free(mb_mapping->tab_registers); free(mb_mapping->tab_input_bits); free(mb_mapping->tab_bits); + _modbus_free_files(mb_mapping->files, mb_mapping->nb_files); + free(mb_mapping->files); free(mb_mapping); } diff --git a/src/modbus.h b/src/modbus.h index fa7ec4a2f..bf8a14533 100644 --- a/src/modbus.h +++ b/src/modbus.h @@ -70,6 +70,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 @@ -83,6 +85,11 @@ MODBUS_BEGIN_DECLS #define MODBUS_MAX_READ_BITS 2000 #define MODBUS_MAX_WRITE_BITS 1968 +/* Modbus_Application_Protocol_V1_1b.pdf (chapter 6 section 14 page 33) + * Sub-Req. x, Record Number (2 bytes): 1 to 9999 (0x270F) + */ +#define MODBUS_MAX_FILE_RECORD_NUMBER 9999 + /* Modbus_Application_Protocol_V1_1b.pdf (chapter 6 section 3 page 15) * Quantity of Registers to read (2 bytes): 1 to 125 (0x7D) * (chapter 6 section 12 page 31) @@ -156,6 +163,11 @@ extern const unsigned int libmodbus_version_micro; typedef struct _modbus modbus_t; +typedef struct _modbus_file_t { + int nb_records; /* Maximum number of records in file */ + uint16_t *records; /* Pointer to memory for record storage */ +} modbus_file_t; + /*! Memory layout in tab_xxx arrays is processor-endianness. When receiving modbus data, it is converted to processor-endianness, see read_registers(). @@ -169,10 +181,13 @@ typedef struct _modbus_mapping_t { int start_input_registers; int nb_registers; int start_registers; + int nb_files; + int start_files; uint8_t *tab_bits; uint8_t *tab_input_bits; uint16_t *tab_input_registers; uint16_t *tab_registers; + modbus_file_t *files; } modbus_mapping_t; typedef enum { @@ -241,6 +256,16 @@ MODBUS_API int modbus_write_and_read_registers(modbus_t *ctx, 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_write_file_record(modbus_t *ctx, + int addr, + int sub_addr, + int nb, + const uint16_t *src); +MODBUS_API int modbus_read_file_record(modbus_t *ctx, + int addr, + int sub_addr, + int nb, + uint16_t *dest); MODBUS_API modbus_mapping_t * modbus_mapping_new_start_address(unsigned int start_bits, @@ -250,12 +275,17 @@ modbus_mapping_new_start_address(unsigned int start_bits, unsigned int start_registers, unsigned int nb_registers, unsigned int start_input_registers, - unsigned int nb_input_registers); + unsigned int nb_input_registers, + unsigned int start_files, + unsigned int nb_files, + unsigned int nb_records); MODBUS_API modbus_mapping_t *modbus_mapping_new(int nb_bits, int nb_input_bits, int nb_registers, - int nb_input_registers); + int nb_input_registers, + int nb_files, + int nb_records); MODBUS_API void modbus_mapping_free(modbus_mapping_t *mb_mapping); MODBUS_API int diff --git a/tests/bandwidth-server-many-up.c b/tests/bandwidth-server-many-up.c index 544f03715..0a5edcf60 100644 --- a/tests/bandwidth-server-many-up.c +++ b/tests/bandwidth-server-many-up.c @@ -55,7 +55,7 @@ int main(void) ctx = modbus_new_tcp("127.0.0.1", 1502); mb_mapping = - modbus_mapping_new(MODBUS_MAX_READ_BITS, 0, MODBUS_MAX_READ_REGISTERS, 0); + modbus_mapping_new(MODBUS_MAX_READ_BITS, 0, MODBUS_MAX_READ_REGISTERS, 0, 0, 0); if (mb_mapping == NULL) { fprintf(stderr, "Failed to allocate the mapping: %s\n", modbus_strerror(errno)); modbus_free(ctx); diff --git a/tests/bandwidth-server-one.c b/tests/bandwidth-server-one.c index c7ef1ab20..962c14d58 100644 --- a/tests/bandwidth-server-one.c +++ b/tests/bandwidth-server-one.c @@ -59,7 +59,7 @@ int main(int argc, char *argv[]) } mb_mapping = - modbus_mapping_new(MODBUS_MAX_READ_BITS, 0, MODBUS_MAX_READ_REGISTERS, 0); + modbus_mapping_new(MODBUS_MAX_READ_BITS, 0, MODBUS_MAX_READ_REGISTERS, 0, 0, 0); if (mb_mapping == NULL) { fprintf(stderr, "Failed to allocate the mapping: %s\n", modbus_strerror(errno)); modbus_free(ctx); diff --git a/tests/random-test-server.c b/tests/random-test-server.c index a064fa8a3..4e40678d8 100644 --- a/tests/random-test-server.c +++ b/tests/random-test-server.c @@ -22,7 +22,7 @@ int main(void) ctx = modbus_new_tcp("127.0.0.1", 1502); /* modbus_set_debug(ctx, TRUE); */ - mb_mapping = modbus_mapping_new(500, 500, 500, 500); + mb_mapping = modbus_mapping_new(500, 500, 500, 500, 0, 0); if (mb_mapping == NULL) { fprintf(stderr, "Failed to allocate the mapping: %s\n", modbus_strerror(errno)); modbus_free(ctx); diff --git a/tests/unit-test-client.c b/tests/unit-test-client.c index a1813b1eb..90e705a90 100644 --- a/tests/unit-test-client.c +++ b/tests/unit-test-client.c @@ -63,6 +63,7 @@ int main(int argc, char *argv[]) const int NB_REPORT_SLAVE_ID = 2 + 3 + strlen(LIBMODBUS_VERSION_STRING); uint8_t *tab_rp_bits = NULL; uint16_t *tab_rp_registers = NULL; + uint16_t *tab_file_records = NULL; /* table to hold file records */ uint16_t *tab_rp_registers_bad = NULL; modbus_t *ctx = NULL; int i; @@ -310,6 +311,142 @@ int main(int argc, char *argv[]) UT_INPUT_REGISTERS_TAB[i]); } + /** FILE OPERATIONS **/ + printf("\nTEST FILE OPERATIONS\n"); + /* Read the whole of the all the files on server to prove they are empty */ + for (value = UT_FILE_ADDRESS; value < (UT_FILE_ADDRESS + UT_FILE_NB); value++) { + tab_file_records = (uint16_t *) malloc(UT_RECORDS_NB * sizeof(uint16_t)); + memset(tab_file_records, 0, UT_RECORDS_NB * sizeof(uint16_t)); + + rc = modbus_read_file_record(ctx, value, 0, + UT_RECORDS_NB, tab_file_records); + printf("%d/6 modbus_read_file_record: ", value); + ASSERT_TRUE(rc == UT_RECORDS_NB, "FAILED (nb points %d)\n", rc); + + for (i=0; i < UT_RECORDS_NB; i++) { + ASSERT_TRUE(tab_file_records[i] == 0x0, + "%d: FAILED (%0X != %0X)\n", + i, tab_file_records[i], 0x0); + } + free(tab_file_records); + } + + /* Write three records to file 1 on server, starting at record 2 */ + rc = modbus_write_file_record(ctx, UT_FILE, UT_RECORD_ADDRESS, + UT_REGISTERS_NB, UT_REGISTERS_TAB); + printf("3/6 modbus_write_file_record: "); + ASSERT_TRUE(rc == UT_REGISTERS_NB, "FAILED (nb records written %d)\n", rc); + /* Write four more records to file 1 on server, starting at record 5, + * i.e. after previous records */ + nb_points = UT_RECORD_ADDRESS + UT_REGISTERS_NB; + rc = modbus_write_file_record(ctx, UT_FILE, nb_points, + UT_FILE_RECORD_NB, UT_FILE_TAB); + printf("4/6 modbus_write_file_record: "); + ASSERT_TRUE(rc == UT_FILE_RECORD_NB, "FAILED (nb records written %d)\n", rc); + + /* Read back all 7 records and make sure the values are correct. + * Allocate and initialize the memory to store the registers as we need more. + */ + nb_points = UT_REGISTERS_NB + UT_FILE_RECORD_NB; + + tab_file_records = (uint16_t *) malloc(nb_points * sizeof(uint16_t)); + memset(tab_file_records, 0, nb_points * sizeof(uint16_t)); + + rc = modbus_read_file_record(ctx, UT_FILE, UT_RECORD_ADDRESS, + nb_points, tab_file_records); + printf("5/6 modbus_read_file_record: "); + ASSERT_TRUE(rc == nb_points, "FAILED (nb points %d)\n", rc); + + for (i=0; i < nb_points; i++) { + if (i < UT_REGISTERS_NB) { /* first three in UT_REGISTERS_TAB */ + ASSERT_TRUE(tab_file_records[i] == UT_REGISTERS_TAB[i], + "%d: FAILED (%0X != %0X)\n", + i, tab_file_records[i], UT_REGISTERS_TAB[i]); + } else { /* Rest in UT_FILE_TAB */ + /* calculate the index in this table */ + rc = i - UT_REGISTERS_NB; + ASSERT_TRUE(tab_file_records[i] == UT_FILE_TAB[rc], + "%d: FAILED (%0X != %0X)\n", + i, tab_file_records[i], UT_FILE_TAB[rc]); + } + } + free(tab_file_records); + + /* Read the whole of the all the files except the first to prove they are still empty */ + for (value = UT_FILE_ADDRESS + 1; value < (UT_FILE_ADDRESS + UT_FILE_NB); value++) { + tab_file_records = (uint16_t *) malloc(UT_RECORDS_NB * sizeof(uint16_t)); + memset(tab_file_records, 0, UT_RECORDS_NB * sizeof(uint16_t)); + + rc = modbus_read_file_record(ctx, value, 0, + UT_RECORDS_NB, tab_file_records); + printf("%d/6 modbus_read_file_record: ", value + 4); + ASSERT_TRUE(rc == UT_RECORDS_NB, "FAILED (nb points %d)\n", rc); + + for (i=0; i < UT_RECORDS_NB; i++) { + ASSERT_TRUE(tab_file_records[i] == 0x0, + "%d: FAILED (%0X != %0X)\n", + i, tab_file_records[i], 0x0); + } + free(tab_file_records); + } + printf("Intentionally making bad requests to test server error handling ...\n"); + /* Write three records to file 1 on server, starting at record 16 (beyond file end) */ + errno = 0; + rc = modbus_write_file_record(ctx, UT_FILE, UT_RECORDS_NB + 1, + UT_REGISTERS_NB, UT_REGISTERS_TAB); + printf("1/4 modbus_write_file_record: rc %d, err %s, ", rc, modbus_strerror(errno)); + ASSERT_TRUE(((rc == -1) && (errno == EMBXILADD)), "FAILED (incorrect error handling)\n"); + /* Write three records to file 1 on server, starting at record 14 + * (before file end, but write goes over) */ + errno = 0; + rc = modbus_write_file_record(ctx, UT_FILE, UT_RECORDS_NB - 1, + UT_REGISTERS_NB, UT_REGISTERS_TAB); + printf("2/4 modbus_write_file_record: rc %d, err %s, ", rc, modbus_strerror(errno)); + ASSERT_TRUE(((rc == -1) && (errno == EMBXILADD)), "FAILED (incorrect error handling)\n"); + /* Write three records to file 3 on server (which does not exist), + * starting at record 1 (which is OK) */ + errno = 0; + rc = modbus_write_file_record(ctx, UT_FILE + 2, 1, + UT_REGISTERS_NB, UT_REGISTERS_TAB); + printf("3/4 modbus_write_file_record: rc %d, err %s, ", rc, modbus_strerror(errno)); + ASSERT_TRUE(((rc == -1) && (errno == EMBXILADD)), "FAILED (incorrect error handling)\n"); + /* Write three records to file 0 on server (which is not allowed), + * starting at record 1 (which is OK) */ + errno = 0; + rc = modbus_write_file_record(ctx, 0, 1, + UT_REGISTERS_NB, UT_REGISTERS_TAB); + printf("4/4 modbus_write_file_record: rc %d, err %s, ", rc, modbus_strerror(errno)); + ASSERT_TRUE(((rc == -1) && (errno == EMBXILADD)), "FAILED (incorrect error handling)\n"); + + tab_file_records = (uint16_t *) malloc(UT_REGISTERS_NB * sizeof(uint16_t)); + /* Read three records from file 1 on server, starting at record 16 (beyond file end) */ + errno = 0; + rc = modbus_read_file_record(ctx, UT_FILE, UT_RECORDS_NB + 1, + UT_REGISTERS_NB, tab_file_records); + printf("1/4 modbus_read_file_record: rc %d, err %s, ", rc, modbus_strerror(errno)); + ASSERT_TRUE(((rc == -1) && (errno == EMBXILADD)), "FAILED (incorrect error handling)\n"); + /* Read three records to file 1 on server, starting at record 14 + * (before file end, but read goes over) */ + errno = 0; + rc = modbus_read_file_record(ctx, UT_FILE, UT_RECORDS_NB - 1, + UT_REGISTERS_NB, tab_file_records); + printf("2/4 modbus_read_file_record: rc %d, err %s, ", rc, modbus_strerror(errno)); + ASSERT_TRUE(((rc == -1) && (errno == EMBXILADD)), "FAILED (incorrect error handling)\n"); + /* Read three records to file 3 on server (which does not exist), + * starting at record 1 (which is OK) */ + errno = 0; + rc = modbus_read_file_record(ctx, UT_FILE + 2, 1, + UT_REGISTERS_NB, tab_file_records); + printf("3/4 modbus_read_file_record: rc %d, err %s, ", rc, modbus_strerror(errno)); + ASSERT_TRUE(((rc == -1) && (errno == EMBXILADD)), "FAILED (incorrect error handling)\n"); + /* Write three records to file 0 on server (which is not allowed), + * starting at record 1 (which is OK) */ + errno = 0; + rc = modbus_write_file_record(ctx, 0, 1, + UT_REGISTERS_NB, tab_file_records); + printf("4/4 modbus_read_file_record: rc %d, err %s, ", rc, modbus_strerror(errno)); + ASSERT_TRUE(((rc == -1) && (errno == EMBXILADD)), "FAILED (incorrect error handling)\n"); + /* MASKS */ printf("1/1 Write mask: "); rc = modbus_write_register(ctx, UT_REGISTERS_ADDRESS, 0x12); diff --git a/tests/unit-test-server.c b/tests/unit-test-server.c index bb25ba43b..8be876af9 100644 --- a/tests/unit-test-server.c +++ b/tests/unit-test-server.c @@ -103,7 +103,11 @@ int main(int argc, char *argv[]) UT_REGISTERS_ADDRESS, UT_REGISTERS_NB_MAX, UT_INPUT_REGISTERS_ADDRESS, - UT_INPUT_REGISTERS_NB); + UT_INPUT_REGISTERS_NB, + /* 2 files, each 10 records */ + UT_FILE_ADDRESS, + UT_FILE_NB, + UT_RECORDS_NB); if (mb_mapping == NULL) { fprintf(stderr, "Failed to allocate the mapping: %s\n", modbus_strerror(errno)); modbus_free(ctx); diff --git a/tests/unit-test.h.in b/tests/unit-test.h.in index 918cc438a..013000072 100644 --- a/tests/unit-test.h.in +++ b/tests/unit-test.h.in @@ -40,6 +40,9 @@ const uint16_t UT_REGISTERS_ADDRESS = 0x160; const uint16_t UT_REGISTERS_NB = 0x3; const uint16_t UT_REGISTERS_NB_MAX = 0x20; const uint16_t UT_REGISTERS_TAB[] = { 0x022B, 0x0001, 0x0064 }; +const uint16_t UT_FILE_TAB[] = { 0x021B, 0x0002, 0x0063, 0x0042 }; +const uint16_t UT_FILE_RECORD_NB = 0x4; +const uint16_t UT_FILE = 0x1; /* Raise a manual exception when this address is used for the first byte */ const uint16_t UT_REGISTERS_ADDRESS_SPECIAL = 0x170; @@ -59,6 +62,11 @@ const uint16_t UT_INPUT_REGISTERS_ADDRESS = 0x108; const uint16_t UT_INPUT_REGISTERS_NB = 0x1; const uint16_t UT_INPUT_REGISTERS_TAB[] = { 0x000A }; +const uint16_t UT_FILE_ADDRESS = 0x1; /* Number of first file on server. Files are numbered from 1 in the spec. */ +const uint16_t UT_FILE_NB = 0x2; /* How many files available on server */ +const uint16_t UT_RECORDS_NB = 0xF; /* How many records per file */ +const uint16_t UT_RECORD_ADDRESS = 0x2; + /* * This float value is 0x47F12000 (in big-endian format). * In Little-endian(intel) format, it will be stored in memory as follows: