msousa@0: /* msousa@0: * Copyright (c) 2001,2016 Mario de Sousa (msousa@fe.up.pt) msousa@0: * msousa@0: * This file is part of the Modbus library for Beremiz and matiec. msousa@0: * msousa@0: * This Modbus library is free software: you can redistribute it and/or modify msousa@0: * it under the terms of the GNU Lesser General Public License as published by msousa@0: * the Free Software Foundation, either version 3 of the License, or msousa@0: * (at your option) any later version. msousa@0: * msousa@0: * This program is distributed in the hope that it will be useful, but msousa@0: * WITHOUT ANY WARRANTY; without even the implied warranty of msousa@0: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser msousa@0: * General Public License for more details. msousa@0: * msousa@0: * You should have received a copy of the GNU Lesser General Public License msousa@0: * along with this Modbus library. If not, see . msousa@0: * msousa@0: * This code is made available on the understanding that it will not be msousa@0: * used in safety-critical situations without a full and competent review. msousa@0: */ msousa@0: msousa@0: msousa@0: msousa@0: #include /* File control definitions */ msousa@0: #include /* Standard input/output */ msousa@0: #include msousa@0: #include msousa@0: #include /* POSIX terminal control definitions */ msousa@0: #include /* Time structures for select() */ msousa@0: #include /* POSIX Symbolic Constants */ msousa@0: #include msousa@0: #include /* Error definitions */ msousa@0: #include /* clock_gettime() */ msousa@0: #include /* required for INT_MAX */ msousa@0: msousa@0: #include /* required for htons() and ntohs() */ msousa@0: msousa@0: #include "mb_layer1.h" /* The public interface this file implements... */ msousa@0: #include "mb_rtu_private.h" msousa@0: msousa@0: msousa@0: #define ERRMSG msousa@0: #define ERRMSG_HEAD "ModbusRTU: " msousa@0: msousa@0: // #define DEBUG /* uncomment to see the data sent and received */ msousa@0: msousa@0: #ifdef DEBUG msousa@0: #ifndef ERRMSG msousa@0: #define ERRMSG msousa@0: #endif msousa@0: #endif msousa@0: msousa@0: msousa@0: #define SAFETY_MARGIN 10 msousa@0: msousa@0: /************************************/ msousa@0: /** **/ msousa@0: /** Include common code... **/ msousa@0: /** **/ msousa@0: /************************************/ msousa@0: msousa@0: #include "mb_ds_util.h" /* data structures... */ msousa@0: #include "mb_time_util.h" /* time conversion routines... */ msousa@0: msousa@0: msousa@0: msousa@0: /**************************************************************/ msousa@0: /**************************************************************/ msousa@0: /**** ****/ msousa@0: /**** ****/ msousa@0: /**** Forward Declarations ****/ msousa@0: /**** and Defaults ****/ msousa@0: /**** ****/ msousa@0: /**************************************************************/ msousa@0: /**************************************************************/ msousa@0: msousa@0: /* CRC funtions... */ msousa@0: typedef u16 (*crc_func_t)(u8 *buf, int cnt); msousa@0: static u16 crc_slow(u8 *buf, int cnt); msousa@0: static u16 crc_fast(u8 *buf, int cnt); msousa@0: msousa@0: /* slow version does not need to be initialised, so we use it as default. */ msousa@0: #define DEF_CRC_FUNCTION crc_slow msousa@0: msousa@0: msousa@0: /**************************************************************/ msousa@0: /**************************************************************/ msousa@0: /**** ****/ msousa@0: /**** ****/ msousa@0: /**** Local Utility functions... ****/ msousa@0: /**** ****/ msousa@0: /**** ****/ msousa@0: /**************************************************************/ msousa@0: /**************************************************************/ msousa@0: msousa@0: /************************************/ msousa@0: /** **/ msousa@0: /** Miscelaneous Utility functions **/ msousa@0: /** **/ msousa@0: /************************************/ msousa@0: msousa@0: /* msousa@0: * Functions to convert u16 variables msousa@0: * between network and host byte order msousa@0: * msousa@0: * NOTE: Modbus uses MSByte first, just like msousa@0: * tcp/ip, so we use the htons() and msousa@0: * ntoh() functions to guarantee msousa@0: * code portability. msousa@0: */ msousa@0: static inline u16 mb_hton(u16 h_value) msousa@0: {return htons(h_value);} /* return h_value; */ msousa@0: msousa@0: static inline u16 mb_ntoh(u16 m_value) msousa@0: {return ntohs(m_value);} /* return m_value; */ msousa@0: msousa@0: /* return Most Significant Byte of value; */ msousa@0: static inline u8 msb(u16 value) msousa@0: {return (value >> 8) & 0xFF;} msousa@0: msousa@0: /* return Least Significant Byte of value; */ msousa@0: static inline u8 lsb(u16 value) msousa@0: {return value & 0xFF;} msousa@0: msousa@0: #define u16_v(char_ptr) (*((u16 *)(&(char_ptr)))) msousa@0: msousa@0: msousa@0: msousa@0: /**************************************/ msousa@0: /** **/ msousa@0: /** Initialise a termios struct **/ msousa@0: /** **/ msousa@0: /**************************************/ msousa@0: static int termios_init(struct termios *tios, msousa@0: int baud, msousa@0: int parity, msousa@0: int data_bits, msousa@0: int stop_bits) { msousa@0: speed_t baud_rate; msousa@0: msousa@0: if (tios == NULL) msousa@0: return -1; msousa@0: msousa@0: /* reset all the values... */ msousa@0: /* NOTE: the following are initialised later on... msousa@0: tios->c_iflag = 0; msousa@0: tios->c_oflag = 0; msousa@0: tios->c_cflag = 0; msousa@0: tios->c_lflag = 0; msousa@0: */ msousa@0: tios->c_line = 0; msousa@0: msousa@0: /* The minimum number of characters that should be received msousa@0: * to satisfy a call to read(). msousa@0: */ msousa@0: tios->c_cc[VMIN ] = 0; msousa@0: msousa@0: /* The maximum inter-arrival interval between two characters, msousa@0: * in deciseconds. msousa@0: * msousa@0: * NOTE: we could use this to detect the end of RTU frames, msousa@0: * but we prefer to use select() that has higher resolution, msousa@0: * even though this higher resolution is most probably not msousa@0: * supported, and the effective resolution is 10ms, msousa@0: * one tenth of a decisecond. msousa@0: */ msousa@0: tios->c_cc[VTIME] = 0; msousa@0: msousa@0: /* configure the input modes... */ msousa@0: tios->c_iflag = IGNBRK | /* ignore BREAK condition on input */ msousa@0: IGNPAR | /* ignore framing errors and parity errors */ msousa@0: IXANY; /* enable any character to restart output */ msousa@0: /* BRKINT Only active if IGNBRK is not set. msousa@0: * generate SIGINT on BREAK condition, msousa@0: * otherwise read BREAK as character \0. msousa@0: * PARMRK Only active if IGNPAR is not set. msousa@0: * replace bytes with parity errors with msousa@0: * \377 \0, instead of \0. msousa@0: * INPCK enable input parity checking msousa@0: * ISTRIP strip off eighth bit msousa@0: * IGNCR ignore carriage return on input msousa@0: * INLCR only active if IGNCR is not set. msousa@0: * translate newline to carriage return on input msousa@0: * ICRNL only active if IGNCR is not set. msousa@0: * translate carriage return to newline on input msousa@0: * IUCLC map uppercase characters to lowercase on input msousa@0: * IXON enable XON/XOFF flow control on output msousa@0: * IXOFF enable XON/XOFF flow control on input msousa@0: * IMAXBEL ring bell when input queue is full msousa@0: */ msousa@0: msousa@0: /* configure the output modes... */ msousa@0: tios->c_oflag = OPOST; /* enable implementation-defined output processing */ msousa@0: /* ONOCR don't output CR at column 0 msousa@0: * OLCUC map lowercase characters to uppercase on output msousa@0: * ONLCR map NL to CR-NL on output msousa@0: * OCRNL map CR to NL on output msousa@0: * OFILL send fill characters for a delay, rather than msousa@0: * using a timed delay msousa@0: * OFDEL fill character is ASCII DEL. If unset, fill msousa@0: * character is ASCII NUL msousa@0: * ONLRET don't output CR msousa@0: * NLDLY NL delay mask. Values are NL0 and NL1. msousa@0: * CRDLY CR delay mask. Values are CR0, CR1, CR2, or CR3. msousa@0: * TABDLY horizontal tab delay mask. Values are TAB0, TAB1, msousa@0: * TAB2, TAB3, or XTABS. A value of XTABS expands msousa@0: * tabs to spaces (with tab stops every eight columns). msousa@0: * BSDLY backspace delay mask. Values are BS0 or BS1. msousa@0: * VTDLY vertical tab delay mask. Values are VT0 or VT1. msousa@0: * FFDLY form feed delay mask. Values are FF0 or FF1. msousa@0: */ msousa@0: msousa@0: /* configure the control modes... */ msousa@0: tios->c_cflag = CREAD | /* enable receiver. */ msousa@0: CLOCAL; /* ignore modem control lines */ msousa@0: /* HUPCL lower modem control lines after last process msousa@0: * closes the device (hang up). msousa@0: * CRTSCTS flow control (Request/Clear To Send). msousa@0: */ msousa@0: if (data_bits == 5) tios->c_cflag |= CS5; msousa@0: else if (data_bits == 6) tios->c_cflag |= CS6; msousa@0: else if (data_bits == 7) tios->c_cflag |= CS7; msousa@0: else if (data_bits == 8) tios->c_cflag |= CS8; msousa@0: else return -1; msousa@0: msousa@0: if (stop_bits == 1) tios->c_cflag &=~ CSTOPB; msousa@0: else if (stop_bits == 2) tios->c_cflag |= CSTOPB; msousa@0: else return -1; msousa@0: msousa@0: if(parity == 0) { /* none */ msousa@0: tios->c_cflag &=~ PARENB; msousa@0: tios->c_cflag &=~ PARODD; msousa@0: } else if(parity == 2) { /* even */ msousa@0: tios->c_cflag |= PARENB; msousa@0: tios->c_cflag &=~ PARODD; msousa@0: } else if(parity == 1) { /* odd */ msousa@0: tios->c_cflag |= PARENB; msousa@0: tios->c_cflag |= PARODD; msousa@0: } else return -1; msousa@0: msousa@0: msousa@0: /* configure the local modes... */ msousa@0: tios->c_lflag = IEXTEN; /* enable implementation-defined input processing */ msousa@0: /* ISIG when any of the characters INTR, QUIT, SUSP, or DSUSP msousa@0: * are received, generate the corresponding signal. msousa@0: * ICANON enable canonical mode. This enables the special msousa@0: * characters EOF, EOL, EOL2, ERASE, KILL, REPRINT, msousa@0: * STATUS, and WERASE, and buffers by lines. msousa@0: * ECHO echo input characters. msousa@0: */ msousa@0: msousa@0: /* Set the baud rate */ msousa@0: /* Must be done before reseting all the values to 0! */ msousa@0: switch(baud) { msousa@0: case 110: baud_rate = B110; break; msousa@0: case 300: baud_rate = B300; break; msousa@0: case 600: baud_rate = B600; break; msousa@0: case 1200: baud_rate = B1200; break; msousa@0: case 2400: baud_rate = B2400; break; msousa@0: case 4800: baud_rate = B4800; break; msousa@0: case 9600: baud_rate = B9600; break; msousa@0: case 19200: baud_rate = B19200; break; msousa@0: case 38400: baud_rate = B38400; break; msousa@0: case 57600: baud_rate = B57600; break; msousa@0: case 115200: baud_rate = B115200; break; msousa@0: default: return -1; msousa@0: } /* switch() */ msousa@0: msousa@0: if ((cfsetispeed(tios, baud_rate) < 0) || msousa@0: (cfsetospeed(tios, baud_rate) < 0)) msousa@0: return -1;; msousa@0: msousa@0: return 0; msousa@0: } msousa@0: msousa@0: msousa@0: /************************************/ msousa@0: /** **/ msousa@0: /** A data structure - recv buffer **/ msousa@0: /** **/ msousa@0: /************************************/ msousa@0: msousa@0: /* A data structutre used for the receive buffer, i.e. the buffer msousa@0: * that stores the bytes we receive from the bus. msousa@0: * msousa@0: * What we realy needed here is an unbounded buffer. This may be msousa@0: * implemented by: msousa@0: * - a circular buffer the size of the maximum frame length msousa@0: * - a linear buffer somewhat larger than the maximum frame length msousa@0: * msousa@0: * Due to the fact that this library's API hands over the frame data msousa@0: * in a linear buffer, and also reads the data (i,e, calls to read()) msousa@0: * into a linear buffer: msousa@0: * - the circular buffer would be more efficient in aborted frame msousa@0: * situations msousa@0: * - the linear is more efficient when no aborted frames are recieved. msousa@0: * msousa@0: * I have decided to optimize for the most often encountered situation, msousa@0: * i.e. when no aborted frames are received. msousa@0: * msousa@0: * The linear buffer has a size larger than the maximum msousa@0: * number of bytes we intend to store in it. We simply start ignoring msousa@0: * the first bytes in the buffer in which we are not interested in, and msousa@0: * continue with the extra bytes of the buffer. When we reach the limit msousa@0: * of these extra bytes, we shift the data down so it once again msousa@0: * uses the first bytes of the buffer. The more number of extra bytes, msousa@0: * the more efficient it will be. msousa@0: * msousa@0: * Note that if we don't receive any aborted frames, it will work as a msousa@0: * simple linear buffer, and no memory shifts will be required! msousa@0: */ msousa@0: msousa@0: typedef struct { msousa@0: lb_buf_t data_buf; msousa@0: /* Flag: msousa@0: * 1 => We have detected a frame boundary using 3.5 character silence msousa@0: * 0 => We have not yet detected any frame boundary msousa@0: */ msousa@0: int found_frame_boundary; /* ==1 => valid data ends at a frame boundary. */ msousa@0: /* Flag: msousa@0: * Used in the call to search_for_frame() as the history parameter! msousa@0: */ msousa@0: int frame_search_history; msousa@0: } recv_buf_t; msousa@0: msousa@0: /* A small auxiliary function... */ msousa@0: static inline u8 *recv_buf_init(recv_buf_t *buf, int size, int max_data_start) { msousa@0: buf->found_frame_boundary = 0; msousa@0: buf->frame_search_history = 0; msousa@0: return lb_init(&buf->data_buf, size, max_data_start); msousa@0: } msousa@0: msousa@0: msousa@0: /* A small auxiliary function... */ msousa@0: static inline void recv_buf_done(recv_buf_t *buf) { msousa@0: buf->found_frame_boundary = 0; msousa@0: buf->frame_search_history = 0; msousa@0: lb_done(&buf->data_buf); msousa@0: } msousa@0: msousa@0: msousa@0: /* A small auxiliary function... */ msousa@0: static inline void recv_buf_reset(recv_buf_t *buf) { msousa@0: buf->found_frame_boundary = 0; msousa@0: buf->frame_search_history = 0; msousa@0: lb_data_purge_all(&buf->data_buf); msousa@0: } msousa@0: msousa@0: msousa@0: /************************************/ msousa@0: /** **/ msousa@0: /** A data structure - nd entry **/ msousa@0: /** **/ msousa@0: /************************************/ msousa@0: msousa@0: /* NOTE: nd = node descriptor */ msousa@0: msousa@0: typedef struct { msousa@0: /* The file descriptor associated with this node */ msousa@0: /* NOTE: if the node is not yet in use, i.e. if the node is free, msousa@0: * then fd will be set to -1 msousa@0: */ msousa@0: int fd; msousa@0: msousa@0: /* the time it takes to transmit 1.5 characters at the current baud rate */ msousa@0: struct timeval time_15_char_; msousa@0: /* the time it takes to transmit 3.5 characters at the current baud rate */ msousa@0: struct timeval time_35_char_; msousa@0: msousa@0: /* Due to the algorithm used to work around aborted frames, the modbus_read() msousa@0: * function might read beyond the current modbus frame. The extra bytes msousa@0: * must be stored for the subsequent call to modbus_read(). msousa@0: */ msousa@0: recv_buf_t recv_buf_; msousa@0: msousa@0: /* The old settings of the serial port, to be reset when the library is closed... */ msousa@0: struct termios old_tty_settings_; msousa@0: msousa@0: /* ignore echo flag. msousa@0: * If set to 1, then it means that we will be reading every byte we msousa@0: * ourselves write out to the bus, so we must ignore those bytes read msousa@0: * before we really read the data sent by remote nodes. msousa@0: * msousa@0: * This comes in useful when using a RS232-RS485 converter that does msousa@0: * not correctly control the RTS-CTS lines... msousa@0: */ msousa@0: int ignore_echo; msousa@0: } nd_entry_t; msousa@0: msousa@0: msousa@0: static inline void nd_entry_init(nd_entry_t *nde) { msousa@0: nde->fd = -1; /* The node is free... */ msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: static int nd_entry_connect(nd_entry_t *nde, msousa@0: node_addr_t *node_addr, msousa@0: optimization_t opt) { msousa@0: msousa@0: int parity_bits, start_bits, char_bits; msousa@0: struct termios settings; msousa@0: int buf_size; msousa@0: msousa@0: /* msousa@0: if (nde == NULL) msousa@0: goto error_exit_0; msousa@0: */ msousa@0: if (nde->fd >= 0) msousa@0: goto error_exit_0; msousa@0: msousa@0: /* initialise the termios data structure */ msousa@0: if (termios_init(&settings, msousa@0: node_addr->addr.rtu.baud, msousa@0: node_addr->addr.rtu.parity, msousa@0: node_addr->addr.rtu.data_bits, msousa@0: node_addr->addr.rtu.stop_bits) msousa@0: < 0) { msousa@0: #ifdef ERRMSG msousa@0: fprintf(stderr, ERRMSG_HEAD "Invalid serial line settings" msousa@0: "(baud=%d, parity=%d, data_bits=%d, stop_bits=%d)\n", msousa@0: node_addr->addr.rtu.baud, msousa@0: node_addr->addr.rtu.parity, msousa@0: node_addr->addr.rtu.data_bits, msousa@0: node_addr->addr.rtu.stop_bits); msousa@0: #endif msousa@0: goto error_exit_1; msousa@0: } msousa@0: msousa@0: /* set the ignore_echo flag */ msousa@0: nde->ignore_echo = node_addr->addr.rtu.ignore_echo; msousa@0: msousa@0: /* initialise recv buffer */ msousa@0: buf_size = (opt == optimize_size)?RECV_BUFFER_SIZE_SMALL: msousa@0: RECV_BUFFER_SIZE_LARGE; msousa@0: if (recv_buf_init(&nde->recv_buf_, buf_size, buf_size - MAX_RTU_FRAME_LENGTH) msousa@0: == NULL) { msousa@0: #ifdef ERRMSG msousa@0: fprintf(stderr, ERRMSG_HEAD "Out of memory: error initializing receive buffer\n"); msousa@0: #endif msousa@0: goto error_exit_2; msousa@0: } msousa@0: msousa@0: /* open the serial port */ msousa@0: if((nde->fd = open(node_addr->addr.rtu.device, O_RDWR | O_NOCTTY | O_NDELAY)) msousa@0: < 0) { msousa@0: #ifdef ERRMSG msousa@0: perror("open()"); msousa@0: fprintf(stderr, ERRMSG_HEAD "Error opening device %s\n", msousa@0: node_addr->addr.rtu.device); msousa@0: #endif msousa@0: goto error_exit_3; msousa@0: } msousa@0: msousa@0: if(tcgetattr(nde->fd, &nde->old_tty_settings_) < 0) { msousa@0: #ifdef ERRMSG msousa@0: perror("tcgetattr()"); msousa@0: fprintf(stderr, ERRMSG_HEAD "Error reading device's %s original settings.\n", msousa@0: node_addr->addr.rtu.device); msousa@0: #endif msousa@0: goto error_exit_4; msousa@0: } msousa@0: msousa@0: if(tcsetattr(nde->fd, TCSANOW, &settings) < 0) { msousa@0: #ifdef ERRMSG msousa@0: perror("tcsetattr()"); msousa@0: fprintf(stderr, ERRMSG_HEAD "Error configuring device %s " msousa@0: "(baud=%d, parity=%d, data_bits=%d, stop_bits=%d)\n", msousa@0: node_addr->addr.rtu.device, msousa@0: node_addr->addr.rtu.baud, msousa@0: node_addr->addr.rtu.parity, msousa@0: node_addr->addr.rtu.data_bits, msousa@0: node_addr->addr.rtu.stop_bits); msousa@0: #endif msousa@0: goto error_exit_4; msousa@0: } msousa@0: msousa@0: parity_bits = (node_addr->addr.rtu.parity == 0)?0:1; msousa@0: start_bits = 1; msousa@0: char_bits = start_bits + node_addr->addr.rtu.data_bits + msousa@0: parity_bits + node_addr->addr.rtu.stop_bits; msousa@0: nde->time_15_char_ = d_to_timeval(SAFETY_MARGIN*1.5*char_bits/node_addr->addr.rtu.baud); msousa@0: nde->time_35_char_ = d_to_timeval(SAFETY_MARGIN*3.5*char_bits/node_addr->addr.rtu.baud); msousa@0: msousa@0: #ifdef DEBUG msousa@0: fprintf(stderr, "nd_entry_connect(): %s ope{.node=NULL, .node_count=0};n\n", node_addr->addr.rtu.device ); msousa@0: fprintf(stderr, "nd_entry_connect(): returning fd=%d\n", nde->fd); msousa@0: #endif msousa@0: return nde->fd; msousa@0: msousa@0: error_exit_4: msousa@0: close(nde->fd); msousa@0: error_exit_3: msousa@0: recv_buf_done(&nde->recv_buf_); msousa@0: error_exit_2: msousa@0: error_exit_1: msousa@0: nde->fd = -1; /* set the node as free... */ msousa@0: error_exit_0: msousa@0: return -1; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: static int nd_entry_free(nd_entry_t *nde) { msousa@0: if (nde->fd < 0) msousa@0: /* already free */ msousa@0: return -1; msousa@0: msousa@0: /* reset the tty device old settings... */ msousa@0: #ifdef ERRMSG msousa@0: int res = msousa@0: #endif msousa@0: tcsetattr(nde->fd, TCSANOW, &nde->old_tty_settings_); msousa@0: #ifdef ERRMSG msousa@0: if(res < 0) msousa@0: fprintf(stderr, ERRMSG_HEAD "Error reconfiguring serial port to it's original settings.\n"); msousa@0: #endif msousa@0: msousa@0: recv_buf_done(&nde->recv_buf_); msousa@0: close(nde->fd); msousa@0: nde->fd = -1; msousa@0: msousa@0: return 0; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: msousa@0: static inline int nd_entry_is_free(nd_entry_t *nde) { msousa@0: return (nde->fd < 0); msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: msousa@0: /************************************/ msousa@0: /** **/ msousa@0: /** A data structure - nd table **/ msousa@0: /** **/ msousa@0: /************************************/ msousa@0: msousa@0: typedef struct { msousa@0: /* the array of node descriptors, and current size... */ msousa@0: nd_entry_t *node; msousa@0: int node_count; /* total number of nodes in the node[] array */ msousa@0: } nd_table_t; msousa@0: msousa@0: msousa@0: #if 1 msousa@0: /* nd_table_init() msousa@0: * Version 1 of the nd_table_init() function. msousa@0: * If called more than once, 2nd and any subsequent calls will msousa@0: * be interpreted as a request to confirm that it was already correctly msousa@0: * initialized with the requested number of nodes. msousa@0: */ msousa@0: static int nd_table_init(nd_table_t *ndt, int nd_count) { msousa@0: int count; msousa@0: msousa@0: if (ndt->node != NULL) { msousa@0: /* this function has already been called, and the node table is already initialised */ msousa@0: return (ndt->node_count == nd_count)?0:-1; msousa@0: } msousa@0: msousa@0: /* initialise the node descriptor metadata array... */ msousa@0: ndt->node = malloc(sizeof(nd_entry_t) * nd_count); msousa@0: if (ndt->node == NULL) { msousa@0: #ifdef ERRMSG msousa@0: fprintf(stderr, ERRMSG_HEAD "Out of memory: error initializing node address buffer\n"); msousa@0: #endif msousa@0: return -1; msousa@0: } msousa@0: ndt->node_count = nd_count; msousa@0: msousa@0: /* initialise the state of each node in the array... */ msousa@0: for (count = 0; count < ndt->node_count; count++) { msousa@0: nd_entry_init(&ndt->node[count]); msousa@0: } /* for() */ msousa@0: msousa@0: return nd_count; /* number of succesfully created nodes! */ msousa@0: } msousa@0: #else msousa@0: /* nd_table_init() msousa@0: * Version 2 of the nd_table_init() function. msousa@0: * If called more than once, 2nd and any subsequent calls will msousa@0: * be interpreted as a request to reserve an extra new_nd_count msousa@0: * number of nodes. This will be done using realloc(). msousa@0: */ msousa@0: static int nd_table_init(nd_table_t *ndt, int new_nd_count) { msousa@0: int count; msousa@0: msousa@0: /* initialise the node descriptor metadata array... */ msousa@0: ndt->node = realloc(ndt->node, sizeof(nd_entry_t) * (ndt->node_count + new_nd_count)); msousa@0: if (ndt->node == NULL) { msousa@0: #ifdef ERRMSG msousa@0: fprintf(stderr, ERRMSG_HEAD "Out of memory: error initializing node address buffer\n"); msousa@0: #endif msousa@0: return -1; msousa@0: } msousa@0: msousa@0: /* initialise the state of each newly added node in the array... */ msousa@0: for (count = ndt->node_count; count < ndt->node_count + new_nd_count; count++) { msousa@0: nd_entry_init(&ndt->node[count]); msousa@0: } /* for() */ msousa@0: ndt->node_count += new_nd_count; msousa@0: msousa@0: return new_nd_count; /* number of succesfully created nodes! */ msousa@0: } msousa@0: #endif msousa@0: msousa@0: msousa@0: static inline nd_entry_t *nd_table_get_nd(nd_table_t *ndt, int nd) { msousa@0: if ((nd < 0) || (nd >= ndt->node_count)) msousa@0: return NULL; msousa@0: msousa@0: return &ndt->node[nd]; msousa@0: } msousa@0: msousa@0: msousa@0: static inline void nd_table_done(nd_table_t *ndt) { msousa@0: int i; msousa@0: msousa@0: if (ndt->node == NULL) msousa@0: return; msousa@0: msousa@0: /* close all the connections... */ msousa@0: for (i = 0; i < ndt->node_count; i++) msousa@0: nd_entry_free(&ndt->node[i]); msousa@0: msousa@0: /* Free memory... */ msousa@0: free(ndt->node); msousa@0: *ndt = (nd_table_t){.node=NULL, .node_count=0}; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: static inline int nd_table_get_free_nd(nd_table_t *ndt) { msousa@0: int count; msousa@0: msousa@0: for (count = 0; count < ndt->node_count; count++) { msousa@0: if (nd_entry_is_free(&ndt->node[count])) msousa@0: return count; msousa@0: } msousa@0: msousa@0: /* none found... */ msousa@0: return -1; msousa@0: } msousa@0: msousa@0: msousa@0: static inline int nd_table_free_nd(nd_table_t *ndt, int nd) { msousa@0: if ((nd < 0) || (nd >= ndt->node_count)) msousa@0: return -1; msousa@0: msousa@0: return nd_entry_free(&ndt->node[nd]); msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: /**************************************************************/ msousa@0: /**************************************************************/ msousa@0: /**** ****/ msousa@0: /**** ****/ msousa@0: /**** Global Library State ****/ msousa@0: /**** ****/ msousa@0: /**** ****/ msousa@0: /**************************************************************/ msousa@0: /**************************************************************/ msousa@0: msousa@0: /* The node descriptor table... */ msousa@0: /* NOTE: This variable must be correctly initialised here!! */ msousa@0: static nd_table_t nd_table_ = {.node=NULL, .node_count=0}; msousa@0: msousa@0: /* The optimization choice... */ msousa@0: static optimization_t optimization_; msousa@0: msousa@0: /* the crc function currently in use... */ msousa@0: /* This will depend on the optimisation choice... */ msousa@0: crc_func_t crc_calc = DEF_CRC_FUNCTION; msousa@0: msousa@0: msousa@0: msousa@0: /**************************************************************/ msousa@0: /**************************************************************/ msousa@0: /**** ****/ msousa@0: /**** ****/ msousa@0: /**** CRC functions ****/ msousa@0: /**** ****/ msousa@0: /**** ****/ msousa@0: /**************************************************************/ msousa@0: /**************************************************************/ msousa@0: msousa@0: #if RTU_FRAME_CRC_LENGTH < 2 msousa@0: #error The CRC on modbus RTU frames requires at least 2 bytes in the frame length. msousa@0: #endif msousa@0: msousa@0: msousa@0: /************************************/ msousa@0: /** **/ msousa@0: /** Read the CRC of a frame **/ msousa@0: /** **/ msousa@0: /************************************/ msousa@0: msousa@0: /* NOTE: cnt is number of bytes in the frame _excluding_ CRC! */ msousa@0: static inline u16 crc_read(u8 *buf, int cnt) { msousa@0: /* For some strange reason, the crc is transmited msousa@0: * LSB first, unlike all other values... msousa@0: */ msousa@0: return (buf[cnt + 1] << 8) | buf[cnt]; msousa@0: } msousa@0: msousa@0: msousa@0: /************************************/ msousa@0: /** **/ msousa@0: /** Write the CRC of a frame **/ msousa@0: /** **/ msousa@0: /************************************/ msousa@0: msousa@0: /* NOTE: cnt is number of bytes in the frame _excluding_ CRC! */ msousa@0: static inline void crc_write(u8 *buf, int cnt) { msousa@0: /* For some strange reason, the crc is transmited msousa@0: * LSB first, unlike all other values... msousa@0: * msousa@0: * u16_v(query[string_length]) = mb_hton(temp_crc); -> This is wrong !! msousa@0: */ msousa@0: /* NOTE: We have already checked above that RTU_FRAME_CRC_LENGTH is >= 2 */ msousa@0: u16 crc = crc_calc(buf, cnt); msousa@0: buf[cnt] = lsb(crc); msousa@0: buf[cnt+1] = msb(crc); msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: /************************************/ msousa@0: /** **/ msousa@0: /** A slow version of the **/ msousa@0: /** CRC function **/ msousa@0: /** **/ msousa@0: /************************************/ msousa@0: msousa@0: /* crc optimized for smallest memory footprint */ msousa@0: static u16 crc_slow(u8 *buf, int cnt) msousa@0: { msousa@0: int bit; msousa@0: u16 temp,flag; msousa@0: msousa@0: temp=0xFFFF; msousa@0: msousa@0: while (cnt-- != 0) { msousa@0: temp=temp ^ *buf++; msousa@0: for (bit=1; bit<=8; bit++) { msousa@0: flag = temp & 0x0001; msousa@0: /* NOTE: msousa@0: * - since temp is unsigned, we are guaranteed a zero in MSbit; msousa@0: * - if it were signed, the value placed in the MSbit would be msousa@0: * compiler dependent! msousa@0: */ msousa@0: temp >>= 1; msousa@0: if (flag) msousa@0: temp=temp ^ 0xA001; msousa@0: } msousa@0: } msousa@0: return(temp); msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: msousa@0: /************************************/ msousa@0: /** **/ msousa@0: /** A fast version of the **/ msousa@0: /** CRC function **/ msousa@0: /** **/ msousa@0: /************************************/ msousa@0: static u8 *crc_fast_buf = NULL; msousa@0: msousa@0: /* crc optimized for speed */ msousa@0: static u16 crc_fast(u8 *buf, int cnt) msousa@0: { msousa@0: /* NOTE: The following arrays have been replaced by an equivalent msousa@0: * array (crc_fast_buf[]) initialised at run-time. msousa@0: */ msousa@0: /* msousa@0: static u8 buf_lsb[] = {0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, msousa@0: 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, msousa@0: 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, msousa@0: 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, msousa@0: 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, msousa@0: 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, msousa@0: 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, msousa@0: 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, msousa@0: 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, msousa@0: 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, msousa@0: 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, msousa@0: 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, msousa@0: 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, msousa@0: 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, msousa@0: 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, msousa@0: 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, msousa@0: 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, msousa@0: 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, msousa@0: 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, msousa@0: 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, msousa@0: 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, msousa@0: 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, msousa@0: 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, msousa@0: 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, msousa@0: 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, msousa@0: 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, msousa@0: 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, msousa@0: 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, msousa@0: 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, msousa@0: 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, msousa@0: 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, msousa@0: 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40 msousa@0: }; msousa@0: msousa@0: static u8 buf_msb[] = {0x00, 0xc0, 0xc1, 0x01, 0xc3, 0x03, 0x02, 0xc2, msousa@0: 0xc6, 0x06, 0x07, 0xc7, 0x05, 0xc5, 0xc4, 0x04, msousa@0: 0xcc, 0x0c, 0x0d, 0xcd, 0x0f, 0xcf, 0xce, 0x0e, msousa@0: 0x0a, 0xca, 0xcb, 0x0b, 0xc9, 0x09, 0x08, 0xc8, msousa@0: 0xd8, 0x18, 0x19, 0xd9, 0x1b, 0xdb, 0xda, 0x1a, msousa@0: 0x1e, 0xde, 0xdf, 0x1f, 0xdd, 0x1d, 0x1c, 0xdc, msousa@0: 0x14, 0xd4, 0xd5, 0x15, 0xd7, 0x17, 0x16, 0xd6, msousa@0: 0xd2, 0x12, 0x13, 0xd3, 0x11, 0xd1, 0xd0, 0x10, msousa@0: 0xf0, 0x30, 0x31, 0xf1, 0x33, 0xf3, 0xf2, 0x32, msousa@0: 0x36, 0xf6, 0xf7, 0x37, 0xf5, 0x35, 0x34, 0xf4, msousa@0: 0x3c, 0xfc, 0xfd, 0x3d, 0xff, 0x3f, 0x3e, 0xfe, msousa@0: 0xfa, 0x3a, 0x3b, 0xfb, 0x39, 0xf9, 0xf8, 0x38, msousa@0: 0x28, 0xe8, 0xe9, 0x29, 0xeb, 0x2b, 0x2a, 0xea, msousa@0: 0xee, 0x2e, 0x2f, 0xef, 0x2d, 0xed, 0xec, 0x2c, msousa@0: 0xe4, 0x24, 0x25, 0xe5, 0x27, 0xe7, 0xe6, 0x26, msousa@0: 0x22, 0xe2, 0xe3, 0x23, 0xe1, 0x21, 0x20, 0xe0, msousa@0: 0xa0, 0x60, 0x61, 0xa1, 0x63, 0xa3, 0xa2, 0x62, msousa@0: 0x66, 0xa6, 0xa7, 0x67, 0xa5, 0x65, 0x64, 0xa4, msousa@0: 0x6c, 0xac, 0xad, 0x6d, 0xaf, 0x6f, 0x6e, 0xae, msousa@0: 0xaa, 0x6a, 0x6b, 0xab, 0x69, 0xa9, 0xa8, 0x68, msousa@0: 0x78, 0xb8, 0xb9, 0x79, 0xbb, 0x7b, 0x7a, 0xba, msousa@0: 0xbe, 0x7e, 0x7f, 0xbf, 0x7d, 0xbd, 0xbc, 0x7c, msousa@0: 0xb4, 0x74, 0x75, 0xb5, 0x77, 0xb7, 0xb6, 0x76, msousa@0: 0x72, 0xb2, 0xb3, 0x73, 0xb1, 0x71, 0x70, 0xb0, msousa@0: 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, msousa@0: 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, msousa@0: 0x9c, 0x5c, 0x5d, 0x9d, 0x5f, 0x9f, 0x9e, 0x5e, msousa@0: 0x5a, 0x9a, 0x9b, 0x5b, 0x99, 0x59, 0x58, 0x98, msousa@0: 0x88, 0x48, 0x49, 0x89, 0x4b, 0x8b, 0x8a, 0x4a, msousa@0: 0x4e, 0x8e, 0x8f, 0x4f, 0x8d, 0x4d, 0x4c, 0x8c, msousa@0: 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, msousa@0: 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40 msousa@0: }; msousa@0: */ msousa@0: u8 crc_msb = 0xFF; msousa@0: u8 crc_lsb = 0xFF; msousa@0: int index; msousa@0: msousa@0: if (cnt <= 0) { msousa@0: fprintf(stderr, "\nInternal program error in file %s at line %d\n\n\n", __FILE__, __LINE__); msousa@0: exit(EXIT_FAILURE); msousa@0: } msousa@0: msousa@0: while (cnt-- != 0) { msousa@0: index = 2 * (crc_lsb ^ *buf++); msousa@0: crc_lsb = crc_msb ^ crc_fast_buf[index]/* buf_lsb[index/2] */; msousa@0: crc_msb = crc_fast_buf[index + 1] /* buf_msb[index/2] */; msousa@0: } msousa@0: msousa@0: return crc_msb*0x0100 + crc_lsb; msousa@0: } msousa@0: msousa@0: msousa@0: /************************************/ msousa@0: /** **/ msousa@0: /** init() and done() functions **/ msousa@0: /** of fast CRC version **/ msousa@0: /** **/ msousa@0: /************************************/ msousa@0: msousa@0: static inline int crc_fast_init(void) { msousa@0: int i; msousa@0: u8 data[2]; msousa@0: u16 tmp_crc; msousa@0: msousa@0: if ((crc_fast_buf = (u8 *)malloc(256 * 2)) == NULL) msousa@0: return -1; msousa@0: msousa@0: for (i = 0x00; i < 0x100; i++) { msousa@0: data[0] = 0xFF; msousa@0: data[1] = i; msousa@0: data[1] = ~data[1]; msousa@0: tmp_crc = crc_slow(data, 2); msousa@0: crc_fast_buf[2*i ] = lsb(tmp_crc); msousa@0: crc_fast_buf[2*i + 1] = msb(tmp_crc); msousa@0: } msousa@0: msousa@0: return 0; msousa@0: } msousa@0: msousa@0: msousa@0: static inline void crc_fast_done(void) { msousa@0: free(crc_fast_buf); msousa@0: } msousa@0: msousa@0: msousa@0: /************************************/ msousa@0: /** **/ msousa@0: /** init() and done() functions **/ msousa@0: /** of generic CRC **/ msousa@0: /** **/ msousa@0: /************************************/ msousa@0: msousa@0: static inline int crc_init(optimization_t opt) { msousa@0: switch (opt) { msousa@0: case optimize_speed: msousa@0: if (crc_fast_init() < 0) msousa@0: return -1; msousa@0: crc_calc = crc_fast; msousa@0: return 0; msousa@0: case optimize_size : msousa@0: crc_calc = crc_slow; msousa@0: return 0; msousa@0: default: msousa@0: return -1; msousa@0: } msousa@0: msousa@0: /* humour the compiler */ msousa@0: return -1; msousa@0: } msousa@0: msousa@0: msousa@0: static inline int crc_done(void) { msousa@0: if (crc_calc == crc_fast) msousa@0: crc_fast_done(); msousa@0: msousa@0: crc_calc = DEF_CRC_FUNCTION; msousa@0: return 0; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: /**************************************************************/ msousa@0: /**************************************************************/ msousa@0: /**** ****/ msousa@0: /**** ****/ msousa@0: /**** Sending of Modbus RTU Frames ****/ msousa@0: /**** ****/ msousa@0: /**** ****/ msousa@0: /**************************************************************/ msousa@0: /**************************************************************/ msousa@0: msousa@0: /* W A R N I N G msousa@0: * ============= msousa@0: * The modbus_rtu_write() function assumes that the caller msousa@0: * has allocated a few bytes extra for the buffer containing msousa@0: * the data. These bytes will be used to write the crc. msousa@0: * msousa@0: * The caller of this function MUST make sure that the data msousa@0: * buffer, although only containing data_length bytes, has msousa@0: * been allocated with a size equal to or larger than msousa@0: * data_length + RTU_FRAME_CRC_LENGTH bytes msousa@0: * msousa@0: * I know, this is a very ugly hack, but we don't have much msousa@0: * choice (please read other comments further on for more msousa@0: * explanations) msousa@0: * msousa@0: * We will nevertheless try and make this explicit by having the msousa@0: * library initialisation function (modbus_rtu_init() ) return a msousa@0: * value specifying how many extra bytes this buffer should have. msousa@0: * Maybe this way this very ugly hack won't go unnoticed, and we msousa@0: * won't be having any segmentation faults...! msousa@0: * msousa@0: * NOTE: for now the transmit_timeout is silently ignored in RTU version! msousa@0: */ msousa@0: int modbus_rtu_write(int nd, msousa@0: u8 *data, msousa@0: size_t data_length, msousa@0: u16 transaction_id, msousa@0: const struct timespec *transmit_timeout msousa@0: ) msousa@0: { msousa@0: fd_set rfds; msousa@0: struct timeval timeout; msousa@0: int res, send_retries; msousa@0: nd_entry_t *nd_entry; msousa@0: msousa@0: #ifdef DEBUG msousa@0: fprintf(stderr, "modbus_rtu_write(fd=%d) called...\n", nd); msousa@0: #endif msousa@0: /* check if nd is correct... */ msousa@0: if ((nd_entry = nd_table_get_nd(&nd_table_, nd)) == NULL) msousa@0: return -1; msousa@0: msousa@0: /* check if nd is initialzed... */ msousa@0: if (nd_entry->fd < 0) msousa@0: return -1; msousa@0: msousa@0: /************************** msousa@0: * append crc to frame... * msousa@0: **************************/ msousa@0: /* WARNING: msousa@0: * The crc_write() function assumes that we have an extra msousa@0: * RTU_FRAME_CRC_LENGTH free bytes at the end of the *data msousa@0: * buffer. msousa@0: * The caller of this function had better make sure he has msousa@0: * allocated those extra bytes, or a segmentation fault will msousa@0: * occur. msousa@0: * Please read on why we leave this as it is... msousa@0: * msousa@0: * REASONS: msousa@0: * We want to write the data and the crc in a single call to msousa@0: * the OS. This is the only way we can minimally try to gurantee msousa@0: * that we will not be introducing a silence of more than 1.5 msousa@0: * character transmission times between any two characters. msousa@0: * msousa@0: * We could do the above using one of two methods: msousa@0: * (a) use a special writev() call in which the data msousa@0: * to be sent is stored in two buffers (one for the msousa@0: * data and the other for the crc). msousa@0: * (b) place all the data in a single linear buffer and msousa@0: * use the normal write() function. msousa@0: * msousa@0: * We cannot use (a) since the writev(2) function does not seem msousa@0: * to be POSIX compliant... msousa@0: * (b) has the drawback that we would need to allocate a new buffer, msousa@0: * and copy all the data into that buffer. We have enough copying of msousa@0: * data between buffers as it is, so we won't be doing it here msousa@0: * yet again! msousa@0: * msousa@0: * The only option that seems left over is to have the caller msousa@0: * of this function allocate a few extra bytes. Let's hope he msousa@0: * does not forget! msousa@0: */ msousa@0: crc_write(data, data_length); msousa@0: data_length += RTU_FRAME_CRC_LENGTH; msousa@0: msousa@0: #ifdef DEBUG msousa@0: /* Print the hex value of each character that is about to be msousa@0: * sent over the bus. msousa@0: */ msousa@0: { int i; msousa@0: for(i = 0; i < data_length; i++) msousa@0: fprintf(stderr, "[0x%2X]", data[i]); msousa@0: fprintf(stderr, "\n"); msousa@0: } msousa@0: #endif msousa@0: /* THE MAIN LOOP!!! */ msousa@0: /* NOTE: The modbus standard specifies that the message must msousa@0: * be sent continuosly over the wire with maximum msousa@0: * inter-character delays of 1.5 character intervals. msousa@0: * msousa@0: * If the write() call is interrupted by a signal, then msousa@0: * this delay will most probably be exceeded. We should then msousa@0: * re-start writing the query from the begining. msousa@0: * msousa@0: * BUT, can we really expect the write() call to return msousa@0: * query_length on every platform when no error occurs? msousa@0: * The write call would still be correct if it only wrote msousa@0: * 1 byte at a time! msousa@0: * msousa@0: * To protect ourselves getting into an infinte loop in the msousa@0: * above cases, we specify a maximum number of retries, and msousa@0: * hope for the best...! The worst will now be we simply do msousa@0: * not get to send out a whole frame, and will therefore always msousa@0: * fail on writing a modbus frame! msousa@0: */ msousa@0: send_retries = RTU_FRAME_SEND_RETRY + 1; /* must try at least once... */ msousa@0: while (send_retries > 0) { msousa@0: msousa@0: /******************************* msousa@0: * synchronise with the bus... * msousa@0: *******************************/ msousa@0: /* Remember that a RS485 bus is half-duplex, so we have to wait until msousa@0: * nobody is transmitting over the bus for our turn to transmit. msousa@0: * This will never happen on a modbus network if the master and msousa@0: * slave state machines never get out of synch (granted, it probably msousa@0: * only has two states, but a state machine nonetheless), but we want msousa@0: * to make sure we can re-synchronise if they ever do get out of synch. msousa@0: * msousa@0: * The following lines will guarantee that we will re-synchronise our msousa@0: * state machine with the current state of the bus. msousa@0: * msousa@0: * We first wait until the bus has been silent for at least msousa@0: * char_interval_timeout (i.e. 3.5 character interval). We then flush msousa@0: * any input and output that might be on the cache. msousa@0: */ msousa@0: /* NOTES: msousa@0: * - we do not need to reset the rfds with FD_SET(ttyfd, &rfds) msousa@0: * before every call to select! We only wait on one file descriptor, msousa@0: * so if select returns succesfully, it must have that same file msousa@0: * decriptor set in the rdfs! msousa@0: * If select returns with a timeout, then we do not get to call msousa@0: * select again! msousa@0: * - On Linux, timeout (i.e. timeout) is modified by select() to msousa@0: * reflect the amount of time not slept; most other implementations msousa@0: * do not do this. In the cases in which timeout is not modified, msousa@0: * we will simply have to wait for longer periods if select is msousa@0: * interrupted by a signal. msousa@0: */ msousa@0: FD_ZERO(&rfds); msousa@0: FD_SET(nd_entry->fd, &rfds); msousa@0: timeout = nd_entry->time_35_char_; msousa@0: while ((res = select(nd_entry->fd+1, &rfds, NULL, NULL, &timeout)) != 0) { msousa@0: if (res > 0) { msousa@0: /* we are receiving data over the serial port! */ msousa@0: /* Throw the data away! */ msousa@0: tcflush(nd_entry->fd, TCIFLUSH); /* flush the input stream */ msousa@0: /* reset the timeout value! */ msousa@0: timeout = nd_entry->time_35_char_; msousa@0: /* We do not need to reset the FD SET here! */ msousa@0: } else { msousa@0: /* some kind of error ocurred */ msousa@0: if (errno != EINTR) msousa@0: /* we were not interrupted by a signal */ msousa@0: return -1; msousa@0: /* We will be calling select() again. msousa@0: * We need to reset the FD SET ! msousa@0: */ msousa@0: FD_ZERO(&rfds); msousa@0: FD_SET(nd_entry->fd, &rfds); msousa@0: } msousa@0: } /* while (select()) */ msousa@0: msousa@0: /* Flush both input and output streams... */ msousa@0: /* NOTE: Due to the nature of the modbus protocol, msousa@0: * when a frame is sent all previous msousa@0: * frames that may have arrived at the sending node become msousa@0: * irrelevant. msousa@0: */ msousa@0: tcflush(nd_entry->fd, TCIOFLUSH); /* flush the input & output streams */ msousa@0: recv_buf_reset(&nd_entry->recv_buf_); /* reset the recv buffer */ msousa@0: msousa@0: /********************** msousa@0: * write to output... * msousa@0: **********************/ msousa@0: /* Please see the comment just above the main loop!! */ msousa@0: if ((res = write(nd_entry->fd, data, data_length)) != data_length) { msousa@0: if ((res < 0) && (errno != EAGAIN ) && (errno != EINTR )) msousa@0: return -1; msousa@0: } else { msousa@0: /* query succesfully sent! */ msousa@0: /* res == query_length */ msousa@0: msousa@0: /* NOTE: We do not flush the input stream after sending the frame! msousa@0: * If the process gets swapped out between the end of writing msousa@0: * to the serial port, and the call to flush the input of the msousa@0: * same serial port, the response to the modbus query may be msousa@0: * sent over between those two calls. This would result in the msousa@0: * tcflush(ttyfd, TCIFLUSH) call flushing out the response msousa@0: * to the query we have just sent! msousa@0: * Not a good thing at all... ;-) msousa@0: */ msousa@0: return data_length - RTU_FRAME_CRC_LENGTH; msousa@0: } msousa@0: /* NOTE: The maximum inter-character delay of 1.5 character times msousa@0: * has most probably been exceeded, so we abort the frame and msousa@0: * retry again... msousa@0: */ msousa@0: send_retries--; msousa@0: } /* while() MAIN LOOP */ msousa@0: msousa@0: /* maximum retries exceeded */ msousa@0: return -1; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: /**************************************************************/ msousa@0: /**************************************************************/ msousa@0: /**** ****/ msousa@0: /**** ****/ msousa@0: /**** Receiving Modbus RTU Frames ****/ msousa@0: /**** ****/ msousa@0: /**** ****/ msousa@0: /**************************************************************/ msousa@0: /**************************************************************/ msousa@0: msousa@0: #if MIN_FRAME_LENGTH < 2 msousa@0: #error Modbus RTU frames have a minimum length larger than MIN_FRAME_LENGTH. msousa@0: #endif msousa@0: msousa@0: /************************************/ msousa@0: /** **/ msousa@0: /** Guess length of frame **/ msousa@0: /** being read. **/ msousa@0: /** **/ msousa@0: /************************************/ msousa@0: msousa@0: /* Auxiliary function to the search_for_frame() function. msousa@0: * msousa@0: * NOTE: data_byte_count must be >=2 for correct operation, therefore msousa@0: * the #error condition above. msousa@0: * msousa@0: * Function to determine the length of the frame currently being read, msousa@0: * assuming it is a query/response frame. msousa@0: * msousa@0: * The guess is obtained by analysing the bytes that have already been msousa@0: * read. Sometimes we cannot be sure what is the frame length, because msousa@0: * not enough bytes of the frame have been read yet (for example, frames msousa@0: * that have a byte_count value which has not yet been read). In these msousa@0: * cases we return not the frame length, but an error (-1). msousa@0: * msousa@0: * If we find the data does not make any sense (i.e. it cannot be a valid msousa@0: * modbus frame), we return -1. msousa@0: */ msousa@0: static int frame_length(u8 *frame_data, msousa@0: int frame_data_length, msousa@0: /* The array containing the lengths of frames. */ msousa@0: /* - query_frame_length[] msousa@0: * - response_frame_length[] msousa@0: */ msousa@0: i8 *frame_length_array) { msousa@0: msousa@0: u8 function_code; msousa@0: int res; msousa@0: msousa@0: /* check consistency of input parameters... */ msousa@0: /* msousa@0: if ((frame_data == NULL) || (frame_length_array == NULL) || (frame_data_length < 2)) msousa@0: return -1; msousa@0: */ msousa@0: msousa@0: function_code = frame_data[L2_FRAME_FUNCTION_OFS]; msousa@0: msousa@0: /* hard code the length of response to diagnostic function 8 (0x08), with msousa@0: * subfunction 21 (0x15), and sub-sub-function (a.k.a. operation) 3 (0x03), msousa@0: * which contains a byte count... msousa@0: */ msousa@0: if ((function_code == 0x08) && (frame_length_array == response_frame_lengths)) { msousa@0: if (frame_data_length < 4) { msousa@0: /* not enough info to determine the sub-function... */ msousa@0: return -1; msousa@0: } else { msousa@0: if ((frame_data[2] == 0x00) && (frame_data[3] == 0x15)) { msousa@0: /* we need a couple more bytes to figure out the sub-sub-function... */ msousa@0: if (frame_data_length < 6) { msousa@0: /* not enough info to determine the sub-sub-function... */ msousa@0: return -1; msousa@0: } else { msousa@0: if ((frame_data[4] == 0x00) && (frame_data[5] == 0x03)) { msousa@0: /* We have found a response frame to diagnostic sub-function ... */ msousa@0: if (frame_data_length < 8) { msousa@0: /* not enough info to determine the frame length */ msousa@0: return -1; msousa@0: } else { msousa@0: return /*HEADER*/ 6 + mb_ntoh(u16_v(frame_data[6])) + RTU_FRAME_CRC_LENGTH; msousa@0: } msousa@0: } msousa@0: } msousa@0: } msousa@0: } msousa@0: } msousa@0: msousa@0: res = frame_length_array[function_code]; msousa@0: msousa@0: switch(res) { msousa@0: case BYTE_COUNT_3 : msousa@0: if (frame_data_length >= 3) msousa@0: return BYTE_COUNT_3_HEADER + frame_data[2] + RTU_FRAME_CRC_LENGTH; msousa@0: break; msousa@0: case BYTE_COUNT_34: msousa@0: if (frame_data_length >= 4) msousa@0: return BYTE_COUNT_34_HEADER + mb_ntoh(u16_v(frame_data[2])) + RTU_FRAME_CRC_LENGTH; msousa@0: break; msousa@0: case BYTE_COUNT_7 : msousa@0: if (frame_data_length >= 7) msousa@0: return BYTE_COUNT_7_HEADER + frame_data[6] + RTU_FRAME_CRC_LENGTH; msousa@0: break; msousa@0: case BYTE_COUNT_11: msousa@0: if (frame_data_length >= 11) msousa@0: return BYTE_COUNT_11_HEADER + frame_data[10] + RTU_FRAME_CRC_LENGTH; msousa@0: break; msousa@0: case BYTE_COUNT_U : msousa@0: return -1; msousa@0: default: msousa@0: return res + RTU_FRAME_CRC_LENGTH; msousa@0: } /* switch() */ msousa@0: msousa@0: /* unknown frame length */ msousa@0: return -1; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: /************************************/ msousa@0: /** **/ msousa@0: /** Search for a frame **/ msousa@0: /** **/ msousa@0: /************************************/ msousa@0: msousa@0: /* Search for a valid frame in the current data. msousa@0: * If no valid frame is found, then we return -1. msousa@0: * msousa@0: * NOTE: Since frame verification is done by calculating the CRC, which is rather msousa@0: * CPU intensive, and this function may be called several times with the same, msousa@0: * data, we keep state regarding the result of previous invocations... msousa@0: * That is the reason for the *search_history parameter! msousa@0: */ msousa@0: static int search_for_frame(u8 *frame_data, msousa@0: int frame_data_length, msousa@0: int *search_history) { msousa@0: int query_length, resp_length; msousa@0: u8 function_code; msousa@0: /* *search_history flag will have or'ed of following values... */ msousa@0: #define SFF_HIST_NO_QUERY_FRAME 0x01 msousa@0: #define SFF_HIST_NO_RESPONSE_FRAME 0x02 msousa@0: #define SFF_HIST_NO_FRAME (SFF_HIST_NO_RESPONSE_FRAME + SFF_HIST_NO_QUERY_FRAME) msousa@0: msousa@0: if ((*search_history == SFF_HIST_NO_FRAME) || msousa@0: (frame_data_length < MIN_FRAME_LENGTH) || msousa@0: (frame_data_length > MAX_RTU_FRAME_LENGTH)) msousa@0: return -1; msousa@0: msousa@0: function_code = frame_data[L2_FRAME_FUNCTION_OFS]; msousa@0: msousa@0: /* check for exception frame... */ msousa@0: if ((function_code && 0x80) == 0x80) { msousa@0: if (frame_data_length >= EXCEPTION_FRAME_LENGTH + RTU_FRAME_CRC_LENGTH) { msousa@0: /* let's check CRC for valid frame. */ msousa@0: if ( crc_calc(frame_data, EXCEPTION_FRAME_LENGTH) msousa@0: == crc_read(frame_data, EXCEPTION_FRAME_LENGTH)) msousa@0: return EXCEPTION_FRAME_LENGTH + RTU_FRAME_CRC_LENGTH; msousa@0: else msousa@0: /* We have checked the CRC, and it is not a valid frame! */ msousa@0: *search_history |= SFF_HIST_NO_FRAME; msousa@0: } msousa@0: return -1; msousa@0: } msousa@0: msousa@0: /* check for valid function code */ msousa@0: if ((function_code > MAX_FUNCTION_CODE) || (function_code < 1)) { msousa@0: /* This is an invalid frame!!! */ msousa@0: *search_history |= SFF_HIST_NO_FRAME; msousa@0: return -1; msousa@0: } msousa@0: msousa@0: /* let's guess the frame length */ msousa@0: query_length = resp_length = -1; msousa@0: if ((*search_history & SFF_HIST_NO_QUERY_FRAME) == 0) msousa@0: query_length = frame_length(frame_data, frame_data_length, query_frame_lengths); msousa@0: if ((*search_history & SFF_HIST_NO_RESPONSE_FRAME) == 0) msousa@0: resp_length = frame_length(frame_data, frame_data_length, response_frame_lengths); msousa@0: msousa@0: /* let's check whether any of the lengths are valid...*/ msousa@0: /* If any of the guesses coincides with the available data length msousa@0: * we check that length first... msousa@0: */ msousa@0: if ((frame_data_length == query_length) || (frame_data_length == resp_length)) { msousa@0: if ( crc_calc(frame_data, frame_data_length - RTU_FRAME_CRC_LENGTH) msousa@0: == crc_read(frame_data, frame_data_length - RTU_FRAME_CRC_LENGTH)) msousa@0: return frame_data_length; msousa@0: /* nope, wrong guess...*/ msousa@0: if (frame_data_length == query_length) msousa@0: *search_history |= SFF_HIST_NO_QUERY_FRAME; msousa@0: if (frame_data_length == resp_length) msousa@0: *search_history |= SFF_HIST_NO_RESPONSE_FRAME; msousa@0: } msousa@0: msousa@0: /* let's shoot for a query frame */ msousa@0: if ((*search_history & SFF_HIST_NO_QUERY_FRAME) == 0) { msousa@0: if (query_length >= 0) { msousa@0: if (frame_data_length >= query_length) { msousa@0: /* let's check if we have a valid frame */ msousa@0: if ( crc_calc(frame_data, query_length - RTU_FRAME_CRC_LENGTH) msousa@0: == crc_read(frame_data, query_length - RTU_FRAME_CRC_LENGTH)) msousa@0: return query_length; msousa@0: else msousa@0: /* We have checked the CRC, and it is not a valid frame! */ msousa@0: *search_history |= SFF_HIST_NO_QUERY_FRAME; msousa@0: } msousa@0: } msousa@0: } msousa@0: msousa@0: /* let's shoot for a response frame */ msousa@0: if ((*search_history & SFF_HIST_NO_RESPONSE_FRAME) == 0) { msousa@0: if (resp_length >= 0) { msousa@0: if (frame_data_length >= resp_length) { msousa@0: /* let's check if we have a valid frame */ msousa@0: if ( crc_calc(frame_data, resp_length - RTU_FRAME_CRC_LENGTH) msousa@0: == crc_read(frame_data, resp_length - RTU_FRAME_CRC_LENGTH)) msousa@0: return resp_length; msousa@0: else msousa@0: *search_history |= SFF_HIST_NO_RESPONSE_FRAME; msousa@0: } msousa@0: } msousa@0: } msousa@0: msousa@0: /* Could not find valid frame... */ msousa@0: return -1; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: /************************************/ msousa@0: /** **/ msousa@0: /** Read a frame **/ msousa@0: /** **/ msousa@0: /************************************/ msousa@0: msousa@0: /* A small auxiliary function, just to make the code easier to read... */ msousa@0: static inline void next_frame_offset(recv_buf_t *buf, u8 *slave_id) { msousa@0: buf->frame_search_history = 0; msousa@0: lb_data_purge(&(buf->data_buf), 1 /* skip one byte */); msousa@0: msousa@0: if (slave_id == NULL) msousa@0: return; msousa@0: msousa@0: /* keep ignoring bytes, until we find one == *slave_id, msousa@0: * or no more bytes... msousa@0: */ msousa@0: while (lb_data_count(&(buf->data_buf)) != 0) { msousa@0: if (*lb_data(&(buf->data_buf)) == *slave_id) msousa@0: return; msousa@0: lb_data_purge(&(buf->data_buf), 1 /* skip one byte */); msousa@0: } msousa@0: } msousa@0: msousa@0: /* A small auxiliary function, just to make the code easier to read... */ msousa@0: static inline int return_frame(recv_buf_t *buf, msousa@0: int frame_length, msousa@0: u8 **recv_data_ptr) { msousa@0: #ifdef DEBUG msousa@0: fprintf(stderr, "\n" ); msousa@0: fprintf(stderr, "returning valid frame of %d bytes.\n", frame_length); msousa@0: #endif msousa@0: /* set the data pointer */ msousa@0: *recv_data_ptr = lb_data(&(buf->data_buf)); msousa@15: /* Mark the frame bytes to be deleted off the buffer by the next call to lb_data_purge() */ msousa@15: /* Notice that we cannot delete the frame bytes right away, as they will still be accessed by whoever msousa@15: * called read_frame(). We can only delete this data on the next call to read_frame() msousa@15: */ msousa@15: lb_data_mark_for_purge(&(buf->data_buf), frame_length); msousa@0: /* reset the search_history flag */ msousa@0: buf->frame_search_history = 0; msousa@0: /* if the buffer becomes empty, then reset boundary flag */ msousa@0: if (lb_data_count(&(buf->data_buf)) <= 0) msousa@0: buf->found_frame_boundary = 0; msousa@0: /* return the frame length, excluding CRC */ msousa@0: return frame_length - RTU_FRAME_CRC_LENGTH; msousa@0: } msousa@0: msousa@0: /* A function to read a valid frame off the rtu bus. msousa@0: * msousa@0: * NOTES: msousa@0: * - The returned frame is guaranteed to be a valid frame. msousa@0: * - The returned length does *not* include the CRC. msousa@0: * - The returned frame is not guaranteed to have the same msousa@0: * slave id as that stored in (*slave_id). This value is used msousa@0: * merely in optimizing the search for wanted valid frames msousa@0: * after reading an aborted frame. Only in this situation do msousa@0: * we limit our search for frames with a slvae id == (*slave_id). msousa@0: * Under normal circumstances, the value in (*slave_id) is msousa@0: * simply ignored... msousa@0: * If any valid frame is desired, then slave_id should be NULL. msousa@0: * msousa@0: */ msousa@0: msousa@0: /* NOTE: We cannot relly on the 3.5 character interval between frames to detect msousa@0: * end of frame. We are reading the bytes from a user process, so in msousa@0: * essence the bytes we are reading are coming off a cache. msousa@0: * Any inter-character delays between the arrival of the bytes are msousa@0: * lost as soon as they were placed in the cache. msousa@0: * msousa@0: * Our only recourse is to analyse the frame we are reading in real-time, msousa@0: * and check if it is a valid frame by checking it's CRC. msousa@0: * To optimise this, we must be able to figure out the length msousa@0: * of the frame currently being received by analysing the first bytes msousa@0: * of that frame. Unfortunately, we have three problems with this: msousa@0: * 1) The spec does not specify the format of every possible modbus msousa@0: * frame. For ex.functions 9, 10, 13, 14, 18 and 19(?). msousa@0: * 2) It is not possible to figure out whether a frame is a query msousa@0: * or a response by just analysing the frame, and query and response msousa@0: * frames have different sizes... msousa@0: * 3) A frame may be aborted in the middle! We have no easy way of telling msousa@0: * if what we are reading is a partial (aborted) frame, followed by a msousa@0: * correct frame. msousa@0: * Possible solutions to: msousa@0: * 1) We could try to reverse engineer, but at the moment I have no msousa@0: * PLCs that will generate the required frames. msousa@0: * The chosen method is to verify the CRC if we are lucky enough to msousa@0: * detect the 3.5 frame boundary imediately following one of these msousa@0: * frames of unknown length. msousa@0: * If we do not detect any frame boundary, then our only option msousa@0: * is to consider it an aborted frame. msousa@0: * 2) We aim for the query frame (usually the shortest), and check msousa@0: * it's CRC. If it matches, we accept, the frame, otherwise we try msousa@0: * a response frame. msousa@0: * 3) The only way is to consider a frame boundary after each byte, msousa@0: * (i.e. ignore one bye at a time) and verify if the following bytes msousa@0: * constitue a valid frame (by checking the CRC). msousa@0: * msousa@0: * When reading an aborted frame followed by two or more valid frames, if msousa@0: * we are unlucky and do not detetect any frame boundary using the 3.5 msousa@0: * character interval, then we will most likely be reading in bytes msousa@0: * beyond the first valid frame. This means we will have to store the extra msousa@0: * bytes we have already read, so they may be handled the next time the msousa@0: * read_frame() function is called. msousa@0: */ msousa@0: /* msousa@0: * NOTE: The modbus RTU spec is inconsistent on how to handle msousa@0: * inter-character delays larger than 1.5 characters. msousa@0: * - On one paragraph it is stated that any delay larger than msousa@0: * 1.5 character times aborts the current frame, and a new msousa@0: * frame is started. msousa@0: * - On another paragraph it is stated that a frame must begin msousa@0: * with a silence of 3.5 character times. msousa@0: * msousa@0: * We will therefore consider that any delay larger than 1.5 character msousa@0: * times terminates a valid frame. All the above references to the 3.5 character msousa@0: * interval should therefore be read as a 1.5 character interval. msousa@0: */ msousa@0: /* NOTE: This function is only called from one place in the rest of the code, msousa@0: * so we might just as well make it inline... msousa@0: */ msousa@0: /* RETURNS: number of bytes in received frame msousa@0: * -1 on read file error msousa@0: * -2 on timeout msousa@0: */ msousa@0: static inline int read_frame(nd_entry_t *nd_entry, msousa@0: u8 **recv_data_ptr, msousa@0: struct timespec *end_time, msousa@0: u8 *slave_id) msousa@0: { msousa@0: /* temporary variables... */ msousa@0: fd_set rfds; msousa@0: struct timeval timeout; msousa@0: int res, read_stat; msousa@0: int frame_length; msousa@0: recv_buf_t *recv_buf = &nd_entry->recv_buf_; msousa@0: msousa@0: /* Flag: msousa@0: * 1 => we are reading in an aborted frame, so we must msousa@0: * start ignoring bytes... msousa@0: */ msousa@0: int found_aborted_frame; msousa@0: msousa@0: /* assume error... */ msousa@0: *recv_data_ptr = NULL; msousa@0: msousa@0: /*===================================* msousa@15: * Delete any previously received data that has already been returned as a valid frame in * msousa@15: *===================================*/ msousa@15: /* Delete any previously received data that has already been returned as a valid frame in msousa@15: * the previous invocation of read_frame(). msousa@15: * Notice that the data that will be deleted hass ben marked for deletion by calling msousa@15: * lb_data_mark_for_purge() in return_frame() msousa@15: */ msousa@15: lb_data_purge(&(recv_buf->data_buf), 0); msousa@15: msousa@15: /*===================================* msousa@0: * Check for frame in left over data * msousa@0: *===================================*/ msousa@0: /* If we have any data left over from previous call to read_frame() msousa@0: * (i.e. this very same function), then we try to interpret that msousa@0: * data, and do not wait for any extra bytes... msousa@0: */ msousa@0: frame_length = search_for_frame(lb_data(&recv_buf->data_buf), msousa@0: lb_data_count(&recv_buf->data_buf), msousa@0: &recv_buf->frame_search_history); msousa@0: if (frame_length > 0) msousa@0: /* We found a valid frame! */ msousa@0: return return_frame(recv_buf, frame_length, recv_data_ptr); msousa@0: msousa@0: /* If the left over data finished at a frame boundary, and since it msousa@0: * doesn't contain any valid frame, we discard those bytes... msousa@0: */ msousa@0: if (recv_buf->found_frame_boundary == 1) msousa@0: recv_buf_reset(recv_buf); msousa@0: msousa@0: /*============================* msousa@0: * wait for data availability * msousa@0: *============================*/ msousa@0: /* if we can't find a valid frame in the existing data, or no data msousa@0: * was left over, then we need to read more bytes! msousa@0: */ msousa@0: FD_ZERO(&rfds); msousa@0: FD_SET(nd_entry->fd, &rfds); msousa@0: {int sel_res = my_select(nd_entry->fd + 1, &rfds, NULL, end_time); msousa@0: if (sel_res < 0) msousa@0: return -1; msousa@0: if (sel_res == 0) msousa@0: return -2; msousa@0: } msousa@0: msousa@0: /*==============* msousa@0: * read a frame * msousa@0: *==============*/ msousa@0: /* The main loop that reads one frame */ msousa@0: /* (multiple calls to read() ) */ msousa@0: /* and jumps out as soon as it finds a valid frame. */ msousa@0: msousa@0: found_aborted_frame = 0; msousa@0: FD_ZERO(&rfds); msousa@0: FD_SET(nd_entry->fd, &rfds); msousa@0: while (1) { msousa@0: msousa@0: /*------------------* msousa@0: * read frame bytes * msousa@0: *------------------*/ msousa@0: /* Read in as many bytes as possible... msousa@0: * But only if we have not found a frame boundary. Once we find msousa@0: * a frame boundary, we do not want to read in any more bytes msousa@0: * and mix them up with the current frame's bytes. msousa@0: */ msousa@0: if (recv_buf->found_frame_boundary == 0) { msousa@0: read_stat = read(nd_entry->fd, msousa@0: lb_free(&recv_buf->data_buf), msousa@0: lb_free_count(&recv_buf->data_buf)); msousa@0: if (read_stat < 0) { msousa@0: if (errno != EINTR) msousa@0: return -1; msousa@0: else msousa@0: read_stat = 0; msousa@0: } msousa@0: #ifdef DEBUG msousa@0: {/* display the hex code of each character received */ msousa@0: int i; msousa@0: fprintf(stderr, "-"); msousa@0: for (i=0; i < read_stat; i++) msousa@0: fprintf(stderr, "<0x%2X>", *(lb_free(&recv_buf->data_buf) + i)); msousa@0: } msousa@0: #endif msousa@0: lb_data_add(&recv_buf->data_buf, read_stat); msousa@0: } msousa@0: msousa@0: /*-----------------------* msousa@0: * check for valid frame * msousa@0: *-----------------------*/ msousa@0: frame_length = search_for_frame(lb_data(&recv_buf->data_buf), msousa@0: lb_data_count(&recv_buf->data_buf), msousa@0: &recv_buf->frame_search_history); msousa@0: if (frame_length > 0) msousa@0: /* We found a valid frame! */ msousa@0: return return_frame(recv_buf, frame_length, recv_data_ptr); msousa@0: msousa@0: /* if we reach this point, we are sure we do not have valid frame msousa@0: * of known length in the current data with the current offset... msousa@0: */ msousa@0: msousa@0: /*---------------------------------* msousa@0: * Have we found an aborted frame? * msousa@0: *---------------------------------*/ msousa@0: if (lb_data_count(&recv_buf->data_buf) >= MAX_RTU_FRAME_LENGTH) msousa@0: found_aborted_frame = 1; msousa@0: msousa@0: /*---------------------------------* msousa@0: * Must we try a new frame_offset? * msousa@0: *---------------------------------*/ msousa@0: if (found_aborted_frame == 1) { msousa@0: /* Note that the found_aborted_frame flag is only set if: msousa@0: * 1 - we have previously detected a frame_boundary, msousa@0: * (i.e. found_frame_boundary is == 1 !!) so we won't be msousa@0: * reading in more bytes; msousa@0: * 2 - we have read more bytes than the maximum frame length msousa@0: * msousa@0: * Considering we have just failed finding a valid frame, and the above msousa@0: * points (1) and (2), then there is no way we are still going to msousa@0: * find a valid frame in the current data. msousa@0: * We must therefore try a new first byte for the frame... msousa@0: */ msousa@0: next_frame_offset(recv_buf, slave_id); msousa@0: } msousa@0: msousa@0: /*-----------------------------* msousa@0: * check for data availability * msousa@0: *-----------------------------*/ msousa@0: if (recv_buf->found_frame_boundary == 0) { msousa@0: /* We need more bytes!! */ msousa@0: /* msousa@0: * if no character at the buffer, then we wait time_15_char_ msousa@0: * before accepting end of frame msousa@0: */ msousa@0: /* NOTES: msousa@0: * - On Linux, timeout is modified by select() to reflect msousa@0: * the amount of time not slept; most other implementations do msousa@0: * not do this. On those platforms we will simply have to wait msousa@0: * longer than we wished if select() is by any chance interrupted msousa@0: * by a signal... msousa@0: */ msousa@0: timeout = nd_entry->time_15_char_; msousa@0: while ((res = select(nd_entry->fd+1, &rfds, NULL, NULL, &timeout)) < 0) { msousa@0: if (errno != EINTR) msousa@0: return -1; msousa@0: /* We will be calling select() again. msousa@0: * We need to reset the FD SET ! msousa@0: */ msousa@0: FD_ZERO(&rfds); msousa@0: FD_SET(nd_entry->fd, &rfds); msousa@0: } msousa@0: msousa@0: if (res == 0) { msousa@0: int frame_length = lb_data_count(&recv_buf->data_buf); msousa@0: /* We have detected an end of frame using timing boundaries... */ msousa@0: recv_buf->found_frame_boundary = 1; /* => stop trying to read any more bytes! */ msousa@0: msousa@0: /* Let's check if we happen to have a correct frame... */ msousa@0: if ((frame_length <= MAX_RTU_FRAME_LENGTH) && msousa@0: (frame_length - RTU_FRAME_CRC_LENGTH > 0)) { msousa@0: if ( crc_calc(lb_data(&recv_buf->data_buf), frame_length - RTU_FRAME_CRC_LENGTH) msousa@0: == crc_read(lb_data(&recv_buf->data_buf), frame_length - RTU_FRAME_CRC_LENGTH)) { msousa@0: /* We have found a valid frame. Let's get out of here! */ msousa@0: return return_frame(recv_buf, frame_length, recv_data_ptr); msousa@0: } msousa@0: } msousa@0: msousa@0: /* We have detected a frame boundary, but the frame we read msousa@0: * is not valid... msousa@0: * msousa@0: * One of the following reasons must be the cause: msousa@0: * 1 - we are reading a single aborted frame. msousa@0: * 2 - we are reading more than one frame. The first frame, msousa@0: * followed by any number of valid and/or aborted frames, msousa@0: * may be one of: msousa@0: * a - a valid frame whose length is unknown to us, msousa@0: * i.e. it is not specified in the public Modbus spec. msousa@0: * b - an aborted frame. msousa@0: * msousa@0: * Due to the complexity of reading 2a as a correct frame, we will msousa@0: * consider it as an aborted frame. (NOTE: it is possible, but msousa@0: * we will ignore it until the need arises... hopefully, never!) msousa@0: * msousa@0: * To put it succintly, what wee now have is an 'aborted' frame msousa@0: * followed by one or more aborted and/or valid frames. To get to msousa@0: * any valid frames, and since we do not know where they begin, msousa@0: * we will have to consider every byte as the possible begining msousa@0: * of a valid frame. For this permutation, we ignore the first byte, msousa@0: * and carry on from there... msousa@0: */ msousa@0: found_aborted_frame = 1; msousa@0: lb_data_purge(&recv_buf->data_buf, 1 /* skip one byte */); msousa@0: recv_buf->frame_search_history = 0; msousa@0: } msousa@0: } msousa@0: msousa@0: /*-------------------------------* msousa@0: * check for data yet to process * msousa@0: *-------------------------------*/ msousa@0: if ((lb_data_count(&recv_buf->data_buf) < MIN_FRAME_LENGTH) && msousa@0: (recv_buf->found_frame_boundary == 1)) { msousa@0: /* We have no more data to process, and will not read anymore! */ msousa@0: recv_buf_reset(recv_buf); msousa@0: /* Return TIMEOUT error */ msousa@0: return -2; msousa@0: } msousa@0: } /* while (1)*/ msousa@0: msousa@0: /* humour the compiler... */ msousa@0: return -1; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: msousa@0: msousa@0: /************************************/ msousa@0: /** **/ msousa@0: /** Read a Modbus RTU frame **/ msousa@0: /** **/ msousa@0: /************************************/ msousa@0: msousa@0: /* The public function that reads a valid modbus frame. msousa@0: * msousa@0: * The returned frame is guaranteed to be different to the msousa@0: * the frame stored in send_data, and to start with the msousa@0: * same slave address stored in send_data[0]. msousa@0: * msousa@0: * If send_data is NULL, send_data_length = 0, or msousa@0: * ignore_echo == 0, then the first valid frame read off msousa@0: * the bus is returned. msousa@0: * msousa@0: * return value: The length (in bytes) of the valid frame, msousa@0: * -1 on error msousa@0: * -2 on timeout msousa@0: */ msousa@0: msousa@0: int modbus_rtu_read(int *nd, msousa@0: u8 **recv_data_ptr, msousa@0: u16 *transaction_id, msousa@0: const u8 *send_data, msousa@0: int send_length, msousa@0: const struct timespec *recv_timeout) { msousa@0: struct timespec end_time, *ts_ptr; msousa@0: int res, recv_length, iter; msousa@0: u8 *local_recv_data_ptr; msousa@0: u8 *slave_id, local_slave_id; msousa@0: nd_entry_t *nd_entry; msousa@0: msousa@0: /* Check input parameters... */ msousa@0: if (nd == NULL) msousa@0: return -1; msousa@0: msousa@0: if (recv_data_ptr == NULL) msousa@0: recv_data_ptr = &local_recv_data_ptr; msousa@0: msousa@0: if ((send_data == NULL) && (send_length != 0)) msousa@0: return -1; msousa@0: msousa@0: /* check if nd is correct... */ msousa@0: if ((nd_entry = nd_table_get_nd(&nd_table_, *nd)) == NULL) msousa@0: return -1; msousa@0: msousa@0: /* check if nd is initialzed... */ msousa@0: if (nd_entry->fd < 0) msousa@0: return -1; msousa@0: msousa@0: slave_id = NULL; msousa@0: if (send_length > L2_FRAME_SLAVEID_OFS) { msousa@0: local_slave_id = send_data[L2_FRAME_SLAVEID_OFS]; msousa@0: slave_id = &local_slave_id; msousa@0: } msousa@0: msousa@0: /* We will potentially read many frames, and we cannot reset the timeout msousa@0: * for every frame we read. We therefore determine the absolute time_out, msousa@0: * and use this as a parameter for each call to read_frame() instead of msousa@0: * using a relative timeout. msousa@0: * msousa@0: * NOTE: see also the timeout related comment in the read_frame()= function! msousa@0: */ msousa@0: /* get the current time... */ msousa@0: ts_ptr = NULL; msousa@0: if (recv_timeout != NULL) { msousa@0: ts_ptr = &end_time; msousa@0: *ts_ptr = timespec_add_curtime(*recv_timeout); msousa@0: } msousa@0: msousa@0: /* NOTE: When using a half-duplex RS-485 bus, some (most ?) RS232-485 msousa@0: * converters will send back to the RS232 port whatever we write, msousa@0: * so we will read in whatever we write out onto the bus. msousa@0: * We will therefore have to compare msousa@0: * the first frame we read with the one we sent. If they are msousa@0: * identical it is because we are in fact working on a RS-485 msousa@0: * bus and must therefore read in a second frame which will be msousa@0: * the true response to our query. msousa@0: * If the first frame we receive is different to the query we msousa@0: * just sent, then we are *not* working on a RS-485 bus, and msousa@0: * that is already the real response to our query. msousa@0: * msousa@0: * Flushing the input cache immediately after sending the query msousa@0: * could solve this issue, but we have no guarantee that this msousa@0: * process would not get swapped out between the write() and msousa@0: * flush() calls, and we could therefore be flushing the response msousa@0: * frame! msousa@0: */ msousa@0: msousa@0: iter = 0; msousa@0: while ((res = recv_length = read_frame(nd_entry, recv_data_ptr, ts_ptr, slave_id)) >= 0) { msousa@0: if (iter < INT_MAX) iter++; msousa@0: msousa@0: if ((send_length <= 0) || (nd_entry->ignore_echo == 0)) msousa@0: /* any valid frame will do... */ msousa@0: return recv_length; msousa@0: msousa@0: if ((send_length > L2_FRAME_SLAVEID_OFS + 1) && (iter == 1)) msousa@0: /* We have a frame in send_data, msousa@0: * so we must make sure we are not reading in the frame just sent... msousa@0: * msousa@0: * We must only do this for the first frame we read. Subsequent msousa@0: * frames are guaranteed not to be the previously sent frame msousa@0: * since the modbus_rtu_write() resets the recv buffer. msousa@0: * Remember too that valid modbus responses may be exactly the same msousa@0: * as the request frame!! msousa@0: */ msousa@0: if (recv_length == send_length) msousa@0: if (memcmp(*recv_data_ptr, send_data, recv_length) == 0) msousa@0: /* recv == send !!! */ msousa@0: /* read in another frame. */ msousa@0: continue; msousa@0: msousa@0: /* The frame read is either: msousa@0: * - different to the frame in send_data msousa@0: * - or there is only the slave id in send_data[0] msousa@0: * - or both of the above... msousa@0: */ msousa@0: if (send_length > L2_FRAME_SLAVEID_OFS) msousa@0: if (recv_length > L2_FRAME_SLAVEID_OFS) msousa@0: /* check that frame is from/to the correct slave... */ msousa@0: if ((*recv_data_ptr)[L2_FRAME_SLAVEID_OFS] == send_data[L2_FRAME_SLAVEID_OFS]) msousa@0: /* yep, it is... */ msousa@0: return recv_length; msousa@0: msousa@0: /* The frame we have received is not acceptable... msousa@0: * Let's read a new frame. msousa@0: */ msousa@0: } /* while(...) */ msousa@0: msousa@0: /* error reading response! */ msousa@0: /* Return the error returned by read_frame! */ msousa@0: return res; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: msousa@0: msousa@0: /**************************************************************/ msousa@0: /**************************************************************/ msousa@0: /**** ****/ msousa@0: /**** ****/ msousa@0: /**** Initialising and Shutting Down Library ****/ msousa@0: /**** ****/ msousa@0: /**** ****/ msousa@0: /**************************************************************/ msousa@0: /**************************************************************/ msousa@0: msousa@0: /******************************/ msousa@0: /** **/ msousa@0: /** Load Default Values **/ msousa@0: /** **/ msousa@0: /******************************/ msousa@0: msousa@0: static void set_defaults(int *baud, msousa@0: int *parity, msousa@0: int *data_bits, msousa@0: int *stop_bits) { msousa@0: /* Set the default values, if required... */ msousa@0: if (*baud == 0) msousa@0: *baud = DEF_BAUD_RATE; msousa@0: if (*data_bits == 0) msousa@0: *data_bits = DEF_DATA_BITS; msousa@0: if (*stop_bits == 0) { msousa@0: if (*parity == 0) msousa@0: *stop_bits = DEF_STOP_BITS_NOP; /* no parity */ msousa@0: else msousa@0: *stop_bits = DEF_STOP_BITS_PAR; /* parity used */ msousa@0: } msousa@0: } msousa@0: msousa@0: msousa@0: /******************************/ msousa@0: /** **/ msousa@0: /** Initialise Library **/ msousa@0: /** **/ msousa@0: /******************************/ msousa@0: msousa@0: int modbus_rtu_init(int nd_count, msousa@0: optimization_t opt, msousa@0: int *extra_bytes) msousa@0: { msousa@0: #ifdef DEBUG msousa@0: fprintf(stderr, "modbus_rtu_init(): called...\n"); msousa@0: fprintf(stderr, "creating %d node descriptors\n", nd_count); msousa@0: if (opt == optimize_speed) msousa@0: fprintf(stderr, "optimizing for speed\n"); msousa@0: if (opt == optimize_size) msousa@0: fprintf(stderr, "optimizing for size\n"); msousa@0: #endif msousa@0: msousa@0: /* check input parameters...*/ msousa@0: if (0 == nd_count) { msousa@0: if (extra_bytes != NULL) msousa@0: // Not the corect value for this layer. msousa@0: // What we set it to in case this layer is not used! msousa@0: *extra_bytes = 0; msousa@0: return 0; msousa@0: } msousa@0: if (nd_count <= 0) msousa@0: goto error_exit_0; msousa@0: msousa@0: if (extra_bytes == NULL) msousa@0: goto error_exit_0; msousa@0: msousa@0: if (crc_init(opt) < 0) { msousa@0: #ifdef ERRMSG msousa@0: fprintf(stderr, ERRMSG_HEAD "Out of memory: error initializing crc buffers\n"); msousa@0: #endif msousa@0: goto error_exit_0; msousa@0: } msousa@0: msousa@0: /* set the extra_bytes value... */ msousa@0: /* Please see note before the modbus_rtu_write() function for a msousa@0: * better understanding of this extremely ugly hack... msousa@0: * msousa@0: * The number of extra bytes that must be allocated to the data buffer msousa@0: * before calling modbus_rtu_write() msousa@0: */ msousa@0: *extra_bytes = RTU_FRAME_CRC_LENGTH; msousa@0: msousa@0: /* initialise nd table... */ msousa@0: if (nd_table_init(&nd_table_, nd_count) < 0) msousa@0: goto error_exit_0; msousa@0: msousa@0: /* remember the optimization choice for later reference... */ msousa@0: optimization_ = opt; msousa@0: msousa@0: #ifdef DEBUG msousa@0: fprintf(stderr, "modbus_rtu_init(): returning succesfuly...\n"); msousa@0: #endif msousa@0: return 0; msousa@0: msousa@0: error_exit_0: msousa@0: if (extra_bytes != NULL) msousa@0: // Not the corect value for this layer. msousa@0: // What we set it to in case of error! msousa@0: *extra_bytes = 0; msousa@0: return -1; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: /******************************/ msousa@0: /** **/ msousa@0: /** Open node descriptor **/ msousa@0: /** **/ msousa@0: /******************************/ msousa@0: msousa@0: /* Open a node for master or slave operation. msousa@0: * Returns the node descriptor, or -1 on error. msousa@0: * msousa@0: * This function is mapped onto both msousa@0: * modbus_connect() and modbus_listen() msousa@0: */ msousa@0: int modbus_rtu_connect(node_addr_t node_addr) { msousa@0: int node_descriptor; msousa@0: nd_entry_t *nd_entry; msousa@0: msousa@0: #ifdef DEBUG msousa@0: fprintf(stderr, "modbus_rtu_connect(): called...\n"); msousa@0: fprintf(stderr, "opening %s\n", node_addr.addr.rtu.device); msousa@0: fprintf(stderr, "baud_rate = %d\n", node_addr.addr.rtu.baud); msousa@0: fprintf(stderr, "parity = %d\n", node_addr.addr.rtu.parity); msousa@0: fprintf(stderr, "data_bits = %d\n", node_addr.addr.rtu.data_bits); msousa@0: fprintf(stderr, "stop_bits = %d\n", node_addr.addr.rtu.stop_bits); msousa@0: fprintf(stderr, "ignore_echo = %d\n", node_addr.addr.rtu.ignore_echo); msousa@0: #endif msousa@0: msousa@0: /* Check for valid address family */ msousa@0: if (node_addr.naf != naf_rtu) msousa@0: /* wrong address type... */ msousa@0: goto error_exit_0; msousa@0: msousa@0: /* find a free node descriptor */ msousa@0: if ((node_descriptor = nd_table_get_free_nd(&nd_table_)) < 0) msousa@0: /* if no free nodes to initialize, then we are finished... */ msousa@0: goto error_exit_0; msousa@0: if ((nd_entry = nd_table_get_nd(&nd_table_, node_descriptor)) == NULL) msousa@0: /* strange, this should not occur... */ msousa@0: goto error_exit_0; msousa@0: msousa@0: /* set the default values... */ msousa@0: set_defaults(&(node_addr.addr.rtu.baud), msousa@0: &(node_addr.addr.rtu.parity), msousa@0: &(node_addr.addr.rtu.data_bits), msousa@0: &(node_addr.addr.rtu.stop_bits)); msousa@0: msousa@0: #ifdef DEBUG msousa@0: fprintf(stderr, "modbus_rtu_connect(): calling nd_entry_connect()\n"); msousa@0: #endif msousa@0: if (nd_entry_connect(nd_entry, &node_addr, optimization_) < 0) msousa@0: goto error_exit_0; msousa@0: msousa@0: #ifdef DEBUG msousa@0: fprintf(stderr, "modbus_rtu_connect(): %s open\n", node_addr.addr.rtu.device); msousa@0: fprintf(stderr, "modbus_rtu_connect(): returning nd=%d\n", node_descriptor); msousa@0: #endif msousa@0: return node_descriptor; msousa@0: msousa@0: error_exit_0: msousa@0: #ifdef DEBUG msousa@0: fprintf(stderr, "modbus_rtu_connect(): error!\n"); msousa@0: #endif msousa@0: return -1; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: int modbus_rtu_listen(node_addr_t node_addr) { msousa@0: return modbus_rtu_connect(node_addr); msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: /******************************/ msousa@0: /** **/ msousa@0: /** Close node descriptor **/ msousa@0: /** **/ msousa@0: /******************************/ msousa@0: msousa@0: int modbus_rtu_close(int nd) { msousa@0: return nd_table_free_nd(&nd_table_, nd); msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: /******************************/ msousa@0: /** **/ msousa@0: /** Shutdown Library **/ msousa@0: /** **/ msousa@0: /******************************/ msousa@0: msousa@0: int modbus_rtu_done(void) { msousa@0: nd_table_done(&nd_table_); msousa@0: crc_done(); msousa@0: msousa@0: return 0; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: msousa@0: /******************************/ msousa@0: /** **/ msousa@0: /** **/ msousa@0: /** **/ msousa@0: /******************************/ msousa@0: int modbus_rtu_silence_init(void) { msousa@0: return 0; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: msousa@0: /******************************/ msousa@0: /** **/ msousa@0: /** **/ msousa@0: /** **/ msousa@0: /******************************/ msousa@0: msousa@0: msousa@0: double modbus_rtu_get_min_timeout(int baud, msousa@0: int parity, msousa@0: int data_bits, msousa@0: int stop_bits) { msousa@0: int parity_bits, start_bits, char_bits; msousa@0: msousa@0: set_defaults(&baud, &parity, &data_bits, &stop_bits); msousa@0: parity_bits = (parity == 0)?0:1; msousa@0: start_bits = 1; msousa@0: char_bits = start_bits + data_bits + parity_bits + stop_bits; msousa@0: return (double)((MAX_RTU_FRAME_LENGTH * char_bits) / baud); msousa@0: } msousa@0: msousa@0: