diff --git a/.gitignore b/.gitignore index 9e9a23911..62c756bf2 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,7 @@ Makefile.in /libtool /ltmain.sh /missing -/libmodbus.pc +/libmodbusepsi.pc /stamp-h1 /*.sublime-* /.vscode diff --git a/AUTHORS b/AUTHORS index 188dcb81a..ac83b0437 100644 --- a/AUTHORS +++ b/AUTHORS @@ -5,4 +5,5 @@ oldfaber Hannu Vuolasaho - CLA Michael Heimpold - CLA Jimmy Bergström - CLA -Jakob Bysewski - CLA \ No newline at end of file +Jakob Bysewski - CLA +Pascal Jean diff --git a/Makefile.am b/Makefile.am index a0a165e18..71434c6cd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3,9 +3,9 @@ ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS} AM_MAKEFLAGS = --no-print-directory pkgconfigdir = $(libdir)/pkgconfig -pkgconfig_DATA = libmodbus.pc -EXTRA_DIST = libmodbus.pc.in -CLEANFILES += libmodbus.pc +pkgconfig_DATA = libmodbusepsi.pc +EXTRA_DIST = libmodbusepsi.pc.in +CLEANFILES += libmodbusepsi.pc dist_doc_DATA = MIGRATION README.md diff --git a/README.md b/README.md index ad8ea4af3..5595730e8 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ You can change installation directory with prefix option, eg. `./configure properly set up on your system (*/etc/ld.so.conf.d*) and library cache is up to date (run `ldconfig` as root if required). -The library provides a *libmodbus.pc* file to use with `pkg-config` to ease your +The library provides a *libmodbusepsi.pc* file to use with `pkg-config` to ease your program compilation and linking. If you want to compile with Microsoft Visual Studio, you need to install diff --git a/configure.ac b/configure.ac index 070879c9a..9eecd41d5 100644 --- a/configure.ac +++ b/configure.ac @@ -13,7 +13,7 @@ # m4_define([libmodbus_version_major], [3]) m4_define([libmodbus_version_minor], [1]) -m4_define([libmodbus_version_micro], [6]) +m4_define([libmodbus_version_micro], [10]) m4_define([libmodbus_release_status], [m4_if(m4_eval(libmodbus_version_minor % 2), [1], [snapshot], [release])]) @@ -22,10 +22,10 @@ m4_define([libmodbus_version], [libmodbus_version_major.libmodbus_version_minor.libmodbus_version_micro]) AC_PREREQ([2.63]) -AC_INIT([libmodbus], +AC_INIT([libmodbusepsi], [libmodbus_version], - [https://github.com/stephane/libmodbus/issues], - [libmodbus], + [https://github.com/epsilonrt/libmodbus/issues], + [libmodbusepsi], [http://libmodbus.org/]) AC_CONFIG_SRCDIR([src/modbus.c]) AC_CONFIG_AUX_DIR([build-aux]) @@ -159,13 +159,13 @@ AC_CONFIG_FILES([ src/win32/modbus.dll.manifest tests/Makefile doc/Makefile - libmodbus.pc + libmodbusepsi.pc ]) AC_OUTPUT AC_MSG_RESULT([ $PACKAGE $VERSION - =============== + ========================= prefix: ${prefix} sysconfdir: ${sysconfdir} diff --git a/doc/Makefile.am b/doc/Makefile.am index 5a52c0409..f4928fdc3 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -19,27 +19,29 @@ TXT3 = \ modbus_mapping_new_start_address.txt \ modbus_mask_write_register.txt \ modbus_new_rtu.txt \ - modbus_new_tcp_pi.txt \ modbus_new_tcp.txt \ + modbus_new_tcp_pi.txt \ modbus_read_bits.txt \ modbus_read_input_bits.txt \ modbus_read_input_registers.txt \ modbus_read_registers.txt \ - modbus_receive_confirmation.txt \ modbus_receive.txt \ - modbus_reply_exception.txt \ + modbus_receive_confirmation.txt \ modbus_reply.txt \ + modbus_reply_exception.txt \ modbus_report_slave_id.txt \ - modbus_rtu_get_serial_mode.txt \ - modbus_rtu_set_serial_mode.txt \ - modbus_rtu_get_rts.txt \ - modbus_rtu_set_rts.txt \ - modbus_rtu_set_custom_rts.txt \ - modbus_rtu_get_rts_delay.txt \ - modbus_rtu_set_rts_delay.txt \ modbus_send_raw_request.txt \ - modbus_set_bits_from_bytes.txt \ + modbus_serial_get_rts.txt \ + modbus_serial_get_rts_delay.txt \ + modbus_serial_get_serial_mode.txt \ + modbus_serial_set_custom_rts.txt \ + modbus_serial_set_rts.txt \ + modbus_serial_set_rts_delay.txt \ + modbus_serial_set_serial_mode.txt \ + modbus_serial_get_recv_filter.txt \ + modbus_serial_set_recv_filter.txt \ modbus_set_bits_from_byte.txt \ + modbus_set_bits_from_bytes.txt \ modbus_set_byte_timeout.txt \ modbus_set_debug.txt \ modbus_set_error_recovery.txt \ @@ -53,14 +55,15 @@ TXT3 = \ modbus_set_socket.txt \ modbus_strerror.txt \ modbus_tcp_accept.txt \ - modbus_tcp_pi_accept.txt \ modbus_tcp_listen.txt \ + modbus_tcp_pi_accept.txt \ modbus_tcp_pi_listen.txt \ modbus_write_and_read_registers.txt \ - modbus_write_bits.txt \ modbus_write_bit.txt \ modbus_write_registers.txt \ - modbus_write_register.txt + modbus_write_register.txt \ + modbus_write_bits.txt + TXT7 = libmodbus.txt EXTRA_DIST = asciidoc.conf $(TXT3) $(TXT7) diff --git a/doc/libmodbus.txt b/doc/libmodbus.txt index 241203712..5a4598006 100644 --- a/doc/libmodbus.txt +++ b/doc/libmodbus.txt @@ -11,7 +11,7 @@ SYNOPSIS -------- *#include * -*cc* \`pkg-config --cflags --libs libmodbus` 'files' +*cc* \`pkg-config --cflags --libs libmodbusepsi` 'files' DESCRIPTION @@ -75,14 +75,16 @@ Create a Modbus RTU context:: linkmb:modbus_new_rtu[3] -Set the serial mode:: - linkmb:modbus_rtu_get_serial_mode[3] - linkmb:modbus_rtu_set_serial_mode[3] - linkmb:modbus_rtu_get_rts[3] - linkmb:modbus_rtu_set_rts[3] - linkmb:modbus_rtu_set_custom_rts[3] - linkmb:modbus_rtu_get_rts_delay[3] - linkmb:modbus_rtu_set_rts_delay[3] +Set the low-level behaviour of the serial communication:: + linkmb:modbus_serial_set_rts[3] + linkmb:modbus_serial_get_rts[3] + linkmb:modbus_serial_set_custom_rts[3] + linkmb:modbus_serial_get_rts_delay[3] + linkmb:modbus_serial_set_rts_delay[3] + linkmb:modbus_serial_set_serial_mode[3] + linkmb:modbus_serial_get_serial_mode[3] + modbus_rtu_set_serial_mode (deprecated) + modbus_rtu_get_serial_mode (deprecated) TCP (IPv4) Context diff --git a/doc/modbus_rtu_get_rts.txt b/doc/modbus_rtu_get_rts.txt deleted file mode 100644 index 559c80b6e..000000000 --- a/doc/modbus_rtu_get_rts.txt +++ /dev/null @@ -1,47 +0,0 @@ -modbus_rtu_get_rts(3) -===================== - - -NAME ----- -modbus_rtu_get_rts - get the current RTS mode in RTU - - -SYNOPSIS --------- -*int modbus_rtu_get_rts(modbus_t *'ctx');* - - -DESCRIPTION ------------ -The *modbus_rtu_get_rts()* function shall get the current Request To Send mode -of the libmodbus context _ctx_. The possible returned values are: - -* MODBUS_RTU_RTS_NONE -* MODBUS_RTU_RTS_UP -* MODBUS_RTU_RTS_DOWN - -This function can only be used with a context using a RTU backend. - - -RETURN VALUE ------------- -The function shall return the current RTS mode if successful. Otherwise it shall -return -1 and set errno. - - -ERRORS ------- -*EINVAL*:: -The libmodbus backend is not RTU. - - -SEE ALSO --------- -linkmb:modbus_rtu_set_rts[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_rtu_get_rts_delay.txt b/doc/modbus_rtu_get_rts_delay.txt deleted file mode 100644 index 43853d453..000000000 --- a/doc/modbus_rtu_get_rts_delay.txt +++ /dev/null @@ -1,46 +0,0 @@ -modbus_rtu_get_rts_delay(3) -=========================== - - -NAME ----- -modbus_rtu_get_rts_delay - get the current RTS delay in RTU - - -SYNOPSIS --------- -*int modbus_rtu_get_rts_delay(modbus_t *'ctx');* - - -DESCRIPTION ------------ - -The _modbus_rtu_get_rts_delay()_ function shall get the current Request To Send -delay period of the libmodbus context 'ctx'. - -This function can only be used with a context using a RTU backend. - - -RETURN VALUE ------------- -The _modbus_rtu_get_rts_delay()_ function shall return the current RTS delay in -microseconds if successful. Otherwise it shall return -1 and set errno. - - -ERRORS ------- -*EINVAL*:: -The libmodbus backend is not RTU. - - -SEE ALSO --------- -linkmb:modbus_rtu_set_rts_delay[3] - - -AUTHORS -------- -Jimmy Bergström - -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_rtu_set_custom_rts.txt b/doc/modbus_rtu_set_custom_rts.txt deleted file mode 100644 index c17bd3d1f..000000000 --- a/doc/modbus_rtu_set_custom_rts.txt +++ /dev/null @@ -1,45 +0,0 @@ -modbus_rtu_set_custom_rts(3) -============================ - - -NAME ----- -modbus_rtu_set_custom_rts - set a function to be used for custom RTS implementation - - -SYNOPSIS --------- -*int modbus_rtu_set_custom_rts(modbus_t *'ctx', void (*'set_rts') (modbus_t *ctx, int on))* - - -DESCRIPTION ------------ -The _modbus_rtu_set_custom_rts()_ function shall set a custom function to be -called when the RTS pin is to be set before and after a transmission. By default -this is set to an internal function that toggles the RTS pin using an ioctl -call. - -Note that this function adheres to the RTS mode, the values MODBUS_RTU_RTS_UP or -MODBUS_RTU_RTS_DOWN must be used for the function to be called. - -This function can only be used with a context using a RTU backend. - - -RETURN VALUE ------------- -The _modbus_rtu_set_custom_rts()_ function shall return 0 if successful. -Otherwise it shall return -1 and set errno to one of the values defined below. - - -ERRORS ------- -*EINVAL*:: -The libmodbus backend is not RTU. - - -AUTHORS -------- -Jimmy Bergström - -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_rtu_set_rts.txt b/doc/modbus_rtu_set_rts.txt deleted file mode 100644 index 98a231eda..000000000 --- a/doc/modbus_rtu_set_rts.txt +++ /dev/null @@ -1,81 +0,0 @@ -modbus_rtu_set_rts(3) -===================== - - -NAME ----- -modbus_rtu_set_rts - set the RTS mode in RTU - - -SYNOPSIS --------- -*int modbus_rtu_set_rts(modbus_t *'ctx', int 'mode')* - - -DESCRIPTION ------------ -The *modbus_rtu_set_rts()* function shall set the Request To Send mode to -communicate on a RS485 serial bus. By default, the mode is set to -`MODBUS_RTU_RTS_NONE` and no signal is issued before writing data on the wire. - -To enable the RTS mode, the values `MODBUS_RTU_RTS_UP` or `MODBUS_RTU_RTS_DOWN` -must be used, these modes enable the RTS mode and set the polarity at the same -time. When `MODBUS_RTU_RTS_UP` is used, an ioctl call is made with RTS flag -enabled then data is written on the bus after a delay of 1 ms, then another -ioctl call is made with the RTS flag disabled and again a delay of 1 ms occurs. -The `MODBUS_RTU_RTS_DOWN` mode applies the same procedure but with an inverted -RTS flag. - -This function can only be used with a context using a RTU backend. - - -RETURN VALUE ------------- -The function shall return 0 if successful. Otherwise it shall return -1 and set -errno to one of the values defined below. - - -ERRORS ------- -*EINVAL*:: -The libmodbus backend isn't RTU or the mode given in argument is invalid. - - -EXAMPLE -------- -.Enable the RTS mode with positive polarity -[source,c] -------------------- -modbus_t *ctx; -uint16_t tab_reg[10]; - -ctx = modbus_new_rtu("/dev/ttyS0", 115200, 'N', 8, 1); -modbus_set_slave(ctx, 1); -modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS485); -modbus_rtu_set_rts(ctx, MODBUS_RTU_RTS_UP); - -if (modbus_connect(ctx) == -1) { - fprintf(stderr, "Connexion failed: %s\n", modbus_strerror(errno)); - modbus_free(ctx); - return -1; -} - -rc = modbus_read_registers(ctx, 0, 7, tab_reg); -if (rc == -1) { - fprintf(stderr, "%s\n", modbus_strerror(errno)); - return -1; -} - -modbus_close(ctx); -modbus_free(ctx); -------------------- - -SEE ALSO --------- -linkmb:modbus_rtu_get_rts[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_rtu_set_rts_delay.txt b/doc/modbus_rtu_set_rts_delay.txt deleted file mode 100644 index 39af7df3e..000000000 --- a/doc/modbus_rtu_set_rts_delay.txt +++ /dev/null @@ -1,46 +0,0 @@ -modbus_rtu_set_rts_delay(3) -=========================== - - -NAME ----- -modbus_rtu_set_rts_delay - set the RTS delay in RTU - - -SYNOPSIS --------- -*int modbus_rtu_set_rts_delay(modbus_t *'ctx', int 'us');* - - -DESCRIPTION ------------ - -The _modbus_rtu_set_rts_delay()_ function shall set the Request To Send delay -period of the libmodbus context 'ctx'. - -This function can only be used with a context using a RTU backend. - - -RETURN VALUE ------------- -The _modbus_rtu_set_rts_delay()_ function shall return 0 if successful. -Otherwise it shall return -1 and set errno. - - -ERRORS ------- -*EINVAL*:: -The libmodbus backend is not RTU or a negative delay was specified. - - -SEE ALSO --------- -linkmb:modbus_rtu_get_rts_delay[3] - - -AUTHORS -------- -Jimmy Bergström - -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_serial_get_recv_filter.txt b/doc/modbus_serial_get_recv_filter.txt new file mode 100644 index 000000000..f795cad2a --- /dev/null +++ b/doc/modbus_serial_get_recv_filter.txt @@ -0,0 +1,43 @@ +modbus_serial_get_recv_filter(3) +============================= + + +NAME +---- +modbus_serial_get_recv_filter - get the current reception filter _flag_ + + +SYNOPSIS +-------- +*int modbus_serial_get_recv_filter(modbus_t *'ctx');* + + +DESCRIPTION +----------- +The *modbus_serial_get_recv_filter()* function shall get the current reception +filter flag of the libmodbus context _ctx_. The possible returned values are +`FALSE` or `TRUE`. By default, the boolean flag is set to `TRUE`. When the value +_flag_ is set to `TRUE`, only messages to the address of the slave defined in +the context and broadcast messages are returned by *modbus_receive*, the others +are ignored. When the value _flag_ is set to `FALSE` all messages are returned +by *modbus_receive*. + +This function can only be used with a context using a RTU backend. + + +RETURN VALUE +------------ +The function shall return the current reception filter _flag_ if successful. +Otherwise it shall return -1 and set errno. + + +SEE ALSO +-------- +linkmb:modbus_receive[3] +linkmb:modbus_serial_get_recv_filter[3] + + +AUTHORS +------- +The libmodbus documentation was written by Stéphane Raimbault + diff --git a/doc/modbus_serial_get_rts.txt b/doc/modbus_serial_get_rts.txt new file mode 100644 index 000000000..8758878f8 --- /dev/null +++ b/doc/modbus_serial_get_rts.txt @@ -0,0 +1,48 @@ +modbus_serial_get_rts(3) +======================== + + +NAME +---- +modbus_serial_get_rts - get the current RTS mode of the serial communication + + +SYNOPSIS +-------- +*int modbus_serial_get_rts(modbus_t *'ctx');* + + +DESCRIPTION +----------- +The *modbus_serial_get_rts()* function shall get the current Request To Send +mode of the libmodbus context _ctx_. The possible returned values are: + +* MODBUS_SERIAL_RTS_NONE +* MODBUS_SERIAL_RTS_UP +* MODBUS_SERIAL_RTS_DOWN + +This function can only be used with a context using a serial backend (RTU or +ASCII). + + +RETURN VALUE +------------ +The function shall return the current RTS mode if successful. Otherwise it shall +return -1 and set errno. + + +ERRORS +------ +*EINVAL*:: +The libmodbus backend is not serial. + + +SEE ALSO +-------- +linkmb:modbus_serial_set_rts[3] + + +AUTHORS +------- +The libmodbus documentation was written by Stéphane Raimbault + diff --git a/doc/modbus_serial_get_rts_delay.txt b/doc/modbus_serial_get_rts_delay.txt new file mode 100644 index 000000000..7aebda170 --- /dev/null +++ b/doc/modbus_serial_get_rts_delay.txt @@ -0,0 +1,46 @@ +modbus_serial_get_rts_delay(3) +============================== + + +NAME +---- +modbus_serial_get_rts_delay - get the current RTS delay of the serial communication + + +SYNOPSIS +-------- +*int modbus_serial_get_rts_delay(modbus_t *'ctx');* + + +DESCRIPTION +----------- +The _modbus_serial_get_rts_delay()_ function shall get the current Request To +Send delay period of the libmodbus context 'ctx'. + +This function can only be used with a context using a serial backend (RTU or +ASCII). + + +RETURN VALUE +------------ +The _modbus_serial_get_rts_delay()_ function shall return the current RTS delay in +microseconds if successful. Otherwise it shall return -1 and set errno. + + +ERRORS +------ +*EINVAL*:: +The libmodbus backend is not serial. + + +SEE ALSO +-------- +linkmb:modbus_serial_set_rts_delay[3] + + +AUTHORS +------- +Jimmy Bergström + +The libmodbus documentation was written by Stéphane Raimbault + diff --git a/doc/modbus_rtu_get_serial_mode.txt b/doc/modbus_serial_get_serial_mode.txt similarity index 61% rename from doc/modbus_rtu_get_serial_mode.txt rename to doc/modbus_serial_get_serial_mode.txt index b58072836..a3c668608 100644 --- a/doc/modbus_rtu_get_serial_mode.txt +++ b/doc/modbus_serial_get_serial_mode.txt @@ -1,29 +1,29 @@ -modbus_rtu_get_serial_mode(3) -============================= +modbus_serial_get_serial_mode(3) +================================ NAME ---- -modbus_rtu_get_serial_mode - get the current serial mode +modbus_serial_get_serial_mode - get the current serial mode SYNOPSIS -------- -*int modbus_rtu_get_serial_mode(modbus_t *'ctx');* +*int modbus_serial_get_serial_mode(modbus_t *'ctx');* DESCRIPTION ----------- -The *modbus_rtu_get_serial_mode()* function shall return the serial mode +The *modbus_serial_get_serial_mode()* function shall return the serial mode currently used by the libmodbus context: -*MODBUS_RTU_RS232*:: the serial line is set for RS232 communication. RS-232 +*MODBUS_SERIAL_RS232*:: the serial line is set for RS232 communication. RS-232 (Recommended Standard 232) is the traditional name for a series of standards for serial binary single-ended data and control signals connecting between a DTE (Data Terminal Equipment) and a DCE (Data Circuit-terminating Equipment). It is commonly used in computer serial ports -*MODBUS_RTU_RS485*:: the serial line is set for RS485 communication. EIA-485, +*MODBUS_SERIAL_RS485*:: the serial line is set for RS485 communication. EIA-485, also known as TIA/EIA-485 or RS-485, is a standard defining the electrical characteristics of drivers and receivers for use in balanced digital multipoint systems. This standard is widely used for communications in industrial @@ -31,12 +31,12 @@ currently used by the libmodbus context: electrically noisy environments. This function is only available on Linux kernels 2.6.28 onwards and can only be -used with a context using a RTU backend. +used with a context using a serial backend (RTU or ASCII). RETURN VALUE ------------ -The function shall return `MODBUS_RTU_RS232` or `MODBUS_RTU_RS485` if +The function shall return `MODBUS_SERIAL_RS232` or `MODBUS_SERIAL_RS485` if successful. Otherwise it shall return -1 and set errno to one of the values defined below. @@ -44,8 +44,11 @@ defined below. ERRORS ------ *EINVAL*:: -The current libmodbus backend is not RTU. +The current libmodbus backend is not serial. +SEE ALSO +-------- +linkmb:modbus_serial_set_serial_mode[3] AUTHORS ------- diff --git a/doc/modbus_serial_set_custom_rts.txt b/doc/modbus_serial_set_custom_rts.txt new file mode 100644 index 000000000..820eb7632 --- /dev/null +++ b/doc/modbus_serial_set_custom_rts.txt @@ -0,0 +1,46 @@ +modbus_serial_set_custom_rts(3) +=============================== + + +NAME +---- +modbus_serial_set_custom_rts - set a function to be used for custom RTS implementation + + +SYNOPSIS +-------- +*int modbus_serial_set_custom_rts(modbus_t *'ctx', void (*'set_rts') (modbus_t *ctx, int on))* + + +DESCRIPTION +----------- +The _modbus_serial_set_custom_rts()_ function shall set a custom function to be +called when the RTS pin is to be set before and after a transmission. By default +this is set to an internal function that toggles the RTS pin using an ioctl +call. + +Note that this function adheres to the RTS mode, the values MODBUS_SERIAL_RTS_UP +or MODBUS_SERIAL_RTS_DOWN must be used for the function to be called. + +This function can only be used with a context using a serial backend (RTU or +ASCII). + + +RETURN VALUE +------------ +The _modbus_serial_set_custom_rts()_ function shall return 0 if successful. +Otherwise it shall return -1 and set errno to one of the values defined below. + + +ERRORS +------ +*EINVAL*:: +The libmodbus backend is not serial. + + +AUTHORS +------- +Jimmy Bergström + +The libmodbus documentation was written by Stéphane Raimbault + diff --git a/doc/modbus_serial_set_recv_filter.txt b/doc/modbus_serial_set_recv_filter.txt new file mode 100644 index 000000000..8688ba09c --- /dev/null +++ b/doc/modbus_serial_set_recv_filter.txt @@ -0,0 +1,38 @@ +modbus_serial_set_recv_filter(3) +============================= + +NAME +---- +modbus_serial_set_recv_filter - set reception filter flag of the context + + +SYNOPSIS +-------- +*int modbus_serial_set_recv_filter(modbus_t *'ctx', int 'flag');* + + +DESCRIPTION +----------- +The *modbus_serial_set_recv_filter()* function shall set the reception filter flag +of the *modbus_t* context by using the argument _flag_. By default, the boolean +flag is set to `TRUE`. When the value _flag_ is set to `TRUE`, only messages to +the address of the slave defined in the context and broadcast messages are +returned by *modbus_receive*, the others are ignored. When the value _flag_ is +set to `FALSE` all messages are returned by *modbus_receive*. + +This function can only be used with a context using a RTU backend. + +RETURN VALUE +------------ +The function shall return 0 if successful. Otherwise it shall return -1 and set errno. + + +SEE ALSO +-------- +linkmb:modbus_receive[3] +linkmb:modbus_serial_get_recv_filter[3] + +AUTHORS +------- +The libmodbus documentation was written by Stéphane Raimbault + diff --git a/doc/modbus_serial_set_rts.txt b/doc/modbus_serial_set_rts.txt new file mode 100644 index 000000000..66f3ad422 --- /dev/null +++ b/doc/modbus_serial_set_rts.txt @@ -0,0 +1,82 @@ +modbus_serial_set_rts(3) +======================== + + +NAME +---- +modbus_serial_set_rts - set the RTS mode of the serial communication + + +SYNOPSIS +-------- +*int modbus_serial_set_rts(modbus_t *'ctx', int 'mode')* + + +DESCRIPTION +----------- +The *modbus_serial_set_rts()* function shall set the Request To Send mode to +communicate on a RS485 serial bus. By default, the mode is set to +`MODBUS_SERIAL_RTS_NONE` and no signal is issued before writing data on the +wire. + +To enable the RTS mode, the values `MODBUS_SERIAL_RTS_UP` or +`MODBUS_SERIAL_RTS_DOWN` must be used, these modes enable the RTS mode and set +the polarity at the same time. When `MODBUS_SERIAL_RTS_UP` is used, an ioctl +call is made with RTS flag enabled then data is written on the bus after a delay +of 1 ms, then another ioctl call is made with the RTS flag disabled and again a +delay of 1 ms occurs. The `MODBUS_SERIAL_RTS_DOWN` mode applies the same +procedure but with an inverted RTS flag. + +This function can only be used with a context using a serial backend (RTU or +ASCII). + + +RETURN VALUE +------------ +The function shall return 0 if successful. Otherwise it shall return -1 and set +errno to one of the values defined below. + + +ERRORS +------ +*EINVAL*:: +The libmodbus backend isn't serial or the mode given in argument is invalid. + + +EXAMPLE +------- +.Enable the RTS mode with positive polarity +[source,c] +------------------- +modbus_t *ctx; +uint16_t tab_reg[10]; + +ctx = modbus_new_rtu("/dev/ttyS0", 115200, 'N', 8, 1); +modbus_set_slave(ctx, 1); +modbus_serial_set_rts(ctx, MODBUS_SERIAL_RTS_UP); + +if (modbus_connect(ctx) == -1) { + fprintf(stderr, "Connexion failed: %s\n", modbus_strerror(errno)); + modbus_free(ctx); + return -1; +} + +rc = modbus_read_registers(ctx, 0, 7, tab_reg); +if (rc == -1) { + fprintf(stderr, "%s\n", modbus_strerror(errno)); + return -1; +} + +modbus_close(ctx); +modbus_free(ctx); +------------------- + +SEE ALSO +-------- +linkmb:modbus_serial_get_rts[3] + + +AUTHORS +------- +The libmodbus documentation was written by Stéphane Raimbault + diff --git a/doc/modbus_serial_set_rts_delay.txt b/doc/modbus_serial_set_rts_delay.txt new file mode 100644 index 000000000..417030c60 --- /dev/null +++ b/doc/modbus_serial_set_rts_delay.txt @@ -0,0 +1,45 @@ +modbus_serial_set_rts_delay(3) +============================== + + +NAME +---- +modbus_serial_set_rts_delay - set the RTS delay of the serial communication + + +SYNOPSIS +-------- +*int modbus_serial_set_rts_delay(modbus_t *'ctx', int 'us');* + + +DESCRIPTION +----------- +The _modbus_serial_set_rts_delay()_ function shall set the Request To Send delay +period of the libmodbus context 'ctx'. + +This function can only be used with a context using a serial backend (RTU or ASCII). + + +RETURN VALUE +------------ +The _modbus_serial_set_rts_delay()_ function shall return 0 if successful. +Otherwise it shall return -1 and set errno. + + +ERRORS +------ +*EINVAL*:: +The libmodbus backend is not serail or a negative delay was specified. + + +SEE ALSO +-------- +linkmb:modbus_serial_get_rts_delay[3] + + +AUTHORS +------- +Jimmy Bergström + +The libmodbus documentation was written by Stéphane Raimbault + diff --git a/doc/modbus_rtu_set_serial_mode.txt b/doc/modbus_serial_set_serial_mode.txt similarity index 61% rename from doc/modbus_rtu_set_serial_mode.txt rename to doc/modbus_serial_set_serial_mode.txt index 7086dc4a8..93bb20cb6 100644 --- a/doc/modbus_rtu_set_serial_mode.txt +++ b/doc/modbus_serial_set_serial_mode.txt @@ -1,29 +1,32 @@ -modbus_rtu_set_serial_mode(3) -============================= +modbus_serial_set_serial_mode(3) +================================ NAME ---- -modbus_rtu_set_serial_mode - set the serial mode +modbus_serial_set_serial_mode - set the serial mode. Warning, very limited support. SYNOPSIS -------- -*int modbus_rtu_set_serial_mode(modbus_t *'ctx', int 'mode');* +*int modbus_serial_set_serial_mode(modbus_t *'ctx', int 'mode');* DESCRIPTION ----------- -The *modbus_rtu_set_serial_mode()* function shall set the selected serial +Warning, this function seems to be only supported by the Linux driver of the +ETRAX 100LX chip. + +The *modbus_serial_set_serial_mode()* function shall set the selected serial mode: -*MODBUS_RTU_RS232*:: the serial line is set for RS232 communication. RS-232 +*MODBUS_SERIAL_RS232*:: the serial line is set for RS232 communication. RS-232 (Recommended Standard 232) is the traditional name for a series of standards for serial binary single-ended data and control signals connecting between a DTE (Data Terminal Equipment) and a DCE (Data Circuit-terminating - Equipment). It is commonly used in computer serial ports + Equipment). It is commonly used in computer serial ports. -*MODBUS_RTU_RS485*:: the serial line is set for RS485 communication. EIA-485, +*MODBUS_SERIAL_RS485*:: the serial line is set for RS485 communication. EIA-485, also known as TIA/EIA-485 or RS-485, is a standard defining the electrical characteristics of drivers and receivers for use in balanced digital multipoint systems. This standard is widely used for communications in industrial @@ -42,13 +45,16 @@ errno to one of the values defined below. ERRORS ------ *EINVAL*:: -The current libmodbus backend is not RTU. +The current libmodbus backend is not serial. *ENOTSUP*:: The function is not supported on your platform. If the call to ioctl() fails, the error code of ioctl will be returned. +SEE ALSO +-------- +linkmb:modbus_serial_get_serial_mode[3] AUTHORS ------- diff --git a/libmodbus.pc.in b/libmodbus.pc.in deleted file mode 100644 index f1a9cbf82..000000000 --- a/libmodbus.pc.in +++ /dev/null @@ -1,10 +0,0 @@ -prefix=@prefix@ -exec_prefix=@exec_prefix@ -libdir=@libdir@ -includedir=@includedir@ - -Name: modbus -Description: Modbus library -Version: @VERSION@ -Libs: -L${libdir} -lmodbus -Cflags: -I${includedir}/modbus diff --git a/libmodbusepsi.pc.in b/libmodbusepsi.pc.in new file mode 100644 index 000000000..54f4ceb31 --- /dev/null +++ b/libmodbusepsi.pc.in @@ -0,0 +1,10 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: modbusepsi +Description: Modbus library (epsilonrt version) +Version: @VERSION@ +Libs: -L${libdir} -lmodbusepsi +Cflags: -I${includedir}/modbusepsi diff --git a/src/Makefile.am b/src/Makefile.am index 551fe4328..e270cb6ca 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,5 @@ EXTRA_DIST = -lib_LTLIBRARIES = libmodbus.la +lib_LTLIBRARIES = libmodbusepsi.la AM_CPPFLAGS = \ -include $(top_builddir)/config.h \ @@ -9,33 +9,39 @@ AM_CPPFLAGS = \ AM_CFLAGS = ${my_CFLAGS} -libmodbus_la_SOURCES = \ +libmodbusepsi_la_SOURCES = \ modbus.c \ 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 \ + modbus-ascii.c \ + modbus-ascii.h \ + modbus-ascii-private.h \ modbus-tcp.c \ modbus-tcp.h \ modbus-tcp-private.h \ modbus-version.h -libmodbus_la_LDFLAGS = -no-undefined \ +libmodbusepsi_la_LDFLAGS = -no-undefined \ -version-info $(LIBMODBUS_LT_VERSION_INFO) if OS_WIN32 -libmodbus_la_LIBADD = -lwsock32 +libmodbusepsi_la_LIBADD = -lwsock32 endif if OS_QNX -libmodbus_la_LIBADD = -lsocket +libmodbusepsi_la_LIBADD = -lsocket endif # Header files to install -libmodbusincludedir = $(includedir)/modbus -libmodbusinclude_HEADERS = modbus.h modbus-version.h modbus-rtu.h modbus-tcp.h +libmodbusincludedir = $(includedir)/modbusepsi +libmodbusinclude_HEADERS = modbus.h modbus-version.h modbus-rtu.h modbus-tcp.h modbus-serial.h modbus-ascii.h DISTCLEANFILES = modbus-version.h EXTRA_DIST += modbus-version.h.in diff --git a/src/modbus-ascii-private.h b/src/modbus-ascii-private.h new file mode 100644 index 000000000..61ae5fb8a --- /dev/null +++ b/src/modbus-ascii-private.h @@ -0,0 +1,17 @@ +/* + * 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 + +/* LRC8 + \r + \n */ +#define _MODBUS_ASCII_CHECKSUM_LENGTH 3 + +#endif /* MODBUS_ASCII_PRIVATE_H */ diff --git a/src/modbus-ascii.c b/src/modbus-ascii.c new file mode 100644 index 000000000..cfc3effad --- /dev/null +++ b/src/modbus-ascii.c @@ -0,0 +1,266 @@ +/* + * 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-private.h" + +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; +} + +/* Build an 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 7; +} + +/* Build an ASCII response header */ +static int _modbus_ascii_build_response_basis(sft_t *sft, uint8_t *rsp) +{ + rsp[0] = ':'; + rsp[1] = sft->slave; + rsp[2] = sft->function; + + return 3; +} + +/* 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 two's complementing of the result */ + return -lrc; +} + +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) +{ + /* Skip colon */ + uint8_t lrc = lrc8(req + 1, req_length - 1); + + 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]; + modbus_serial_t *ctx_serial = ctx->backend_data; + + /* 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 && + ctx_serial->disable_receive_filter == FALSE) { + 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; + } + + /* Strip ":" and "\r\n" */ + lrc = lrc8(msg + 1, msg_length - 3); + + /* 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) +{ + /* FIXME Ugly copy! */ + uint8_t ascii_req[MODBUS_ASCII_MAX_ADU_LENGTH]; + ssize_t i, j = 0; + + for (i = 0; i < req_length; i++) { + if ((i == 0 && req[i] == ':') || + (i == req_length - 2 && req[i] == '\r') || + (i == req_length - 1 && 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); + /* FIXME */ + return ((size - 3) / 2) + 3; +} + +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_serial_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) +{ + return _modbus_serial_new(&_modbus_ascii_backend, + device, baud, parity, data_bit, stop_bit); +} diff --git a/src/modbus-ascii.h b/src/modbus-ascii.h new file mode 100644 index 000000000..84d1c6044 --- /dev/null +++ b/src/modbus-ascii.h @@ -0,0 +1,26 @@ +/* + * Copyright © 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 513 + +MODBUS_API modbus_t* modbus_new_ascii(const char *device, int baud, char parity, + int data_bit, int stop_bit); + +MODBUS_END_DECLS + +#endif /* MODBUS_ASCII_H */ diff --git a/src/modbus-private.h b/src/modbus-private.h index 198baeffd..b4e50465f 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 1c7d4781a..e31f722d3 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,52 +15,4 @@ #define _MODBUS_RTU_CHECKSUM_LENGTH 2 -#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 rts_delay; - int onebyte_time; - void (*set_rts) (modbus_t *ctx, int on); -#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 56c812ca3..3a2f5ed7a 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-private.h" /* Table of CRC values for high-order byte */ static const uint8_t table_crc_hi[] = { @@ -87,21 +81,6 @@ static const uint8_t table_crc_lo[] = { 0x43, 0x83, 0x41, 0x81, 0x80, 0x40 }; -/* 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; -} - /* Builds a RTU request header */ static int _modbus_rtu_build_request_basis(modbus_t *ctx, int function, int addr, int nb, @@ -161,198 +140,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(modbus_t *ctx, int on) -{ - int fd = ctx->s; - 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"); - } - - ctx_rtu->set_rts(ctx, ctx_rtu->rts == MODBUS_RTU_RTS_UP); - usleep(ctx_rtu->rts_delay); - - size = write(ctx->s, req, req_length); - - usleep(ctx_rtu->onebyte_time * req_length + ctx_rtu->rts_delay); - ctx_rtu->set_rts(ctx, 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 EMBBADCRC. */ @@ -362,10 +149,12 @@ static int _modbus_rtu_check_integrity(modbus_t *ctx, uint8_t *msg, uint16_t crc_calculated; uint16_t crc_received; int slave = msg[0]; + modbus_serial_t *ctx_serial = ctx->backend_data; /* Filter on the Modbus unit identifier (slave) in RTU mode to avoid useless * CRC computing. */ - if (slave != ctx->slave && slave != MODBUS_BROADCAST_ADDRESS) { + if (slave != ctx->slave && slave != MODBUS_BROADCAST_ADDRESS && + ctx_serial->disable_receive_filter == FALSE) { if (ctx->debug) { printf("Request for slave %d ignored (not %d)\n", slave, ctx->slave); } @@ -386,914 +175,43 @@ 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; - - if (mode == MODBUS_RTU_RS485) { - // Get - if (ioctl(ctx->s, TIOCGRS485, &rs485conf) < 0) { - return -1; - } - // Set - 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, TIOCGRS485, &rs485conf) < 0) { - return -1; - } - rs485conf.flags &= ~SER_RS485_ENABLED; - 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; -} - -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 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; - } -} - -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 */ - ctx_rtu->set_rts(ctx, 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; -} - -int modbus_rtu_set_custom_rts(modbus_t *ctx, void (*set_rts) (modbus_t *ctx, int on)) -{ - 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; - ctx_rtu->set_rts = set_rts; - return 0; -#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_rtu_get_rts_delay(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_rtu = (modbus_rtu_t *)ctx->backend_data; - return ctx_rtu->rts_delay; -#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_rtu_set_rts_delay(modbus_t *ctx, int us) -{ - if (ctx == NULL || us < 0) { - errno = EINVAL; - return -1; - } - - if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) { -#if HAVE_DECL_TIOCM_RTS - modbus_rtu_t *ctx_rtu; - ctx_rtu = (modbus_rtu_t *)ctx->backend_data; - ctx_rtu->rts_delay = us; - return 0; -#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; -} - static void _modbus_rtu_free(modbus_t *ctx) { - if (ctx->backend_data) { - free(((modbus_rtu_t *)ctx->backend_data)->device); - free(ctx->backend_data); - } - - free(ctx); + _modbus_serial_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, - _modbus_set_slave, + _modbus_serial_set_slave, _modbus_rtu_build_request_basis, _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 }; modbus_t* modbus_new_rtu(const char *device, - int baud, char parity, int data_bit, - int stop_bit) + int baud, char parity, int data_bit, 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)); - if (ctx == NULL) { - return NULL; - } - - _modbus_init_common(ctx); - ctx->backend = &_modbus_rtu_backend; - ctx->backend_data = (modbus_rtu_t *)malloc(sizeof(modbus_rtu_t)); - if (ctx->backend_data == NULL) { - modbus_free(ctx); - errno = ENOMEM; - return NULL; - } - ctx_rtu = (modbus_rtu_t *)ctx->backend_data; - - /* Device name and \0 */ - ctx_rtu->device = (char *)malloc((strlen(device) + 1) * sizeof(char)); - if (ctx_rtu->device == NULL) { - modbus_free(ctx); - errno = ENOMEM; - return NULL; - } - 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_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 = 1000000 * (1 + data_bit + (parity == 'N' ? 0 : 1) + stop_bit) / baud; - - /* The internal function is used by default to set RTS */ - ctx_rtu->set_rts = _modbus_rtu_ioctl_rts; - - /* The delay before and after transmission when toggling the RTS pin */ - ctx_rtu->rts_delay = ctx_rtu->onebyte_time; -#endif - - ctx_rtu->confirmation_to_ignore = FALSE; - - return ctx; + return _modbus_serial_new(&_modbus_rtu_backend, + device, baud, parity, data_bit, stop_bit); } diff --git a/src/modbus-rtu.h b/src/modbus-rtu.h index fa3765521..fdb2fadab 100644 --- a/src/modbus-rtu.h +++ b/src/modbus-rtu.h @@ -8,6 +8,7 @@ #define MODBUS_RTU_H #include "modbus.h" +#include "modbus-serial.h" MODBUS_BEGIN_DECLS @@ -19,23 +20,9 @@ MODBUS_BEGIN_DECLS MODBUS_API modbus_t* modbus_new_rtu(const char *device, int baud, char parity, int data_bit, int stop_bit); -#define MODBUS_RTU_RS232 0 -#define MODBUS_RTU_RS485 1 - -MODBUS_API int modbus_rtu_set_serial_mode(modbus_t *ctx, int mode); -MODBUS_API int modbus_rtu_get_serial_mode(modbus_t *ctx); - -#define MODBUS_RTU_RTS_NONE 0 -#define MODBUS_RTU_RTS_UP 1 -#define MODBUS_RTU_RTS_DOWN 2 - -MODBUS_API int modbus_rtu_set_rts(modbus_t *ctx, int mode); -MODBUS_API int modbus_rtu_get_rts(modbus_t *ctx); - -MODBUS_API int modbus_rtu_set_custom_rts(modbus_t *ctx, void (*set_rts) (modbus_t *ctx, int on)); - -MODBUS_API int modbus_rtu_set_rts_delay(modbus_t *ctx, int us); -MODBUS_API int modbus_rtu_get_rts_delay(modbus_t *ctx); +/* Deprecated */ +#define MODBUS_RTU_RS232 MODBUS_SERIAL_RS232 +#define MODBUS_RTU_RS485 MODBUS_SERIAL_RS485 MODBUS_END_DECLS diff --git a/src/modbus-serial-private.h b/src/modbus-serial-private.h new file mode 100644 index 000000000..3e027cafb --- /dev/null +++ b/src/modbus-serial-private.h @@ -0,0 +1,88 @@ +/* + * 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-private.h" +#include "modbus-serial.h" + +#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_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 rts_delay; + int onebyte_time; + void (*set_rts) (modbus_t *ctx, int on); +#endif + /* To handle many slaves on the same link */ + int confirmation_to_ignore; + int disable_receive_filter; +} modbus_serial_t; + +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_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); + +int _modbus_serial_set_slave(modbus_t *ctx, int slave); +void _modbus_serial_free(modbus_serial_t *serial_ctx); +modbus_t* _modbus_serial_new(const modbus_backend_t *modbus_backend, + const char *device, int baud, char parity, int data_bit, int stop_bit); + +#endif /* MODBUS_SERIAL_PRIVATE_H */ diff --git a/src/modbus-serial.c b/src/modbus-serial.c new file mode 100644 index 000000000..31317994c --- /dev/null +++ b/src/modbus-serial.c @@ -0,0 +1,1125 @@ +/* + * 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 + +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_SERIAL_RTS_NONE) { + ssize_t size; + + if (ctx->debug) { + fprintf(stderr, "Sending request using RTS signal\n"); + } + + ctx_serial->set_rts(ctx, ctx_serial->rts == MODBUS_SERIAL_RTS_UP); + usleep(ctx_serial->rts_delay); + + size = write(ctx->s, req, req_length); + + usleep(ctx_serial->onebyte_time * req_length + ctx_serial->rts_delay); + ctx_serial->set_rts(ctx, 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_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; +} + +/* Only the serial port driver for the ETRAX 100LX chip seems to support it! */ +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 + return MODBUS_SERIAL_RS232; +#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_SERIAL_RTS_NONE || + mode == MODBUS_SERIAL_RTS_UP || + mode == MODBUS_SERIAL_RTS_DOWN) { + ctx_serial->rts = mode; + + /* Set the RTS bit in order to not reserve the RS485 bus */ + ctx_serial->set_rts(ctx, ctx_serial->rts != MODBUS_SERIAL_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; + } +} + +int modbus_serial_set_custom_rts(modbus_t *ctx, void (*set_rts) (modbus_t *ctx, int on)) +{ + 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; + ctx_serial->set_rts = set_rts; + return 0; +#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_delay(modbus_t *ctx, int us) +{ + if (ctx == NULL || us < 0) { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_SERIAL) { +#if HAVE_DECL_TIOCM_RTS + modbus_serial_t *ctx_serial; + ctx_serial = (modbus_serial_t *)ctx->backend_data; + ctx_serial->rts_delay = us; + return 0; +#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_get_recv_filter(modbus_t *ctx) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_SERIAL) { + modbus_serial_t *ctx_serial = ctx->backend_data; + return (ctx_serial->disable_receive_filter == FALSE); + } + errno = EINVAL; + return -1; +} + +int modbus_serial_set_recv_filter(modbus_t *ctx, int on) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_SERIAL) { + modbus_serial_t *ctx_serial = ctx->backend_data; + + ctx_serial->disable_receive_filter = (on == FALSE); + } + /* Wrong backend specified */ + errno = EINVAL; + return -1; +} + +int modbus_serial_get_rts_delay(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_serial = (modbus_serial_t *)ctx->backend_data; + return ctx_serial->rts_delay; +#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; +} + +/* Define the slave ID of the remote device to talk in master mode or set the + * internal slave ID in slave mode */ +int _modbus_serial_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; +} + +void _modbus_serial_free(modbus_serial_t *ctx_serial) +{ + if (ctx_serial) { + free(ctx_serial->device); + } + free(ctx_serial); +} + +#if HAVE_DECL_TIOCM_RTS +static void _modbus_serial_ioctl_rts(modbus_t *ctx, int on) +{ + int fd = ctx->s; + int flags; + + ioctl(fd, TIOCMGET, &flags); + if (on) { + flags |= TIOCM_RTS; + } else { + flags &= ~TIOCM_RTS; + } + ioctl(fd, TIOCMSET, &flags); +} +#endif + +modbus_t* _modbus_serial_new(const modbus_backend_t *modbus_backend, const char *device, + int baud, char parity, int data_bit, int stop_bit) +{ + modbus_t *ctx; + modbus_serial_t *ctx_serial; + + /* 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 '%c' is invalid (not 'N', 'E' or 'O')\n", parity); + errno = EINVAL; + return NULL; + } + + ctx = (modbus_t *)malloc(sizeof(modbus_t)); + _modbus_init_common(ctx); + ctx->backend = modbus_backend; + ctx->backend_data = (modbus_serial_t*)malloc(sizeof(modbus_serial_t)); + + ctx_serial = ctx->backend_data; + 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 is set by default */ + ctx_serial->serial_mode = MODBUS_SERIAL_RS232; +#endif + +#if HAVE_DECL_TIOCM_RTS + /* The RTS use has been set by default */ + ctx_serial->rts = MODBUS_SERIAL_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; + + /* The internal function is used by default to set RTS */ + ctx_serial->set_rts = _modbus_serial_ioctl_rts; + + /* The delay before and after transmission when toggling the RTS pin */ + ctx_serial->rts_delay = ctx_serial->onebyte_time; +#endif + + ctx_serial->confirmation_to_ignore = FALSE; + + return ctx; +} diff --git a/src/modbus-serial.h b/src/modbus-serial.h new file mode 100644 index 000000000..66e58327b --- /dev/null +++ b/src/modbus-serial.h @@ -0,0 +1,37 @@ +/* + * Copyright © Stéphane Raimbault + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#ifndef MODBUS_SERIAL_H +#define MODBUS_SERIAL_H + +#include "modbus.h" + +MODBUS_BEGIN_DECLS + +#define MODBUS_SERIAL_RS232 0 +#define MODBUS_SERIAL_RS485 1 + +MODBUS_API int modbus_serial_set_serial_mode(modbus_t *ctx, int mode); +MODBUS_API 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 + +MODBUS_API int modbus_serial_set_rts(modbus_t *ctx, int mode); +MODBUS_API int modbus_serial_get_rts(modbus_t *ctx); + +MODBUS_API int modbus_serial_set_custom_rts(modbus_t *ctx, void (*set_rts) (modbus_t *ctx, int on)); + +MODBUS_API int modbus_serial_set_rts_delay(modbus_t *ctx, int us); +MODBUS_API int modbus_serial_get_rts_delay(modbus_t *ctx); + +MODBUS_API int modbus_serial_set_recv_filter(modbus_t *ctx, int on); +MODBUS_API int modbus_serial_get_recv_filter(modbus_t *ctx); + +MODBUS_END_DECLS + +#endif /* MODBUS_SERIAL_H */ diff --git a/src/modbus.c b/src/modbus.c index 41a421359..2f53c89c8 100644 --- a/src/modbus.c +++ b/src/modbus.c @@ -998,7 +998,7 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req, } /* Suppress any responses when the request was a broadcast */ - return (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU && + return (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_SERIAL && slave == MODBUS_BROADCAST_ADDRESS) ? 0 : send_msg(ctx, rsp, rsp_length); } diff --git a/src/modbus.h b/src/modbus.h index c63f5ceb4..bd4936db7 100644 --- a/src/modbus.h +++ b/src/modbus.h @@ -286,7 +286,9 @@ MODBUS_API void modbus_set_float_badc(float f, uint16_t *dest); MODBUS_API void modbus_set_float_cdab(float f, uint16_t *dest); #include "modbus-tcp.h" +#include "modbus-serial.h" #include "modbus-rtu.h" +#include "modbus-ascii.h" MODBUS_END_DECLS diff --git a/tests/Makefile.am b/tests/Makefile.am index 38fa21c09..7de2e402f 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -11,7 +11,7 @@ noinst_PROGRAMS = \ version common_ldflags = \ - $(top_builddir)/src/libmodbus.la + $(top_builddir)/src/libmodbusepsi.la bandwidth_server_one_SOURCES = bandwidth-server-one.c bandwidth_server_one_LDADD = $(common_ldflags) diff --git a/tests/unit-test-client.c b/tests/unit-test-client.c index e95ea69fa..99b2144ce 100644 --- a/tests/unit-test-client.c +++ b/tests/unit-test-client.c @@ -99,7 +99,13 @@ int main(int argc, char *argv[]) MODBUS_ERROR_RECOVERY_PROTOCOL); if (use_backend == RTU) { + int serial_mode; + modbus_set_slave(ctx, SERVER_ID); + + /* Not really tested but called at least */ + serial_mode = modbus_serial_get_serial_mode(ctx); + ASSERT_TRUE(serial_mode == MODBUS_SERIAL_RS232, ""); } modbus_get_response_timeout(ctx, &old_response_to_sec, &old_response_to_usec);