/*
* Copyright © 2001-2010 Stéphane Raimbault <stephane.raimbault@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser Public License for more details.
*
* You should have received a copy of the GNU Lesser Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* This library implements the Modbus protocol.
* http://libmodbus.org/
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
//#include <config.h>
#include "modbus.h"
#include "modbus-private.h"
/* Internal use */
#define MSG_LENGTH_UNDEFINED -1
/* Exported version */
const unsigned int libmodbus_version_major = LIBMODBUS_VERSION_MAJOR;
const unsigned int libmodbus_version_minor = LIBMODBUS_VERSION_MINOR;
const unsigned int libmodbus_version_micro = LIBMODBUS_VERSION_MICRO;
/* Max between RTU and TCP max adu length (so TCP) */
#define MAX_MESSAGE_LENGTH 260
/* 3 steps are used to parse the query */
typedef enum {
_STEP_FUNCTION,
_STEP_META,
_STEP_DATA
} _step_t;
const char *modbus_strerror(int errnum) {
switch (errnum) {
case EMBXILFUN:
return "Illegal function";
case EMBXILADD:
return "Illegal data address";
case EMBXILVAL:
return "Illegal data value";
case EMBXSFAIL:
return "Slave device or server failure";
case EMBXACK:
return "Acknowledge";
case EMBXSBUSY:
return "Slave device or server is busy";
case EMBXNACK:
return "Negative acknowledge";
case EMBXMEMPAR:
return "Memory parity error";
case EMBXGPATH:
return "Gateway path unavailable";
case EMBXGTAR:
return "Target device failed to respond";
case EMBBADCRC:
return "Invalid CRC";
case EMBBADDATA:
return "Invalid data";
case EMBBADEXC:
return "Invalid exception code";
case EMBMDATA:
return "Too many data";
default:
return strerror(errnum);
}
}
void _error_print(modbus_t *ctx, const char *context)
{
if (ctx->debug) {
fprintf(stderr, "ERROR %s", modbus_strerror(errno));
if (context != NULL) {
fprintf(stderr, ": %s\n", context);
} else {
fprintf(stderr, "\n");
}
}
}
int modbus_flush(modbus_t *ctx)
{
return ctx->backend->flush(ctx);
}
/* Computes the length of the expected response */
static unsigned int compute_response_length_from_request(modbus_t *ctx, uint8_t *req)
{
int length;
int offset;
offset = ctx->backend->header_length;
switch (req[offset]) {
case _FC_READ_COILS:
case _FC_READ_DISCRETE_INPUTS: {
/* Header + nb values (code from write_bits) */
int nb = (req[offset + 3] << 8) | req[offset + 4];
length = 2 + (nb / 8) + ((nb % 8) ? 1 : 0);
}
break;
case _FC_READ_AND_WRITE_REGISTERS:
case _FC_READ_HOLDING_REGISTERS:
case _FC_READ_INPUT_REGISTERS:
/* Header + 2 * nb values */
length = 2 + 2 * (req[offset + 3] << 8 | req[offset + 4]);
break;
case _FC_READ_EXCEPTION_STATUS:
length = 3;
break;
case _FC_REPORT_SLAVE_ID:
/* The response is device specific (the header provides the
length) */
return MSG_LENGTH_UNDEFINED;
default:
length = 5;
}
return offset + length + ctx->backend->checksum_length;
}
/* Sends a request/response */
static int send_msg(modbus_t *ctx, uint8_t *req, int req_length)
{
int rc;
int i;
req_length = ctx->backend->send_msg_pre(req, req_length);
if (ctx->debug) {
for (i = 0; i < req_length; i++)
printf("[%.2X]", req[i]);
printf("\n");
}
/* In recovery mode, the write command will be issued until to be
successful! Disabled by default. */
do {
rc = ctx->backend->send(ctx, req, req_length);
if (rc == -1) {
_error_print(ctx, NULL);
if (ctx->error_recovery &&
(errno == EBADF || errno == ECONNRESET || errno == EPIPE)) {
modbus_close(ctx);
modbus_connect(ctx);
}
}
} while (ctx->error_recovery && rc == -1);
if (rc > 0 && rc != req_length) {
errno = EMBBADDATA;
return -1;
}
return rc;
}
/*
---------- Request Indication ----------
| Client | ---------------------->| Server |
---------- Confirmation Response ----------
*/
typedef enum {
/* Request message on the server side */
MSG_INDICATION,
/* Request message on the client side */
MSG_CONFIRMATION
} msg_type_t;
/* Computes the length to read after the function received */
static uint8_t compute_meta_length_after_function(int function,
msg_type_t msg_type)
{
int length;
if (msg_type == MSG_INDICATION) {
if (function <= _FC_WRITE_SINGLE_REGISTER) {
length = 4;
} else if (function == _FC_WRITE_MULTIPLE_COILS ||
function == _FC_WRITE_MULTIPLE_REGISTERS) {
length = 5;
} else if (function == _FC_READ_AND_WRITE_REGISTERS) {
length = 9;
} else {
/* _FC_READ_EXCEPTION_STATUS, _FC_REPORT_SLAVE_ID */
length = 0;
}
} else {
/* MSG_CONFIRMATION */
switch (function) {
case _FC_WRITE_SINGLE_COIL:
case _FC_WRITE_SINGLE_REGISTER:
case _FC_WRITE_MULTIPLE_COILS:
case _FC_WRITE_MULTIPLE_REGISTERS:
length = 4;
break;
default:
length = 1;
}
}
return length;
}
/* Computes the length to read after the meta information (address, count, etc) */
static int compute_data_length_after_meta(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type)
{
int function = msg[ctx->backend->header_length];
int length;
if (msg_type == MSG_INDICATION) {
switch (function) {
case _FC_WRITE_MULTIPLE_COILS:
case _FC_WRITE_MULTIPLE_REGISTERS:
length = msg[ctx->backend->header_length + 5];
break;
case _FC_READ_AND_WRITE_REGISTERS:
length = msg[ctx->backend->header_length + 9];
break;
default:
length = 0;
}
} else {
/* MSG_CONFIRMATION */
if (function <= _FC_READ_INPUT_REGISTERS ||
function == _FC_REPORT_SLAVE_ID ||
function == _FC_READ_AND_WRITE_REGISTERS) {
length = msg[ctx->backend->header_length + 1];
} else {
length = 0;
}
}
length += ctx->backend->checksum_length;
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).
The function shall return the number of received characters and the received
message in an array of uint8_t if successful. Otherwise it shall return -1
and errno is set to one of the values defined below:
- ECONNRESET
- EMBBADDATA
- EMBUNKEXC
- ETIMEDOUT
- read() or recv() error codes
*/
static int receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type)
{
int rc;
fd_set rfds;
struct timeval tv;
int length_to_read;
uint8_t *p_msg;
int msg_length = 0;
_step_t step;
if (ctx->debug) {
if (msg_type == MSG_INDICATION) {
printf("Waiting for a indication...\n");
} else {
printf("Waiting for a confirmation...\n"