From 4ccbb9fae6b7f1b89c783a382b249c2b47cc0ac5 Mon Sep 17 00:00:00 2001 From: Jakob Bysewski Date: Thu, 2 Jul 2015 16:41:29 +0200 Subject: [PATCH 1/3] Extracted serial interface handling from modbus-rtu* --- src/Makefile.am | 3 + src/modbus-private.h | 2 +- src/modbus-rtu-private.h | 62 +-- src/modbus-rtu.c | 1010 ++--------------------------------- src/modbus-rtu.h | 5 + src/modbus-serial-private.h | 74 +++ src/modbus-serial.c | 991 ++++++++++++++++++++++++++++++++++ src/modbus-serial.h | 50 ++ 8 files changed, 1184 insertions(+), 1013 deletions(-) create mode 100644 src/modbus-serial-private.h create mode 100644 src/modbus-serial.c create mode 100644 src/modbus-serial.h diff --git a/src/Makefile.am b/src/Makefile.am index 8f4280650..d4404e48f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -15,6 +15,9 @@ libmodbus_la_SOURCES = \ modbus.h \ modbus-data.c \ modbus-private.h \ + modbus-serial.c \ + modbus-serial.h \ + modbus-serial-private.h \ modbus-rtu.c \ modbus-rtu.h \ modbus-rtu-private.h \ diff --git a/src/modbus-private.h b/src/modbus-private.h index c5af0f939..b8d5e8afc 100644 --- a/src/modbus-private.h +++ b/src/modbus-private.h @@ -40,7 +40,7 @@ MODBUS_BEGIN_DECLS #define _BYTE_TIMEOUT 500000 typedef enum { - _MODBUS_BACKEND_TYPE_RTU=0, + _MODBUS_BACKEND_TYPE_SERIAL=0, _MODBUS_BACKEND_TYPE_TCP } modbus_backend_type_t; diff --git a/src/modbus-rtu-private.h b/src/modbus-rtu-private.h index 2e8b327bb..87176e5c8 100644 --- a/src/modbus-rtu-private.h +++ b/src/modbus-rtu-private.h @@ -7,17 +7,7 @@ #ifndef MODBUS_RTU_PRIVATE_H #define MODBUS_RTU_PRIVATE_H -#ifndef _MSC_VER -#include -#else -#include "stdint.h" -#endif - -#if defined(_WIN32) -#include -#else -#include -#endif +#include "modbus-serial.h" #define _MODBUS_RTU_HEADER_LENGTH 1 #define _MODBUS_RTU_PRESET_REQ_LENGTH 6 @@ -25,54 +15,4 @@ #define _MODBUS_RTU_CHECKSUM_LENGTH 2 -/* Time waited beetween the RTS switch before transmit data or after transmit - data before to read */ -#define _MODBUS_RTU_TIME_BETWEEN_RTS_SWITCH 10000 - -#if defined(_WIN32) -#if !defined(ENOTSUP) -#define ENOTSUP WSAEOPNOTSUPP -#endif - -/* WIN32: struct containing serial handle and a receive buffer */ -#define PY_BUF_SIZE 512 -struct win32_ser { - /* File handle */ - HANDLE fd; - /* Receive buffer */ - uint8_t buf[PY_BUF_SIZE]; - /* Received chars */ - DWORD n_bytes; -}; -#endif /* _WIN32 */ - -typedef struct _modbus_rtu { - /* Device: "/dev/ttyS0", "/dev/ttyUSB0" or "/dev/tty.USA19*" on Mac OS X. */ - char *device; - /* Bauds: 9600, 19200, 57600, 115200, etc */ - int baud; - /* Data bit */ - uint8_t data_bit; - /* Stop bit */ - uint8_t stop_bit; - /* Parity: 'N', 'O', 'E' */ - char parity; -#if defined(_WIN32) - struct win32_ser w_ser; - DCB old_dcb; -#else - /* Save old termios settings */ - struct termios old_tios; -#endif -#if HAVE_DECL_TIOCSRS485 - int serial_mode; -#endif -#if HAVE_DECL_TIOCM_RTS - int rts; - int onebyte_time; -#endif - /* To handle many slaves on the same link */ - int confirmation_to_ignore; -} modbus_rtu_t; - #endif /* MODBUS_RTU_PRIVATE_H */ diff --git a/src/modbus-rtu.c b/src/modbus-rtu.c index 430a0e197..93a355d35 100644 --- a/src/modbus-rtu.c +++ b/src/modbus-rtu.c @@ -19,13 +19,7 @@ #include "modbus-rtu.h" #include "modbus-rtu-private.h" -#if HAVE_DECL_TIOCSRS485 || HAVE_DECL_TIOCM_RTS -#include -#endif - -#if HAVE_DECL_TIOCSRS485 -#include -#endif +#include "modbus-serial.h" /* Table of CRC values for high-order byte */ static const uint8_t table_crc_hi[] = { @@ -161,197 +155,6 @@ static int _modbus_rtu_send_msg_pre(uint8_t *req, int req_length) return req_length; } -#if defined(_WIN32) - -/* This simple implementation is sort of a substitute of the select() call, - * working this way: the win32_ser_select() call tries to read some data from - * the serial port, setting the timeout as the select() call would. Data read is - * stored into the receive buffer, that is then consumed by the win32_ser_read() - * call. So win32_ser_select() does both the event waiting and the reading, - * while win32_ser_read() only consumes the receive buffer. - */ - -static void win32_ser_init(struct win32_ser *ws) -{ - /* Clear everything */ - memset(ws, 0x00, sizeof(struct win32_ser)); - - /* Set file handle to invalid */ - ws->fd = INVALID_HANDLE_VALUE; -} - -/* FIXME Try to remove length_to_read -> max_len argument, only used by win32 */ -static int win32_ser_select(struct win32_ser *ws, int max_len, - const struct timeval *tv) -{ - COMMTIMEOUTS comm_to; - unsigned int msec = 0; - - /* Check if some data still in the buffer to be consumed */ - if (ws->n_bytes > 0) { - return 1; - } - - /* Setup timeouts like select() would do. - FIXME Please someone on Windows can look at this? - Does it possible to use WaitCommEvent? - When tv is NULL, MAXDWORD isn't infinite! - */ - if (tv == NULL) { - msec = MAXDWORD; - } else { - msec = tv->tv_sec * 1000 + tv->tv_usec / 1000; - if (msec < 1) - msec = 1; - } - - comm_to.ReadIntervalTimeout = msec; - comm_to.ReadTotalTimeoutMultiplier = 0; - comm_to.ReadTotalTimeoutConstant = msec; - comm_to.WriteTotalTimeoutMultiplier = 0; - comm_to.WriteTotalTimeoutConstant = 1000; - SetCommTimeouts(ws->fd, &comm_to); - - /* Read some bytes */ - if ((max_len > PY_BUF_SIZE) || (max_len < 0)) { - max_len = PY_BUF_SIZE; - } - - if (ReadFile(ws->fd, &ws->buf, max_len, &ws->n_bytes, NULL)) { - /* Check if some bytes available */ - if (ws->n_bytes > 0) { - /* Some bytes read */ - return 1; - } else { - /* Just timed out */ - return 0; - } - } else { - /* Some kind of error */ - return -1; - } -} - -static int win32_ser_read(struct win32_ser *ws, uint8_t *p_msg, - unsigned int max_len) -{ - unsigned int n = ws->n_bytes; - - if (max_len < n) { - n = max_len; - } - - if (n > 0) { - memcpy(p_msg, ws->buf, n); - } - - ws->n_bytes -= n; - - return n; -} -#endif - -#if HAVE_DECL_TIOCM_RTS -static void _modbus_rtu_ioctl_rts(int fd, int on) -{ - int flags; - - ioctl(fd, TIOCMGET, &flags); - if (on) { - flags |= TIOCM_RTS; - } else { - flags &= ~TIOCM_RTS; - } - ioctl(fd, TIOCMSET, &flags); -} -#endif - -static ssize_t _modbus_rtu_send(modbus_t *ctx, const uint8_t *req, int req_length) -{ -#if defined(_WIN32) - modbus_rtu_t *ctx_rtu = ctx->backend_data; - DWORD n_bytes = 0; - return (WriteFile(ctx_rtu->w_ser.fd, req, req_length, &n_bytes, NULL)) ? (ssize_t)n_bytes : -1; -#else -#if HAVE_DECL_TIOCM_RTS - modbus_rtu_t *ctx_rtu = ctx->backend_data; - if (ctx_rtu->rts != MODBUS_RTU_RTS_NONE) { - ssize_t size; - - if (ctx->debug) { - fprintf(stderr, "Sending request using RTS signal\n"); - } - - _modbus_rtu_ioctl_rts(ctx->s, ctx_rtu->rts == MODBUS_RTU_RTS_UP); - usleep(_MODBUS_RTU_TIME_BETWEEN_RTS_SWITCH); - - size = write(ctx->s, req, req_length); - - usleep(ctx_rtu->onebyte_time * req_length + _MODBUS_RTU_TIME_BETWEEN_RTS_SWITCH); - _modbus_rtu_ioctl_rts(ctx->s, ctx_rtu->rts != MODBUS_RTU_RTS_UP); - - return size; - } else { -#endif - return write(ctx->s, req, req_length); -#if HAVE_DECL_TIOCM_RTS - } -#endif -#endif -} - -static int _modbus_rtu_receive(modbus_t *ctx, uint8_t *req) -{ - int rc; - modbus_rtu_t *ctx_rtu = ctx->backend_data; - - if (ctx_rtu->confirmation_to_ignore) { - _modbus_receive_msg(ctx, req, MSG_CONFIRMATION); - /* Ignore errors and reset the flag */ - ctx_rtu->confirmation_to_ignore = FALSE; - rc = 0; - if (ctx->debug) { - printf("Confirmation to ignore\n"); - } - } else { - rc = _modbus_receive_msg(ctx, req, MSG_INDICATION); - if (rc == 0) { - /* The next expected message is a confirmation to ignore */ - ctx_rtu->confirmation_to_ignore = TRUE; - } - } - return rc; -} - -static ssize_t _modbus_rtu_recv(modbus_t *ctx, uint8_t *rsp, int rsp_length) -{ -#if defined(_WIN32) - return win32_ser_read(&((modbus_rtu_t *)ctx->backend_data)->w_ser, rsp, rsp_length); -#else - return read(ctx->s, rsp, rsp_length); -#endif -} - -static int _modbus_rtu_flush(modbus_t *); - -static int _modbus_rtu_pre_check_confirmation(modbus_t *ctx, const uint8_t *req, - const uint8_t *rsp, int rsp_length) -{ - /* Check responding slave is the slave we requested (except for broacast - * request) */ - if (req[0] != rsp[0] && req[0] != MODBUS_BROADCAST_ADDRESS) { - if (ctx->debug) { - fprintf(stderr, - "The responding slave %d isn't the requested slave %d\n", - rsp[0], req[0]); - } - errno = EMBBADSLAVE; - return -1; - } else { - return 0; - } -} - /* The check_crc16 function shall return 0 is the message is ignored and the message length if the CRC is valid. Otherwise it shall return -1 and set errno to EMBADCRC. */ @@ -385,731 +188,77 @@ static int _modbus_rtu_check_integrity(modbus_t *ctx, uint8_t *msg, } if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) { - _modbus_rtu_flush(ctx); + _modbus_serial_flush(ctx); } errno = EMBBADCRC; return -1; } } -/* Sets up a serial port for RTU communications */ -static int _modbus_rtu_connect(modbus_t *ctx) -{ -#if defined(_WIN32) - DCB dcb; -#else - struct termios tios; - speed_t speed; - int flags; -#endif - modbus_rtu_t *ctx_rtu = ctx->backend_data; - - if (ctx->debug) { - printf("Opening %s at %d bauds (%c, %d, %d)\n", - ctx_rtu->device, ctx_rtu->baud, ctx_rtu->parity, - ctx_rtu->data_bit, ctx_rtu->stop_bit); - } - -#if defined(_WIN32) - /* Some references here: - * http://msdn.microsoft.com/en-us/library/aa450602.aspx - */ - win32_ser_init(&ctx_rtu->w_ser); - - /* ctx_rtu->device should contain a string like "COMxx:" xx being a decimal - * number */ - ctx_rtu->w_ser.fd = CreateFileA(ctx_rtu->device, - GENERIC_READ | GENERIC_WRITE, - 0, - NULL, - OPEN_EXISTING, - 0, - NULL); - - /* Error checking */ - if (ctx_rtu->w_ser.fd == INVALID_HANDLE_VALUE) { - if (ctx->debug) { - fprintf(stderr, "ERROR Can't open the device %s (LastError %d)\n", - ctx_rtu->device, (int)GetLastError()); - } - return -1; - } - - /* Save params */ - ctx_rtu->old_dcb.DCBlength = sizeof(DCB); - if (!GetCommState(ctx_rtu->w_ser.fd, &ctx_rtu->old_dcb)) { - if (ctx->debug) { - fprintf(stderr, "ERROR Error getting configuration (LastError %d)\n", - (int)GetLastError()); - } - CloseHandle(ctx_rtu->w_ser.fd); - ctx_rtu->w_ser.fd = INVALID_HANDLE_VALUE; - return -1; - } - - /* Build new configuration (starting from current settings) */ - dcb = ctx_rtu->old_dcb; - - /* Speed setting */ - switch (ctx_rtu->baud) { - case 110: - dcb.BaudRate = CBR_110; - break; - case 300: - dcb.BaudRate = CBR_300; - break; - case 600: - dcb.BaudRate = CBR_600; - break; - case 1200: - dcb.BaudRate = CBR_1200; - break; - case 2400: - dcb.BaudRate = CBR_2400; - break; - case 4800: - dcb.BaudRate = CBR_4800; - break; - case 9600: - dcb.BaudRate = CBR_9600; - break; - case 14400: - dcb.BaudRate = CBR_14400; - break; - case 19200: - dcb.BaudRate = CBR_19200; - break; - case 38400: - dcb.BaudRate = CBR_38400; - break; - case 57600: - dcb.BaudRate = CBR_57600; - break; - case 115200: - dcb.BaudRate = CBR_115200; - break; - case 230400: - /* CBR_230400 - not defined */ - dcb.BaudRate = 230400; - break; - case 250000: - dcb.BaudRate = 250000; - break; - case 460800: - dcb.BaudRate = 460800; - break; - case 500000: - dcb.BaudRate = 500000; - break; - case 921600: - dcb.BaudRate = 921600; - break; - case 1000000: - dcb.BaudRate = 1000000; - break; - default: - dcb.BaudRate = CBR_9600; - if (ctx->debug) { - fprintf(stderr, "WARNING Unknown baud rate %d for %s (B9600 used)\n", - ctx_rtu->baud, ctx_rtu->device); - } - } - - /* Data bits */ - switch (ctx_rtu->data_bit) { - case 5: - dcb.ByteSize = 5; - break; - case 6: - dcb.ByteSize = 6; - break; - case 7: - dcb.ByteSize = 7; - break; - case 8: - default: - dcb.ByteSize = 8; - break; - } - - /* Stop bits */ - if (ctx_rtu->stop_bit == 1) - dcb.StopBits = ONESTOPBIT; - else /* 2 */ - dcb.StopBits = TWOSTOPBITS; - - /* Parity */ - if (ctx_rtu->parity == 'N') { - dcb.Parity = NOPARITY; - dcb.fParity = FALSE; - } else if (ctx_rtu->parity == 'E') { - dcb.Parity = EVENPARITY; - dcb.fParity = TRUE; - } else { - /* odd */ - dcb.Parity = ODDPARITY; - dcb.fParity = TRUE; - } - - /* Hardware handshaking left as default settings retrieved */ - - /* No software handshaking */ - dcb.fTXContinueOnXoff = TRUE; - dcb.fOutX = FALSE; - dcb.fInX = FALSE; - - /* Binary mode (it's the only supported on Windows anyway) */ - dcb.fBinary = TRUE; - - /* Don't want errors to be blocking */ - dcb.fAbortOnError = FALSE; - - /* Setup port */ - if (!SetCommState(ctx_rtu->w_ser.fd, &dcb)) { - if (ctx->debug) { - fprintf(stderr, "ERROR Error setting new configuration (LastError %d)\n", - (int)GetLastError()); - } - CloseHandle(ctx_rtu->w_ser.fd); - ctx_rtu->w_ser.fd = INVALID_HANDLE_VALUE; - return -1; - } -#else - /* The O_NOCTTY flag tells UNIX that this program doesn't want - to be the "controlling terminal" for that port. If you - don't specify this then any input (such as keyboard abort - signals and so forth) will affect your process - - Timeouts are ignored in canonical input mode or when the - NDELAY option is set on the file via open or fcntl */ - flags = O_RDWR | O_NOCTTY | O_NDELAY | O_EXCL; -#ifdef O_CLOEXEC - flags |= O_CLOEXEC; -#endif - - ctx->s = open(ctx_rtu->device, flags); - if (ctx->s == -1) { - if (ctx->debug) { - fprintf(stderr, "ERROR Can't open the device %s (%s)\n", - ctx_rtu->device, strerror(errno)); - } - return -1; - } - - /* Save */ - tcgetattr(ctx->s, &ctx_rtu->old_tios); - - memset(&tios, 0, sizeof(struct termios)); - - /* C_ISPEED Input baud (new interface) - C_OSPEED Output baud (new interface) - */ - switch (ctx_rtu->baud) { - case 110: - speed = B110; - break; - case 300: - speed = B300; - break; - case 600: - speed = B600; - break; - case 1200: - speed = B1200; - break; - case 2400: - speed = B2400; - break; - case 4800: - speed = B4800; - break; - case 9600: - speed = B9600; - break; - case 19200: - speed = B19200; - break; - case 38400: - speed = B38400; - break; -#ifdef B57600 - case 57600: - speed = B57600; - break; -#endif -#ifdef B115200 - case 115200: - speed = B115200; - break; -#endif -#ifdef B230400 - case 230400: - speed = B230400; - break; -#endif -#ifdef B460800 - case 460800: - speed = B460800; - break; -#endif -#ifdef B500000 - case 500000: - speed = B500000; - break; -#endif -#ifdef B576000 - case 576000: - speed = B576000; - break; -#endif -#ifdef B921600 - case 921600: - speed = B921600; - break; -#endif -#ifdef B1000000 - case 1000000: - speed = B1000000; - break; -#endif -#ifdef B1152000 - case 1152000: - speed = B1152000; - break; -#endif -#ifdef B1500000 - case 1500000: - speed = B1500000; - break; -#endif -#ifdef B2500000 - case 2500000: - speed = B2500000; - break; -#endif -#ifdef B3000000 - case 3000000: - speed = B3000000; - break; -#endif -#ifdef B3500000 - case 3500000: - speed = B3500000; - break; -#endif -#ifdef B4000000 - case 4000000: - speed = B4000000; - break; -#endif - default: - speed = B9600; - if (ctx->debug) { - fprintf(stderr, - "WARNING Unknown baud rate %d for %s (B9600 used)\n", - ctx_rtu->baud, ctx_rtu->device); - } - } - - /* Set the baud rate */ - if ((cfsetispeed(&tios, speed) < 0) || - (cfsetospeed(&tios, speed) < 0)) { - close(ctx->s); - ctx->s = -1; - return -1; - } - - /* C_CFLAG Control options - CLOCAL Local line - do not change "owner" of port - CREAD Enable receiver - */ - tios.c_cflag |= (CREAD | CLOCAL); - /* CSIZE, HUPCL, CRTSCTS (hardware flow control) */ - - /* Set data bits (5, 6, 7, 8 bits) - CSIZE Bit mask for data bits - */ - tios.c_cflag &= ~CSIZE; - switch (ctx_rtu->data_bit) { - case 5: - tios.c_cflag |= CS5; - break; - case 6: - tios.c_cflag |= CS6; - break; - case 7: - tios.c_cflag |= CS7; - break; - case 8: - default: - tios.c_cflag |= CS8; - break; - } - - /* Stop bit (1 or 2) */ - if (ctx_rtu->stop_bit == 1) - tios.c_cflag &=~ CSTOPB; - else /* 2 */ - tios.c_cflag |= CSTOPB; - - /* PARENB Enable parity bit - PARODD Use odd parity instead of even */ - if (ctx_rtu->parity == 'N') { - /* None */ - tios.c_cflag &=~ PARENB; - } else if (ctx_rtu->parity == 'E') { - /* Even */ - tios.c_cflag |= PARENB; - tios.c_cflag &=~ PARODD; - } else { - /* Odd */ - tios.c_cflag |= PARENB; - tios.c_cflag |= PARODD; - } - - /* Read the man page of termios if you need more information. */ - - /* This field isn't used on POSIX systems - tios.c_line = 0; - */ - - /* C_LFLAG Line options - - ISIG Enable SIGINTR, SIGSUSP, SIGDSUSP, and SIGQUIT signals - ICANON Enable canonical input (else raw) - XCASE Map uppercase \lowercase (obsolete) - ECHO Enable echoing of input characters - ECHOE Echo erase character as BS-SP-BS - ECHOK Echo NL after kill character - ECHONL Echo NL - NOFLSH Disable flushing of input buffers after - interrupt or quit characters - IEXTEN Enable extended functions - ECHOCTL Echo control characters as ^char and delete as ~? - ECHOPRT Echo erased character as character erased - ECHOKE BS-SP-BS entire line on line kill - FLUSHO Output being flushed - PENDIN Retype pending input at next read or input char - TOSTOP Send SIGTTOU for background output - - Canonical input is line-oriented. Input characters are put - into a buffer which can be edited interactively by the user - until a CR (carriage return) or LF (line feed) character is - received. - - Raw input is unprocessed. Input characters are passed - through exactly as they are received, when they are - received. Generally you'll deselect the ICANON, ECHO, - ECHOE, and ISIG options when using raw input - */ - - /* Raw input */ - tios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); - - /* C_IFLAG Input options - - Constant Description - INPCK Enable parity check - IGNPAR Ignore parity errors - PARMRK Mark parity errors - ISTRIP Strip parity bits - IXON Enable software flow control (outgoing) - IXOFF Enable software flow control (incoming) - IXANY Allow any character to start flow again - IGNBRK Ignore break condition - BRKINT Send a SIGINT when a break condition is detected - INLCR Map NL to CR - IGNCR Ignore CR - ICRNL Map CR to NL - IUCLC Map uppercase to lowercase - IMAXBEL Echo BEL on input line too long - */ - if (ctx_rtu->parity == 'N') { - /* None */ - tios.c_iflag &= ~INPCK; - } else { - tios.c_iflag |= INPCK; - } - - /* Software flow control is disabled */ - tios.c_iflag &= ~(IXON | IXOFF | IXANY); - - /* C_OFLAG Output options - OPOST Postprocess output (not set = raw output) - ONLCR Map NL to CR-NL - - ONCLR ant others needs OPOST to be enabled - */ - - /* Raw ouput */ - tios.c_oflag &=~ OPOST; - - /* C_CC Control characters - VMIN Minimum number of characters to read - VTIME Time to wait for data (tenths of seconds) - - UNIX serial interface drivers provide the ability to - specify character and packet timeouts. Two elements of the - c_cc array are used for timeouts: VMIN and VTIME. Timeouts - are ignored in canonical input mode or when the NDELAY - option is set on the file via open or fcntl. - - VMIN specifies the minimum number of characters to read. If - it is set to 0, then the VTIME value specifies the time to - wait for every character read. Note that this does not mean - that a read call for N bytes will wait for N characters to - come in. Rather, the timeout will apply to the first - character and the read call will return the number of - characters immediately available (up to the number you - request). - - If VMIN is non-zero, VTIME specifies the time to wait for - the first character read. If a character is read within the - time given, any read will block (wait) until all VMIN - characters are read. That is, once the first character is - read, the serial interface driver expects to receive an - entire packet of characters (VMIN bytes total). If no - character is read within the time allowed, then the call to - read returns 0. This method allows you to tell the serial - driver you need exactly N bytes and any read call will - return 0 or N bytes. However, the timeout only applies to - the first character read, so if for some reason the driver - misses one character inside the N byte packet then the read - call could block forever waiting for additional input - characters. - - VTIME specifies the amount of time to wait for incoming - characters in tenths of seconds. If VTIME is set to 0 (the - default), reads will block (wait) indefinitely unless the - NDELAY option is set on the port with open or fcntl. - */ - /* Unused because we use open with the NDELAY option */ - tios.c_cc[VMIN] = 0; - tios.c_cc[VTIME] = 0; - - if (tcsetattr(ctx->s, TCSANOW, &tios) < 0) { - close(ctx->s); - ctx->s = -1; - return -1; - } -#endif - - return 0; -} - int modbus_rtu_set_serial_mode(modbus_t *ctx, int mode) { - if (ctx == NULL) { - errno = EINVAL; - return -1; - } - - if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) { -#if HAVE_DECL_TIOCSRS485 - modbus_rtu_t *ctx_rtu = ctx->backend_data; - struct serial_rs485 rs485conf; - memset(&rs485conf, 0x0, sizeof(struct serial_rs485)); - - if (mode == MODBUS_RTU_RS485) { - rs485conf.flags = SER_RS485_ENABLED; - if (ioctl(ctx->s, TIOCSRS485, &rs485conf) < 0) { - return -1; - } - - ctx_rtu->serial_mode = MODBUS_RTU_RS485; - return 0; - } else if (mode == MODBUS_RTU_RS232) { - /* Turn off RS485 mode only if required */ - if (ctx_rtu->serial_mode == MODBUS_RTU_RS485) { - /* The ioctl call is avoided because it can fail on some RS232 ports */ - if (ioctl(ctx->s, TIOCSRS485, &rs485conf) < 0) { - return -1; - } - } - ctx_rtu->serial_mode = MODBUS_RTU_RS232; - return 0; - } -#else - if (ctx->debug) { - fprintf(stderr, "This function isn't supported on your platform\n"); - } - errno = ENOTSUP; - return -1; -#endif - } - - /* Wrong backend and invalid mode specified */ - errno = EINVAL; - return -1; + /* Translate the define to decouple the interfaces */ + int serial_mode; + switch(mode) + { + case MODBUS_RTU_RS232: serial_mode = MODBUS_SERIAL_RS232; break; + case MODBUS_RTU_RS485: serial_mode = MODBUS_SERIAL_RS485; break; + default: serial_mode = -1; break; + } + + return modbus_serial_set_serial_mode(ctx, serial_mode); } int modbus_rtu_get_serial_mode(modbus_t *ctx) { - if (ctx == NULL) { - errno = EINVAL; - return -1; - } - - if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) { -#if HAVE_DECL_TIOCSRS485 - modbus_rtu_t *ctx_rtu = ctx->backend_data; - return ctx_rtu->serial_mode; -#else - if (ctx->debug) { - fprintf(stderr, "This function isn't supported on your platform\n"); - } - errno = ENOTSUP; - return -1; -#endif - } else { - errno = EINVAL; - return -1; - } + int serial_mode = modbus_serial_get_serial_mode(ctx); + + /* Translate the define to decouple the interfaces */ + switch(serial_mode) + { + case MODBUS_SERIAL_RS232: return MODBUS_RTU_RS232; + case MODBUS_SERIAL_RS485: return MODBUS_RTU_RS485; + default: return -1; + } } int modbus_rtu_set_rts(modbus_t *ctx, int mode) { - if (ctx == NULL) { - errno = EINVAL; - return -1; - } - - if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) { -#if HAVE_DECL_TIOCM_RTS - modbus_rtu_t *ctx_rtu = ctx->backend_data; - - if (mode == MODBUS_RTU_RTS_NONE || mode == MODBUS_RTU_RTS_UP || - mode == MODBUS_RTU_RTS_DOWN) { - ctx_rtu->rts = mode; - - /* Set the RTS bit in order to not reserve the RS485 bus */ - _modbus_rtu_ioctl_rts(ctx->s, ctx_rtu->rts != MODBUS_RTU_RTS_UP); - - return 0; - } else { - errno = EINVAL; - return -1; - } -#else - if (ctx->debug) { - fprintf(stderr, "This function isn't supported on your platform\n"); - } - errno = ENOTSUP; - return -1; -#endif - } - /* Wrong backend or invalid mode specified */ - errno = EINVAL; - return -1; + /* Translate the define to decouple the interfaces */ + int serial_mode; + switch(mode) + { + case MODBUS_RTU_RTS_NONE: serial_mode = MODBUS_SERIAL_RTS_NONE; break; + case MODBUS_RTU_RTS_UP: serial_mode = MODBUS_SERIAL_RTS_UP; break; + case MODBUS_RTU_RTS_DOWN: serial_mode = MODBUS_SERIAL_RTS_DOWN; break; + default: serial_mode = -1; break; + } + + return modbus_serial_set_rts(ctx, serial_mode); } int modbus_rtu_get_rts(modbus_t *ctx) { - if (ctx == NULL) { - errno = EINVAL; - return -1; - } - - if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) { -#if HAVE_DECL_TIOCM_RTS - modbus_rtu_t *ctx_rtu = ctx->backend_data; - return ctx_rtu->rts; -#else - if (ctx->debug) { - fprintf(stderr, "This function isn't supported on your platform\n"); - } - errno = ENOTSUP; - return -1; -#endif - } else { - errno = EINVAL; - return -1; - } -} - -static void _modbus_rtu_close(modbus_t *ctx) -{ - /* Restore line settings and close file descriptor in RTU mode */ - modbus_rtu_t *ctx_rtu = ctx->backend_data; - -#if defined(_WIN32) - /* Revert settings */ - if (!SetCommState(ctx_rtu->w_ser.fd, &ctx_rtu->old_dcb) && ctx->debug) { - fprintf(stderr, "ERROR Couldn't revert to configuration (LastError %d)\n", - (int)GetLastError()); - } - - if (!CloseHandle(ctx_rtu->w_ser.fd) && ctx->debug) { - fprintf(stderr, "ERROR Error while closing handle (LastError %d)\n", - (int)GetLastError()); - } -#else - if (ctx->s != -1) { - tcsetattr(ctx->s, TCSANOW, &ctx_rtu->old_tios); - close(ctx->s); - ctx->s = -1; - } -#endif -} - -static int _modbus_rtu_flush(modbus_t *ctx) -{ -#if defined(_WIN32) - modbus_rtu_t *ctx_rtu = ctx->backend_data; - ctx_rtu->w_ser.n_bytes = 0; - return (PurgeComm(ctx_rtu->w_ser.fd, PURGE_RXCLEAR) == FALSE); -#else - return tcflush(ctx->s, TCIOFLUSH); -#endif -} - -static int _modbus_rtu_select(modbus_t *ctx, fd_set *rset, - struct timeval *tv, int length_to_read) -{ - int s_rc; -#if defined(_WIN32) - s_rc = win32_ser_select(&((modbus_rtu_t *)ctx->backend_data)->w_ser, - length_to_read, tv); - if (s_rc == 0) { - errno = ETIMEDOUT; - return -1; - } - - if (s_rc < 0) { - return -1; - } -#else - while ((s_rc = select(ctx->s+1, rset, NULL, NULL, tv)) == -1) { - if (errno == EINTR) { - if (ctx->debug) { - fprintf(stderr, "A non blocked signal was caught\n"); - } - /* Necessary after an error */ - FD_ZERO(rset); - FD_SET(ctx->s, rset); - } else { - return -1; - } - } - - if (s_rc == 0) { - /* Timeout */ - errno = ETIMEDOUT; - return -1; - } -#endif - - return s_rc; + int serial_mode = modbus_serial_get_rts(ctx); + + /* Translate the define to decouple the interfaces */ + switch(serial_mode) + { + case MODBUS_SERIAL_RTS_NONE: return MODBUS_RTU_RTS_NONE; + case MODBUS_SERIAL_RTS_UP: return MODBUS_RTU_RTS_UP; + case MODBUS_SERIAL_RTS_DOWN: return MODBUS_RTU_RTS_DOWN; + default: return -1; + } } static void _modbus_rtu_free(modbus_t *ctx) { - free(((modbus_rtu_t*)ctx->backend_data)->device); - free(ctx->backend_data); - free(ctx); + _modbus_serial_free(ctx->backend_data); + free(ctx->backend_data); + free(ctx); } const modbus_backend_t _modbus_rtu_backend = { - _MODBUS_BACKEND_TYPE_RTU, + _MODBUS_BACKEND_TYPE_SERIAL, _MODBUS_RTU_HEADER_LENGTH, _MODBUS_RTU_CHECKSUM_LENGTH, MODBUS_RTU_MAX_ADU_LENGTH, @@ -1118,15 +267,15 @@ const modbus_backend_t _modbus_rtu_backend = { _modbus_rtu_build_response_basis, _modbus_rtu_prepare_response_tid, _modbus_rtu_send_msg_pre, - _modbus_rtu_send, - _modbus_rtu_receive, - _modbus_rtu_recv, + _modbus_serial_send, + _modbus_serial_receive, + _modbus_serial_recv, _modbus_rtu_check_integrity, - _modbus_rtu_pre_check_confirmation, - _modbus_rtu_connect, - _modbus_rtu_close, - _modbus_rtu_flush, - _modbus_rtu_select, + _modbus_serial_pre_check_confirmation, + _modbus_serial_connect, + _modbus_serial_close, + _modbus_serial_flush, + _modbus_serial_select, _modbus_rtu_free }; @@ -1135,58 +284,17 @@ modbus_t* modbus_new_rtu(const char *device, int stop_bit) { modbus_t *ctx; - modbus_rtu_t *ctx_rtu; - - /* Check device argument */ - if (device == NULL || *device == 0) { - fprintf(stderr, "The device string is empty\n"); - errno = EINVAL; - return NULL; - } - - /* Check baud argument */ - if (baud == 0) { - fprintf(stderr, "The baud rate value must not be zero\n"); - errno = EINVAL; - return NULL; - } ctx = (modbus_t *)malloc(sizeof(modbus_t)); _modbus_init_common(ctx); ctx->backend = &_modbus_rtu_backend; - ctx->backend_data = (modbus_rtu_t *)malloc(sizeof(modbus_rtu_t)); - ctx_rtu = (modbus_rtu_t *)ctx->backend_data; - ctx_rtu->device = NULL; - - /* Device name and \0 */ - ctx_rtu->device = (char *)malloc((strlen(device) + 1) * sizeof(char)); - strcpy(ctx_rtu->device, device); - ctx_rtu->baud = baud; - if (parity == 'N' || parity == 'E' || parity == 'O') { - ctx_rtu->parity = parity; - } else { - modbus_free(ctx); - errno = EINVAL; - return NULL; + ctx->backend_data = modbus_serial_init(device, baud, parity, data_bit, stop_bit); + if(ctx->backend_data == NULL) + { + modbus_free(ctx); + return NULL; } - ctx_rtu->data_bit = data_bit; - ctx_rtu->stop_bit = stop_bit; - -#if HAVE_DECL_TIOCSRS485 - /* The RS232 mode has been set by default */ - ctx_rtu->serial_mode = MODBUS_RTU_RS232; -#endif - -#if HAVE_DECL_TIOCM_RTS - /* The RTS use has been set by default */ - ctx_rtu->rts = MODBUS_RTU_RTS_NONE; - - /* Calculate estimated time in micro second to send one byte */ - ctx_rtu->onebyte_time = (1000 * 1000) * (1 + data_bit + (parity == 'N' ? 0 : 1) + stop_bit) / baud; -#endif - - ctx_rtu->confirmation_to_ignore = FALSE; return ctx; } diff --git a/src/modbus-rtu.h b/src/modbus-rtu.h index bb0b91367..5a1272580 100644 --- a/src/modbus-rtu.h +++ b/src/modbus-rtu.h @@ -19,6 +19,11 @@ MODBUS_BEGIN_DECLS MODBUS_API modbus_t* modbus_new_rtu(const char *device, int baud, char parity, int data_bit, int stop_bit); +/* + * The get/set serial mode and get/set rts methods should be deprecated, + * as they just forward to the serial backend. + * Currently they are kept so the known interface does not change. + */ #define MODBUS_RTU_RS232 0 #define MODBUS_RTU_RS485 1 diff --git a/src/modbus-serial-private.h b/src/modbus-serial-private.h new file mode 100644 index 000000000..4d6373cec --- /dev/null +++ b/src/modbus-serial-private.h @@ -0,0 +1,74 @@ +/* + * Copyright © 2001-2011 Stéphane Raimbault + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#ifndef MODBUS_SERIAL_PRIVATE_H +#define MODBUS_SERIAL_PRIVATE_H + +#ifndef _MSC_VER +#include +#else +#include "stdint.h" +#endif + +#if defined(_WIN32) +#include +#else +#include +#endif + +#include "modbus-serial.h" + +/* Time waited beetween the RTS switch before transmit data or after transmit + data before to read */ +#define _MODBUS_SERIAL_TIME_BETWEEN_RTS_SWITCH 10000 + +#if defined(_WIN32) +#if !defined(ENOTSUP) +#define ENOTSUP WSAEOPNOTSUPP +#endif + +/* WIN32: struct containing serial handle and a receive buffer */ +#define PY_BUF_SIZE 512 +struct win32_ser { + /* File handle */ + HANDLE fd; + /* Receive buffer */ + uint8_t buf[PY_BUF_SIZE]; + /* Received chars */ + DWORD n_bytes; +}; +#endif /* _WIN32 */ + +struct _modbus_serial { + /* Device: "/dev/ttyS0", "/dev/ttyUSB0" or "/dev/tty.USA19*" on Mac OS X. */ + char *device; + /* Bauds: 9600, 19200, 57600, 115200, etc */ + int baud; + /* Data bit */ + uint8_t data_bit; + /* Stop bit */ + uint8_t stop_bit; + /* Parity: 'N', 'O', 'E' */ + char parity; +#if defined(_WIN32) + struct win32_ser w_ser; + DCB old_dcb; +#else + /* Save old termios settings */ + struct termios old_tios; +#endif +#if HAVE_DECL_TIOCSRS485 + int serial_mode; +#endif +#if HAVE_DECL_TIOCM_RTS + int rts; + int onebyte_time; +#endif + /* To handle many slaves on the same link */ + int confirmation_to_ignore; +}; + +#endif /* MODBUS_SERIAL_PRIVATE_H */ diff --git a/src/modbus-serial.c b/src/modbus-serial.c new file mode 100644 index 000000000..78e19f95e --- /dev/null +++ b/src/modbus-serial.c @@ -0,0 +1,991 @@ +/* + * Copyright © 2001-2011 Stéphane Raimbault + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include +#include +#include +#include +#include +#ifndef _MSC_VER +#include +#endif +#include + +#include "modbus-private.h" + +#include "modbus-serial.h" +#include "modbus-serial-private.h" + +#if HAVE_DECL_TIOCSRS485 || HAVE_DECL_TIOCM_RTS +#include +#endif + +#if HAVE_DECL_TIOCSRS485 +#include +#endif + +#if defined(_WIN32) + +/* This simple implementation is sort of a substitute of the select() call, + * working this way: the win32_ser_select() call tries to read some data from + * the serial port, setting the timeout as the select() call would. Data read is + * stored into the receive buffer, that is then consumed by the win32_ser_read() + * call. So win32_ser_select() does both the event waiting and the reading, + * while win32_ser_read() only consumes the receive buffer. + */ + +void win32_ser_init(struct win32_ser *ws) +{ + /* Clear everything */ + memset(ws, 0x00, sizeof(struct win32_ser)); + + /* Set file handle to invalid */ + ws->fd = INVALID_HANDLE_VALUE; +} + +/* FIXME Try to remove length_to_read -> max_len argument, only used by win32 */ +int win32_ser_select(struct win32_ser *ws, int max_len, + const struct timeval *tv) +{ + COMMTIMEOUTS comm_to; + unsigned int msec = 0; + + /* Check if some data still in the buffer to be consumed */ + if (ws->n_bytes > 0) { + return 1; + } + + /* Setup timeouts like select() would do. + FIXME Please someone on Windows can look at this? + Does it possible to use WaitCommEvent? + When tv is NULL, MAXDWORD isn't infinite! + */ + if (tv == NULL) { + msec = MAXDWORD; + } else { + msec = tv->tv_sec * 1000 + tv->tv_usec / 1000; + if (msec < 1) + msec = 1; + } + + comm_to.ReadIntervalTimeout = msec; + comm_to.ReadTotalTimeoutMultiplier = 0; + comm_to.ReadTotalTimeoutConstant = msec; + comm_to.WriteTotalTimeoutMultiplier = 0; + comm_to.WriteTotalTimeoutConstant = 1000; + SetCommTimeouts(ws->fd, &comm_to); + + /* Read some bytes */ + if ((max_len > PY_BUF_SIZE) || (max_len < 0)) { + max_len = PY_BUF_SIZE; + } + + if (ReadFile(ws->fd, &ws->buf, max_len, &ws->n_bytes, NULL)) { + /* Check if some bytes available */ + if (ws->n_bytes > 0) { + /* Some bytes read */ + return 1; + } else { + /* Just timed out */ + return 0; + } + } else { + /* Some kind of error */ + return -1; + } +} + +int win32_ser_read(struct win32_ser *ws, uint8_t *p_msg, + unsigned int max_len) +{ + unsigned int n = ws->n_bytes; + + if (max_len < n) { + n = max_len; + } + + if (n > 0) { + memcpy(p_msg, ws->buf, n); + } + + ws->n_bytes -= n; + + return n; +} +#endif + +#if HAVE_DECL_TIOCM_RTS +void _modbus_serial_ioctl_rts(int fd, int on) +{ + int flags; + + ioctl(fd, TIOCMGET, &flags); + if (on) { + flags |= TIOCM_RTS; + } else { + flags &= ~TIOCM_RTS; + } + ioctl(fd, TIOCMSET, &flags); +} +#endif + +ssize_t _modbus_serial_send(modbus_t *ctx, const uint8_t *req, int req_length) +{ +#if defined(_WIN32) + modbus_serial_t *ctx_serial = ctx->backend_data; + DWORD n_bytes = 0; + return (WriteFile(ctx_serial->w_ser.fd, req, req_length, &n_bytes, NULL)) ? (ssize_t)n_bytes : -1; +#else +#if HAVE_DECL_TIOCM_RTS + modbus_serial_t *ctx_serial = ctx->backend_data; + if (ctx_serial->rts != _MODBUS_BACKEND_TYPE_SERIAL) { + ssize_t size; + + if (ctx->debug) { + fprintf(stderr, "Sending request using RTS signal\n"); + } + + _modbus_serial_ioctl_rts(ctx->s, ctx_serial->rts == MODBUS_SERIAL_RTS_UP); + usleep(_MODBUS_SERIAL_TIME_BETWEEN_RTS_SWITCH); + + size = write(ctx->s, req, req_length); + + usleep(ctx_serial->onebyte_time * req_length + _MODBUS_SERIAL_TIME_BETWEEN_RTS_SWITCH); + _modbus_serial_ioctl_rts(ctx->s, ctx_serial->rts != MODBUS_SERIAL_RTS_UP); + + return size; + } else { +#endif + return write(ctx->s, req, req_length); +#if HAVE_DECL_TIOCM_RTS + } +#endif +#endif +} + +int _modbus_serial_receive(modbus_t *ctx, uint8_t *req) +{ + int rc; + modbus_serial_t *ctx_serial = ctx->backend_data; + + if (ctx_serial->confirmation_to_ignore) { + _modbus_receive_msg(ctx, req, MSG_CONFIRMATION); + /* Ignore errors and reset the flag */ + ctx_serial->confirmation_to_ignore = FALSE; + rc = 0; + if (ctx->debug) { + printf("Confirmation to ignore\n"); + } + } else { + rc = _modbus_receive_msg(ctx, req, MSG_INDICATION); + if (rc == 0) { + /* The next expected message is a confirmation to ignore */ + ctx_serial->confirmation_to_ignore = TRUE; + } + } + return rc; +} + +ssize_t _modbus_serial_recv(modbus_t *ctx, uint8_t *rsp, int rsp_length) +{ +#if defined(_WIN32) + return win32_ser_read(&((modbus_serial_t *)ctx->backend_data)->w_ser, rsp, rsp_length); +#else + return read(ctx->s, rsp, rsp_length); +#endif +} + +int _modbus_serial_flush(modbus_t *); + +int _modbus_serial_pre_check_confirmation(modbus_t *ctx, const uint8_t *req, + const uint8_t *rsp, int rsp_length) +{ + /* Check responding slave is the slave we requested (except for broacast + * request) */ + if (req[0] != rsp[0] && req[0] != MODBUS_BROADCAST_ADDRESS) { + if (ctx->debug) { + fprintf(stderr, + "The responding slave %d isn't the requested slave %d\n", + rsp[0], req[0]); + } + errno = EMBBADSLAVE; + return -1; + } else { + return 0; + } +} + +/* Sets up a serial port for RTU communications */ +int _modbus_serial_connect(modbus_t *ctx) +{ +#if defined(_WIN32) + DCB dcb; +#else + struct termios tios; + speed_t speed; + int flags; +#endif + modbus_serial_t *ctx_serial = ctx->backend_data; + + if (ctx->debug) { + printf("Opening %s at %d bauds (%c, %d, %d)\n", + ctx_serial->device, ctx_serial->baud, ctx_serial->parity, + ctx_serial->data_bit, ctx_serial->stop_bit); + } + +#if defined(_WIN32) + /* Some references here: + * http://msdn.microsoft.com/en-us/library/aa450602.aspx + */ + win32_ser_init(&ctx_serial->w_ser); + + /* ctx_serial->device should contain a string like "COMxx:" xx being a decimal + * number */ + ctx_serial->w_ser.fd = CreateFileA(ctx_serial->device, + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + OPEN_EXISTING, + 0, + NULL); + + /* Error checking */ + if (ctx_serial->w_ser.fd == INVALID_HANDLE_VALUE) { + if (ctx->debug) { + fprintf(stderr, "ERROR Can't open the device %s (LastError %d)\n", + ctx_serial->device, (int)GetLastError()); + } + return -1; + } + + /* Save params */ + ctx_serial->old_dcb.DCBlength = sizeof(DCB); + if (!GetCommState(ctx_serial->w_ser.fd, &ctx_serial->old_dcb)) { + if (ctx->debug) { + fprintf(stderr, "ERROR Error getting configuration (LastError %d)\n", + (int)GetLastError()); + } + CloseHandle(ctx_serial->w_ser.fd); + ctx_serial->w_ser.fd = INVALID_HANDLE_VALUE; + return -1; + } + + /* Build new configuration (starting from current settings) */ + dcb = ctx_serial->old_dcb; + + /* Speed setting */ + switch (ctx_serial->baud) { + case 110: + dcb.BaudRate = CBR_110; + break; + case 300: + dcb.BaudRate = CBR_300; + break; + case 600: + dcb.BaudRate = CBR_600; + break; + case 1200: + dcb.BaudRate = CBR_1200; + break; + case 2400: + dcb.BaudRate = CBR_2400; + break; + case 4800: + dcb.BaudRate = CBR_4800; + break; + case 9600: + dcb.BaudRate = CBR_9600; + break; + case 14400: + dcb.BaudRate = CBR_14400; + break; + case 19200: + dcb.BaudRate = CBR_19200; + break; + case 38400: + dcb.BaudRate = CBR_38400; + break; + case 57600: + dcb.BaudRate = CBR_57600; + break; + case 115200: + dcb.BaudRate = CBR_115200; + break; + case 230400: + /* CBR_230400 - not defined */ + dcb.BaudRate = 230400; + break; + case 250000: + dcb.BaudRate = 250000; + break; + case 460800: + dcb.BaudRate = 460800; + break; + case 500000: + dcb.BaudRate = 500000; + break; + case 921600: + dcb.BaudRate = 921600; + break; + case 1000000: + dcb.BaudRate = 1000000; + break; + default: + dcb.BaudRate = CBR_9600; + if (ctx->debug) { + fprintf(stderr, "WARNING Unknown baud rate %d for %s (B9600 used)\n", + ctx_serial->baud, ctx_serial->device); + } + } + + /* Data bits */ + switch (ctx_serial->data_bit) { + case 5: + dcb.ByteSize = 5; + break; + case 6: + dcb.ByteSize = 6; + break; + case 7: + dcb.ByteSize = 7; + break; + case 8: + default: + dcb.ByteSize = 8; + break; + } + + /* Stop bits */ + if (ctx_serial->stop_bit == 1) + dcb.StopBits = ONESTOPBIT; + else /* 2 */ + dcb.StopBits = TWOSTOPBITS; + + /* Parity */ + if (ctx_serial->parity == 'N') { + dcb.Parity = NOPARITY; + dcb.fParity = FALSE; + } else if (ctx_serial->parity == 'E') { + dcb.Parity = EVENPARITY; + dcb.fParity = TRUE; + } else { + /* odd */ + dcb.Parity = ODDPARITY; + dcb.fParity = TRUE; + } + + /* Hardware handshaking left as default settings retrieved */ + + /* No software handshaking */ + dcb.fTXContinueOnXoff = TRUE; + dcb.fOutX = FALSE; + dcb.fInX = FALSE; + + /* Binary mode (it's the only supported on Windows anyway) */ + dcb.fBinary = TRUE; + + /* Don't want errors to be blocking */ + dcb.fAbortOnError = FALSE; + + /* Setup port */ + if (!SetCommState(ctx_serial->w_ser.fd, &dcb)) { + if (ctx->debug) { + fprintf(stderr, "ERROR Error setting new configuration (LastError %d)\n", + (int)GetLastError()); + } + CloseHandle(ctx_serial->w_ser.fd); + ctx_serial->w_ser.fd = INVALID_HANDLE_VALUE; + return -1; + } +#else + /* The O_NOCTTY flag tells UNIX that this program doesn't want + to be the "controlling terminal" for that port. If you + don't specify this then any input (such as keyboard abort + signals and so forth) will affect your process + + Timeouts are ignored in canonical input mode or when the + NDELAY option is set on the file via open or fcntl */ + flags = O_RDWR | O_NOCTTY | O_NDELAY | O_EXCL; +#ifdef O_CLOEXEC + flags |= O_CLOEXEC; +#endif + + ctx->s = open(ctx_serial->device, flags); + if (ctx->s == -1) { + if (ctx->debug) { + fprintf(stderr, "ERROR Can't open the device %s (%s)\n", + ctx_serial->device, strerror(errno)); + } + return -1; + } + + /* Save */ + tcgetattr(ctx->s, &ctx_serial->old_tios); + + memset(&tios, 0, sizeof(struct termios)); + + /* C_ISPEED Input baud (new interface) + C_OSPEED Output baud (new interface) + */ + switch (ctx_serial->baud) { + case 110: + speed = B110; + break; + case 300: + speed = B300; + break; + case 600: + speed = B600; + break; + case 1200: + speed = B1200; + break; + case 2400: + speed = B2400; + break; + case 4800: + speed = B4800; + break; + case 9600: + speed = B9600; + break; + case 19200: + speed = B19200; + break; + case 38400: + speed = B38400; + break; +#ifdef B57600 + case 57600: + speed = B57600; + break; +#endif +#ifdef B115200 + case 115200: + speed = B115200; + break; +#endif +#ifdef B230400 + case 230400: + speed = B230400; + break; +#endif +#ifdef B460800 + case 460800: + speed = B460800; + break; +#endif +#ifdef B500000 + case 500000: + speed = B500000; + break; +#endif +#ifdef B576000 + case 576000: + speed = B576000; + break; +#endif +#ifdef B921600 + case 921600: + speed = B921600; + break; +#endif +#ifdef B1000000 + case 1000000: + speed = B1000000; + break; +#endif +#ifdef B1152000 + case 1152000: + speed = B1152000; + break; +#endif +#ifdef B1500000 + case 1500000: + speed = B1500000; + break; +#endif +#ifdef B2500000 + case 2500000: + speed = B2500000; + break; +#endif +#ifdef B3000000 + case 3000000: + speed = B3000000; + break; +#endif +#ifdef B3500000 + case 3500000: + speed = B3500000; + break; +#endif +#ifdef B4000000 + case 4000000: + speed = B4000000; + break; +#endif + default: + speed = B9600; + if (ctx->debug) { + fprintf(stderr, + "WARNING Unknown baud rate %d for %s (B9600 used)\n", + ctx_serial->baud, ctx_serial->device); + } + } + + /* Set the baud rate */ + if ((cfsetispeed(&tios, speed) < 0) || + (cfsetospeed(&tios, speed) < 0)) { + close(ctx->s); + ctx->s = -1; + return -1; + } + + /* C_CFLAG Control options + CLOCAL Local line - do not change "owner" of port + CREAD Enable receiver + */ + tios.c_cflag |= (CREAD | CLOCAL); + /* CSIZE, HUPCL, CRTSCTS (hardware flow control) */ + + /* Set data bits (5, 6, 7, 8 bits) + CSIZE Bit mask for data bits + */ + tios.c_cflag &= ~CSIZE; + switch (ctx_serial->data_bit) { + case 5: + tios.c_cflag |= CS5; + break; + case 6: + tios.c_cflag |= CS6; + break; + case 7: + tios.c_cflag |= CS7; + break; + case 8: + default: + tios.c_cflag |= CS8; + break; + } + + /* Stop bit (1 or 2) */ + if (ctx_serial->stop_bit == 1) + tios.c_cflag &=~ CSTOPB; + else /* 2 */ + tios.c_cflag |= CSTOPB; + + /* PARENB Enable parity bit + PARODD Use odd parity instead of even */ + if (ctx_serial->parity == 'N') { + /* None */ + tios.c_cflag &=~ PARENB; + } else if (ctx_serial->parity == 'E') { + /* Even */ + tios.c_cflag |= PARENB; + tios.c_cflag &=~ PARODD; + } else { + /* Odd */ + tios.c_cflag |= PARENB; + tios.c_cflag |= PARODD; + } + + /* Read the man page of termios if you need more information. */ + + /* This field isn't used on POSIX systems + tios.c_line = 0; + */ + + /* C_LFLAG Line options + + ISIG Enable SIGINTR, SIGSUSP, SIGDSUSP, and SIGQUIT signals + ICANON Enable canonical input (else raw) + XCASE Map uppercase \lowercase (obsolete) + ECHO Enable echoing of input characters + ECHOE Echo erase character as BS-SP-BS + ECHOK Echo NL after kill character + ECHONL Echo NL + NOFLSH Disable flushing of input buffers after + interrupt or quit characters + IEXTEN Enable extended functions + ECHOCTL Echo control characters as ^char and delete as ~? + ECHOPRT Echo erased character as character erased + ECHOKE BS-SP-BS entire line on line kill + FLUSHO Output being flushed + PENDIN Retype pending input at next read or input char + TOSTOP Send SIGTTOU for background output + + Canonical input is line-oriented. Input characters are put + into a buffer which can be edited interactively by the user + until a CR (carriage return) or LF (line feed) character is + received. + + Raw input is unprocessed. Input characters are passed + through exactly as they are received, when they are + received. Generally you'll deselect the ICANON, ECHO, + ECHOE, and ISIG options when using raw input + */ + + /* Raw input */ + tios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + + /* C_IFLAG Input options + + Constant Description + INPCK Enable parity check + IGNPAR Ignore parity errors + PARMRK Mark parity errors + ISTRIP Strip parity bits + IXON Enable software flow control (outgoing) + IXOFF Enable software flow control (incoming) + IXANY Allow any character to start flow again + IGNBRK Ignore break condition + BRKINT Send a SIGINT when a break condition is detected + INLCR Map NL to CR + IGNCR Ignore CR + ICRNL Map CR to NL + IUCLC Map uppercase to lowercase + IMAXBEL Echo BEL on input line too long + */ + if (ctx_serial->parity == 'N') { + /* None */ + tios.c_iflag &= ~INPCK; + } else { + tios.c_iflag |= INPCK; + } + + /* Software flow control is disabled */ + tios.c_iflag &= ~(IXON | IXOFF | IXANY); + + /* C_OFLAG Output options + OPOST Postprocess output (not set = raw output) + ONLCR Map NL to CR-NL + + ONCLR ant others needs OPOST to be enabled + */ + + /* Raw ouput */ + tios.c_oflag &=~ OPOST; + + /* C_CC Control characters + VMIN Minimum number of characters to read + VTIME Time to wait for data (tenths of seconds) + + UNIX serial interface drivers provide the ability to + specify character and packet timeouts. Two elements of the + c_cc array are used for timeouts: VMIN and VTIME. Timeouts + are ignored in canonical input mode or when the NDELAY + option is set on the file via open or fcntl. + + VMIN specifies the minimum number of characters to read. If + it is set to 0, then the VTIME value specifies the time to + wait for every character read. Note that this does not mean + that a read call for N bytes will wait for N characters to + come in. Rather, the timeout will apply to the first + character and the read call will return the number of + characters immediately available (up to the number you + request). + + If VMIN is non-zero, VTIME specifies the time to wait for + the first character read. If a character is read within the + time given, any read will block (wait) until all VMIN + characters are read. That is, once the first character is + read, the serial interface driver expects to receive an + entire packet of characters (VMIN bytes total). If no + character is read within the time allowed, then the call to + read returns 0. This method allows you to tell the serial + driver you need exactly N bytes and any read call will + return 0 or N bytes. However, the timeout only applies to + the first character read, so if for some reason the driver + misses one character inside the N byte packet then the read + call could block forever waiting for additional input + characters. + + VTIME specifies the amount of time to wait for incoming + characters in tenths of seconds. If VTIME is set to 0 (the + default), reads will block (wait) indefinitely unless the + NDELAY option is set on the port with open or fcntl. + */ + /* Unused because we use open with the NDELAY option */ + tios.c_cc[VMIN] = 0; + tios.c_cc[VTIME] = 0; + + if (tcsetattr(ctx->s, TCSANOW, &tios) < 0) { + close(ctx->s); + ctx->s = -1; + return -1; + } +#endif + + return 0; +} + +int modbus_serial_set_serial_mode(modbus_t *ctx, int mode) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_SERIAL) { +#if HAVE_DECL_TIOCSRS485 + modbus_serial_t *ctx_serial = ctx->backend_data; + struct serial_rs485 rs485conf; + memset(&rs485conf, 0x0, sizeof(struct serial_rs485)); + + if (mode == MODBUS_RTU_RS485) { + rs485conf.flags = SER_RS485_ENABLED; + if (ioctl(ctx->s, TIOCSRS485, &rs485conf) < 0) { + return -1; + } + + ctx_serial->serial_mode = MODBUS_RTU_RS485; + return 0; + } else if (mode == MODBUS_RTU_RS232) { + /* Turn off RS485 mode only if required */ + if (ctx_serial->serial_mode == MODBUS_RTU_RS485) { + /* The ioctl call is avoided because it can fail on some RS232 ports */ + if (ioctl(ctx->s, TIOCSRS485, &rs485conf) < 0) { + return -1; + } + } + ctx_serial->serial_mode = MODBUS_RTU_RS232; + return 0; + } +#else + if (ctx->debug) { + fprintf(stderr, "This function isn't supported on your platform\n"); + } + errno = ENOTSUP; + return -1; +#endif + } + + /* Wrong backend and invalid mode specified */ + errno = EINVAL; + return -1; +} + +int modbus_serial_get_serial_mode(modbus_t *ctx) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_SERIAL) { +#if HAVE_DECL_TIOCSRS485 + modbus_serial_t *ctx_serial = ctx->backend_data; + return ctx_serial->serial_mode; +#else + if (ctx->debug) { + fprintf(stderr, "This function isn't supported on your platform\n"); + } + errno = ENOTSUP; + return -1; +#endif + } else { + errno = EINVAL; + return -1; + } +} + +int modbus_serial_set_rts(modbus_t *ctx, int mode) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_SERIAL) { +#if HAVE_DECL_TIOCM_RTS + modbus_serial_t *ctx_serial = ctx->backend_data; + + if (mode == MODBUS_RTU_RTS_NONE || mode == MODBUS_RTU_RTS_UP || + mode == MODBUS_RTU_RTS_DOWN) { + ctx_serial->rts = mode; + + /* Set the RTS bit in order to not reserve the RS485 bus */ + _modbus_serial_ioctl_rts(ctx->s, ctx_serial->rts != MODBUS_RTU_RTS_UP); + + return 0; + } else { + errno = EINVAL; + return -1; + } +#else + if (ctx->debug) { + fprintf(stderr, "This function isn't supported on your platform\n"); + } + errno = ENOTSUP; + return -1; +#endif + } + /* Wrong backend or invalid mode specified */ + errno = EINVAL; + return -1; +} + +int modbus_serial_get_rts(modbus_t *ctx) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_SERIAL) { +#if HAVE_DECL_TIOCM_RTS + modbus_serial_t *ctx_serial = ctx->backend_data; + return ctx_serial->rts; +#else + if (ctx->debug) { + fprintf(stderr, "This function isn't supported on your platform\n"); + } + errno = ENOTSUP; + return -1; +#endif + } else { + errno = EINVAL; + return -1; + } +} + +void _modbus_serial_close(modbus_t *ctx) +{ + /* Restore line settings and close file descriptor in RTU mode */ + modbus_serial_t *ctx_serial = ctx->backend_data; + +#if defined(_WIN32) + /* Revert settings */ + if (!SetCommState(ctx_serial->w_ser.fd, &ctx_serial->old_dcb) && ctx->debug) { + fprintf(stderr, "ERROR Couldn't revert to configuration (LastError %d)\n", + (int)GetLastError()); + } + + if (!CloseHandle(ctx_serial->w_ser.fd) && ctx->debug) { + fprintf(stderr, "ERROR Error while closing handle (LastError %d)\n", + (int)GetLastError()); + } +#else + if (ctx->s != -1) { + tcsetattr(ctx->s, TCSANOW, &ctx_serial->old_tios); + close(ctx->s); + ctx->s = -1; + } +#endif +} + +int _modbus_serial_flush(modbus_t *ctx) +{ +#if defined(_WIN32) + modbus_serial_t *ctx_serial = ctx->backend_data; + ctx_serial->w_ser.n_bytes = 0; + return (PurgeComm(ctx_serial->w_ser.fd, PURGE_RXCLEAR) == FALSE); +#else + return tcflush(ctx->s, TCIOFLUSH); +#endif +} + +int _modbus_serial_select(modbus_t *ctx, fd_set *rset, + struct timeval *tv, int length_to_read) +{ + int s_rc; +#if defined(_WIN32) + s_rc = win32_ser_select(&((modbus_serial_t *)ctx->backend_data)->w_ser, + length_to_read, tv); + if (s_rc == 0) { + errno = ETIMEDOUT; + return -1; + } + + if (s_rc < 0) { + return -1; + } +#else + while ((s_rc = select(ctx->s+1, rset, NULL, NULL, tv)) == -1) { + if (errno == EINTR) { + if (ctx->debug) { + fprintf(stderr, "A non blocked signal was caught\n"); + } + /* Necessary after an error */ + FD_ZERO(rset); + FD_SET(ctx->s, rset); + } else { + return -1; + } + } + + if (s_rc == 0) { + /* Timeout */ + errno = ETIMEDOUT; + return -1; + } +#endif + + return s_rc; +} + +void _modbus_serial_free(modbus_serial_t *ctx_serial) +{ + if(ctx_serial) free(ctx_serial->device); + free(ctx_serial); +} + +modbus_serial_t* modbus_serial_init(const char *device, + int baud, char parity, int data_bit, int stop_bit) +{ + /* Check device argument */ + if (device == NULL || *device == 0) { + fprintf(stderr, "The device string is empty\n"); + errno = EINVAL; + return NULL; + } + + /* Check baud argument */ + if (baud == 0) { + fprintf(stderr, "The baud rate value must not be zero\n"); + errno = EINVAL; + return NULL; + } + + /* Check parity argument */ + if (parity != 'N' && parity != 'E' && parity != 'O') { + fprintf(stderr, "The parity given is not one of 'N', 'E' or 'O' and thus invalid\n"); + errno = EINVAL; + return NULL; + } + + modbus_serial_t *ctx_serial = (modbus_serial_t*)malloc(sizeof(modbus_serial_t)); + + ctx_serial->device = NULL; + + /* Device name and \0 */ + ctx_serial->device = (char *)malloc((strlen(device) + 1) * sizeof(char)); + strcpy(ctx_serial->device, device); + + ctx_serial->baud = baud; + ctx_serial->parity = parity; + ctx_serial->data_bit = data_bit; + ctx_serial->stop_bit = stop_bit; + +#if HAVE_DECL_TIOCSRS485 + /* The RS232 mode has been set by default */ + ctx_serial->serial_mode = MODBUS_RTU_RS232; +#endif + +#if HAVE_DECL_TIOCM_RTS + /* The RTS use has been set by default */ + ctx_serial->rts = MODBUS_RTU_RTS_NONE; + + /* Calculate estimated time in micro second to send one byte */ + ctx_serial->onebyte_time = (1000 * 1000) * (1 + data_bit + (parity == 'N' ? 0 : 1) + stop_bit) / baud; +#endif + + ctx_serial->confirmation_to_ignore = FALSE; + + return ctx_serial; +} + diff --git a/src/modbus-serial.h b/src/modbus-serial.h new file mode 100644 index 000000000..111e62eee --- /dev/null +++ b/src/modbus-serial.h @@ -0,0 +1,50 @@ +/* + * Copyright © 2001-2011 Stéphane Raimbault + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#ifndef MODBUS_SERIAL_H +#define MODBUS_SERIAL_H + +typedef struct _modbus_serial modbus_serial_t; + +#define MODBUS_SERIAL_RS232 0 +#define MODBUS_SERIAL_RS485 1 + +int modbus_serial_set_serial_mode(modbus_t *ctx, int mode); +int modbus_serial_get_serial_mode(modbus_t *ctx); + +#define MODBUS_SERIAL_RTS_NONE 0 +#define MODBUS_SERIAL_RTS_UP 1 +#define MODBUS_SERIAL_RTS_DOWN 2 + +int modbus_serial_set_rts(modbus_t *ctx, int mode); +int modbus_serial_get_rts(modbus_t *ctx); + +#if defined(_WIN32) + +void win32_ser_init(struct win32_ser *ws); +int win32_ser_select(struct win32_ser *ws, int max_len, const struct timeval *tv); +int win32_ser_read(struct win32_ser *ws, uint8_t *p_msg, unsigned int max_len); + +#endif + +#if HAVE_DECL_TIOCM_RTS +void _modbus_serial_ioctl_rts(int fd, int on); +#endif + +ssize_t _modbus_serial_send(modbus_t *ctx, const uint8_t *req, int req_length); +int _modbus_serial_receive(modbus_t *ctx, uint8_t *req); +ssize_t _modbus_serial_recv(modbus_t *ctx, uint8_t *rsp, int rsp_length); +int _modbus_serial_flush(modbus_t *); +int _modbus_serial_pre_check_confirmation(modbus_t *ctx, const uint8_t *req, const uint8_t *rsp, int rsp_length); +int _modbus_serial_connect(modbus_t *ctx); +void _modbus_serial_close(modbus_t *ctx); +int _modbus_serial_flush(modbus_t *ctx); +int _modbus_serial_select(modbus_t *ctx, fd_set *rset, struct timeval *tv, int length_to_read); + +void _modbus_serial_free(modbus_serial_t *serial_ctx); +modbus_serial_t* modbus_serial_init(const char *device, int baud, char parity, int data_bit, int stop_bit); + +#endif /* MODBUS_SERIAL_H */ From 64ed817412a9174e83f71ebe09c905f620bbe776 Mon Sep 17 00:00:00 2001 From: Jakob Bysewski Date: Tue, 7 Jul 2015 11:18:25 +0200 Subject: [PATCH 2/3] Created modbus-ascii --- src/Makefile.am | 3 + src/modbus-ascii-private.h | 16 ++ src/modbus-ascii.c | 334 +++++++++++++++++++++++++++++++++++++ src/modbus-ascii.h | 44 +++++ 4 files changed, 397 insertions(+) create mode 100644 src/modbus-ascii-private.h create mode 100644 src/modbus-ascii.c create mode 100644 src/modbus-ascii.h diff --git a/src/Makefile.am b/src/Makefile.am index d4404e48f..baab4eef2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -21,6 +21,9 @@ libmodbus_la_SOURCES = \ modbus-rtu.c \ modbus-rtu.h \ modbus-rtu-private.h \ + modbus-ascii.c \ + modbus-ascii.h \ + modbus-ascii-private.h \ modbus-tcp.c \ modbus-tcp.h \ modbus-tcp-private.h \ diff --git a/src/modbus-ascii-private.h b/src/modbus-ascii-private.h new file mode 100644 index 000000000..9e550818d --- /dev/null +++ b/src/modbus-ascii-private.h @@ -0,0 +1,16 @@ +/* + * Copyright © 2001-2011 Stéphane Raimbault + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#ifndef MODBUS_ASCII_PRIVATE_H +#define MODBUS_ASCII_PRIVATE_H + +#define _MODBUS_ASCII_HEADER_LENGTH 2 +#define _MODBUS_ASCII_PRESET_REQ_LENGTH 7 +#define _MODBUS_ASCII_PRESET_RSP_LENGTH 2 + +#define _MODBUS_ASCII_CHECKSUM_LENGTH 3 /* lrc8 + \r\n */ + +#endif /* MODBUS_ASCII_PRIVATE_H */ diff --git a/src/modbus-ascii.c b/src/modbus-ascii.c new file mode 100644 index 000000000..2392f8b3d --- /dev/null +++ b/src/modbus-ascii.c @@ -0,0 +1,334 @@ +/* + * Copyright © 2001-2011 Stéphane Raimbault + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include +#include +#include +#include +#include +#ifndef _MSC_VER +#include +#endif +#include + +#include "modbus-private.h" + +#include "modbus-ascii.h" +#include "modbus-ascii-private.h" + +#include "modbus-serial.h" + +/* Define the slave ID of the remote device to talk in master mode or set the + * internal slave ID in slave mode */ +static int _modbus_set_slave(modbus_t *ctx, int slave) +{ + /* Broadcast address is 0 (MODBUS_BROADCAST_ADDRESS) */ + if (slave >= 0 && slave <= 247) { + ctx->slave = slave; + } else { + errno = EINVAL; + return -1; + } + + return 0; +} + +static char nibble_to_hex_ascii(uint8_t nibble) { + char c; + if (nibble < 10) { + c = nibble + '0'; + } else { + c = nibble - 10 + 'A'; + } + return c; +} + +static uint8_t hex_ascii_to_nibble(char digit) { + if (digit >= '0' && digit <= '9' ) { + return digit - '0'; + } else if (digit >= 'A' && digit <= 'F' ) { + return digit - 'A' + 10; + } else if (digit >= 'a' && digit <= 'f' ) { + return digit - 'a' + 10; + } + return 0xff; +} + +/* Builds a ascii request header */ +static int _modbus_ascii_build_request_basis(modbus_t *ctx, int function, + int addr, int nb, + uint8_t *req) +{ + assert(ctx->slave != -1); + + req[0] = ':'; + req[1] = ctx->slave; + req[2] = function; + req[3] = addr >> 8; + req[4] = addr & 0x00ff; + req[5] = nb >> 8; + req[6] = nb & 0x00ff; + + return _MODBUS_ASCII_PRESET_REQ_LENGTH; +} + +/* Builds a ascii response header */ +static int _modbus_ascii_build_response_basis(sft_t *sft, uint8_t *rsp) +{ + rsp[0] = sft->slave; + rsp[1] = sft->function; + + return _MODBUS_ASCII_PRESET_RSP_LENGTH; +} + +/* calculate the Longitudinal Redundancy Checking (LRC) + * see http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf - + * 2.5.2.2 LRC Checking Page 18 + * and + * 6.2.1 LRC Generation Page 38 + **/ +static uint8_t lrc8(uint8_t *buffer, uint16_t buffer_length) +{ + uint8_t lrc = 0; + while(buffer_length--) lrc += *buffer++; + return (uint8_t) -lrc; /* return twos complement */ +} + +static int _modbus_ascii_prepare_response_tid(const uint8_t *req, int *req_length) +{ + (*req_length) -= _MODBUS_ASCII_CHECKSUM_LENGTH; + /* No TID */ + return 0; +} + +static int _modbus_ascii_send_msg_pre(uint8_t *req, int req_length) +{ + uint8_t lrc = lrc8(req + 1, req_length - 1); /* skip colon */ + req[req_length++] = lrc; + req[req_length++] = '\r'; + req[req_length++] = '\n'; + + return req_length; +} + +static int _modbus_ascii_check_integrity(modbus_t *ctx, uint8_t *msg, const int msg_length) +{ + uint8_t lrc; + char colon = msg[0]; + int slave = msg[1]; + + /* check for leading colon*/ + if (colon != ':') { + if (ctx->debug) { + fprintf(stderr, "No leading colon\n"); + } + /* Following call to check_confirmation handles this error */ + return 0; + } + + /* Filter on the Modbus unit identifier (slave) in ascii mode to avoid useless + * CRC computing. */ + if (slave != ctx->slave && slave != MODBUS_BROADCAST_ADDRESS) { + if (ctx->debug) { + fprintf(stderr, "Request for slave %d ignored (not %d)\n", slave, ctx->slave); + } + /* Following call to check_confirmation handles this error */ + return 0; + } + + lrc = lrc8(msg + 1, msg_length - 3); /* strip ":" and "\r\n" */ + /* Check CRC of msg */ + if (lrc == 0) { + return msg_length; + } else { + if (ctx->debug) { + fprintf(stderr, "ERROR lrc received %0X != 0\n", lrc); + } + + if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) { + _modbus_serial_flush(ctx); + } + errno = EMBBADCRC; + return -1; + } +} + +static ssize_t _modbus_ascii_recv_char(modbus_t *ctx, uint8_t *p_char_rsp, uint8_t with_select) +{ + int rc; + fd_set rset; + struct timeval tv; + + if (with_select) { + FD_ZERO(&rset); + FD_SET(ctx->s, &rset); + + if (ctx->byte_timeout.tv_sec >= 0 && ctx->byte_timeout.tv_usec >= 0) { + /* Byte timeout can be disabled with negative values */ + tv.tv_sec = ctx->byte_timeout.tv_sec; + tv.tv_usec = ctx->byte_timeout.tv_usec; + } else { + tv.tv_sec = ctx->response_timeout.tv_sec; + tv.tv_usec = ctx->response_timeout.tv_usec; + } + + rc = _modbus_serial_select(ctx, &rset, &tv, 1); + if (rc == -1) { + return 0; + } + } + + return _modbus_serial_recv(ctx, p_char_rsp, 1); +} + +/* We're reading character by character (ignoring how many bytes the caller + * requested), translating between Modbus RTU and Modbus ASCII as needed. + * Maybe it's better to try to read as many bytes as possible and then convert? + */ +static ssize_t _modbus_ascii_recv(modbus_t *ctx, uint8_t *rsp, int rsp_length) +{ + uint8_t char_resp; + uint8_t nibble_resp; + + if (_modbus_ascii_recv_char(ctx, &char_resp, 0) != 1) { + return 0; + } + + if (char_resp == ':' || char_resp == '\r' || char_resp == '\n') { + *rsp = char_resp; + } else { + nibble_resp = hex_ascii_to_nibble(char_resp); + *rsp = nibble_resp << 4; + if (_modbus_ascii_recv_char(ctx, &char_resp, 1) != 1) { + return 0; + } + nibble_resp = hex_ascii_to_nibble(char_resp); + *rsp |= nibble_resp; + } + return 1; +} + +static ssize_t _modbus_ascii_send(modbus_t *ctx, const uint8_t *req, int req_length) +{ + uint8_t ascii_req[3 + (MODBUS_ASCII_MAX_ADU_LENGTH * 2)]; + + ssize_t i, j = 0; + for (i = 0; i < req_length; i++) { + if (req[i] == ':' || req[i] == '\r' || req[i] == '\n') { + ascii_req[j++] = req[i]; + } else { + ascii_req[j++] = nibble_to_hex_ascii(req[i] >> 4); + ascii_req[j++] = nibble_to_hex_ascii(req[i] & 0x0f); + } + } + ascii_req[j] = '\0'; + + ssize_t size = _modbus_serial_send(ctx, ascii_req, j); + return ((size - 3) / 2) +3; +} + +int modbus_ascii_set_serial_mode(modbus_t *ctx, int mode) +{ + /* Translate the define to decouple the interfaces */ + int serial_mode; + switch(mode) + { + case MODBUS_ASCII_RS232: serial_mode = MODBUS_SERIAL_RS232; break; + case MODBUS_ASCII_RS485: serial_mode = MODBUS_SERIAL_RS485; break; + default: serial_mode = -1; break; + } + + return modbus_serial_set_serial_mode(ctx, serial_mode); +} + +int modbus_ascii_get_serial_mode(modbus_t *ctx) +{ + int serial_mode = modbus_serial_get_serial_mode(ctx); + + /* Translate the define to decouple the interfaces */ + switch(serial_mode) + { + case MODBUS_SERIAL_RS232: return MODBUS_ASCII_RS232; + case MODBUS_SERIAL_RS485: return MODBUS_ASCII_RS485; + default: return -1; + } +} + +int modbus_ascii_set_rts(modbus_t *ctx, int mode) +{ + /* Translate the define to decouple the interfaces */ + int serial_mode; + switch(mode) + { + case MODBUS_ASCII_RTS_NONE: serial_mode = MODBUS_SERIAL_RTS_NONE; break; + case MODBUS_ASCII_RTS_UP: serial_mode = MODBUS_SERIAL_RTS_UP; break; + case MODBUS_ASCII_RTS_DOWN: serial_mode = MODBUS_SERIAL_RTS_DOWN; break; + default: serial_mode = -1; break; + } + + return modbus_serial_set_rts(ctx, serial_mode); +} + +int modbus_ascii_get_rts(modbus_t *ctx) +{ + int serial_mode = modbus_serial_get_rts(ctx); + + /* Translate the define to decouple the interfaces */ + switch(serial_mode) + { + case MODBUS_SERIAL_RTS_NONE: return MODBUS_ASCII_RTS_NONE; + case MODBUS_SERIAL_RTS_UP: return MODBUS_ASCII_RTS_UP; + case MODBUS_SERIAL_RTS_DOWN: return MODBUS_ASCII_RTS_DOWN; + default: return -1; + } +} + +static void _modbus_ascii_free(modbus_t *ctx) { + _modbus_serial_free(ctx->backend_data); + free(ctx); +} + +const modbus_backend_t _modbus_ascii_backend = { + _MODBUS_BACKEND_TYPE_SERIAL, + _MODBUS_ASCII_HEADER_LENGTH, + _MODBUS_ASCII_CHECKSUM_LENGTH, + MODBUS_ASCII_MAX_ADU_LENGTH, + _modbus_set_slave, + _modbus_ascii_build_request_basis, + _modbus_ascii_build_response_basis, + _modbus_ascii_prepare_response_tid, + _modbus_ascii_send_msg_pre, + _modbus_ascii_send, + _modbus_serial_receive, + _modbus_ascii_recv, + _modbus_ascii_check_integrity, + _modbus_serial_pre_check_confirmation, + _modbus_serial_connect, + _modbus_serial_close, + _modbus_serial_flush, + _modbus_serial_select, + _modbus_ascii_free +}; + +modbus_t* modbus_new_ascii(const char *device, + int baud, char parity, int data_bit, + int stop_bit) +{ + modbus_t *ctx; + + ctx = (modbus_t *)malloc(sizeof(modbus_t)); + _modbus_init_common(ctx); + ctx->backend = &_modbus_ascii_backend; + + ctx->backend_data = modbus_serial_init(device, baud, parity, data_bit, stop_bit); + if(ctx->backend_data == NULL) + { + modbus_free(ctx); + return NULL; + } + + return ctx; +} diff --git a/src/modbus-ascii.h b/src/modbus-ascii.h new file mode 100644 index 000000000..472cd0b01 --- /dev/null +++ b/src/modbus-ascii.h @@ -0,0 +1,44 @@ +/* + * Copyright © 2001-2011 Stéphane Raimbault + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#ifndef MODBUS_ASCII_H +#define MODBUS_ASCII_H + +#include "modbus.h" + +MODBUS_BEGIN_DECLS + +/* Modbus_over_serial_line_V1_02.pdf - 2.5.2.1 MODBUS Message ASCII Framing + * Page 17 + * RS232 / RS485 ADU = + * start (1 byte) + address (2 bytes) + function (2 bytes) + 2x252 bytes + LRC (2 bytes) + end (2 bytes) = 513 bytes + */ +#define MODBUS_ASCII_MAX_ADU_LENGTH 256 + +MODBUS_API modbus_t* modbus_new_ascii(const char *device, int baud, char parity, + int data_bit, int stop_bit); + +/* + * The get/set serial mode and get/set rts methods should be deprecated, + * as they just forward to the serial backend. + * Currently they are kept so the known interface does not change. + */ +#define MODBUS_ASCII_RS232 0 +#define MODBUS_ASCII_RS485 1 + +MODBUS_API int modbus_ascii_set_serial_mode(modbus_t *ctx, int mode); +MODBUS_API int modbus_ascii_get_serial_mode(modbus_t *ctx); + +#define MODBUS_ASCII_RTS_NONE 0 +#define MODBUS_ASCII_RTS_UP 1 +#define MODBUS_ASCII_RTS_DOWN 2 + +MODBUS_API int modbus_ascii_set_rts(modbus_t *ctx, int mode); +MODBUS_API int modbus_ascii_get_rts(modbus_t *ctx); + +MODBUS_END_DECLS + +#endif /* MODBUS_ASCII_H */ From 6b9bfc2f5b6120daa5e7261089523da0892eddad Mon Sep 17 00:00:00 2001 From: Jakob Bysewski Date: Sun, 13 Mar 2016 12:53:37 +0100 Subject: [PATCH 3/3] Add bswap macro to compile on OSX --- src/modbus-data.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/modbus-data.c b/src/modbus-data.c index b2fa6407b..e36b7c936 100644 --- a/src/modbus-data.c +++ b/src/modbus-data.c @@ -19,6 +19,13 @@ # include #endif +#if defined(__APPLE__) + #include + #define bswap_16 OSSwapInt16 + #define bswap_32 OSSwapInt32 + #define bswap_64 OSSwapInt64 +#endif + #if defined(__GNUC__) # define GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__ * 10) # if GCC_VERSION >= 430