diff --git a/.clabot b/.clabot new file mode 100644 index 000000000..8adaacb3d --- /dev/null +++ b/.clabot @@ -0,0 +1,66 @@ +{ + "contributors": [ + "0x34d", + "20162026", + "2128506", + "alongl", + "arkapkaj", + "bobbybelieve", + "ccdmuro", + "cedricboudinet", + "DavidKorczynski", + "devotip", + "diplfranzhoepfinger", + "embeddedmz", + "fcntlcc", + "fedepell", + "franzhoepfinger", + "franzhollerer", + "ghorwin", + "jbysewski", + "jcarrano", + "jeroendoggen", + "jetforme", + "jiriki86", + "jordanjohnson56", + "kay30kim", + "kyllingstad", + "mek-x", + "merkag", + "mhei", + "msk-repo-0x4d", + "ndunks", + "peternewman", + "philj56", + "raymondbh", + "ringlej", + "rm5248", + "rongli-eaton", + "sebastianpsm", + "SergeyMy", + "sirsoweird", + "taikiakita", + "vancepym", + "vvck", + "woodsnake", + "EchterAgo", + "Su-Yuming", + "anrodlo", + "chowchin", + "desteemy", + "jimklimov", + "jonathanfleming135", + "mbeards", + "richardash1981", + "titania7777", + "micrictor", + "jyjsunny", + "ProtocolSpecChecker", + "raenray", + "hbrodin", + "PGZXB", + "philj56", + "diplfranzhoepfinger" + ], + "message": "We require contributors to sign our Contributor License Agreement. In order for us to review and merge your code, please fill https://forms.gle/5635zjphDo5JEJQSA to get added. Your document will be manually checked by the maintainer. Be patient..." +} diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..9521e9f09 --- /dev/null +++ b/.clang-format @@ -0,0 +1,56 @@ +--- +BasedOnStyle: LLVM +AlignArrayOfStructures: Left +AlignOperands: true +AlignConsecutiveAssignments: false +AlignConsecutiveMacros: true +AlignEscapedNewlines: Left +AlignTrailingComments: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: Never +BreakBeforeBraces: Custom +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BinPackArguments: false +BinPackParameters: false +ColumnLimit: 90 +ConstructorInitializerAllOnOneLineOrOnePerLine: true +IncludeBlocks: Preserve +IndentWidth: 4 +ObjCBlockIndentWidth: 4 +PointerAlignment: Right +ReferenceAlignment: Right +SpaceAfterCStyleCast: true +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatementsExceptForEachMacros +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SeparateDefinitionBlocks: Always +UseTab: Never +PPIndentWidth: 2 diff --git a/.dir-locals.el b/.dir-locals.el deleted file mode 100644 index 501d1d0a9..000000000 --- a/.dir-locals.el +++ /dev/null @@ -1,4 +0,0 @@ -((nil . ((indent-tabs-mode . nil) - (c-basic-offset . 4) - (fill-column . 80)))) - diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..640a2a203 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 + +# Tab indentation (no size specified) +[Makefile] +indent_style = tab diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..8eaacf6e0 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: [stephane] +polar: stephane diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..126c7f31f --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,20 @@ +name: Build libmodbus + +on: + push: + branches: ["master"] + pull_request: + branches: ["master"] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6.0.1 + - name: configure + run: ./autogen.sh && ./configure + - name: make + run: make + - name: make distcheck + run: make distcheck diff --git a/.gitignore b/.gitignore index 9e9a23911..c562d901c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +# Temporary files *~ *.swp *.o @@ -7,10 +8,14 @@ *.trs .deps .libs -GPATH -GRTAGS -GSYMS -GTAGS +.DS_Store + +# Editors +/*.sublime-* +/.vscode +/.venv + +# Generated by Autotools INSTALL Makefile Makefile.in @@ -22,25 +27,27 @@ Makefile.in /configure.scan /depcomp /install-sh +/libmodbus.pc /libtool /ltmain.sh /missing -/libmodbus.pc /stamp-h1 -/*.sublime-* -/.vscode src/modbus-version.h src/win32/modbus.dll.manifest +tests/unit-test.h + +# mkdocs +/site + +# Binary tests/bandwidth-client tests/bandwidth-server-many-up tests/bandwidth-server-one tests/random-test-client tests/random-test-server +tests/proxy-test-client +tests/proxy-test-server tests/unit-test-client -tests/unit-test.h tests/unit-test-server tests/version tests/stamp-h2 -doc/*.html -doc/*.3 -doc/*.7 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e04b38808..000000000 --- a/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: c - -compiler: - - gcc - - clang - -script: ./autogen.sh && ./configure && make && make check diff --git a/CLA.txt b/CLA.txt new file mode 100644 index 000000000..f3111c30c --- /dev/null +++ b/CLA.txt @@ -0,0 +1,113 @@ +libmodbus Contributor License Agreement + +Thank you for your interest in contributing to libmodbus (the "Project") created +by Stéphane Raimbault. + +In order to clarify the intellectual property license granted with Contributions +from any person or entity, the Project must have a Contributor License Agreement +(CLA) on file that has been signed by each Contributor, indicating agreement to +the license terms below. This license is for your protection as a Contributor as +well as the protection of the Project and its users; it does not change your +rights to use your own Contributions for any other purpose. + +This Agreement allows an entity (the "Corporation") to submit Contributions to +The Project, to authorize Contributions submitted by its designated employees to +The Project, and to grant copyright and patent licenses thereto. + +If you have not already done so, please complete and sign this Agreement using +the electronic signature portal made available to you by the Project or its +third-party service providers, or email a PDF of the signed agreement to +stephane.raimbault@gmail.com. Please read this document carefully before signing +and keep a copy for your records. + +You accept and agree to the following terms and conditions for Your present and +future Contributions submitted to the Project. Except for the license granted +herein to The Project and recipients of software distributed by The Project, You +reserve all right, title, and interest in and to Your Contributions. + +1. Definitions + +"You" (or "Your") shall mean the copyright owner or legal entity authorized by +the copyright owner that is making this Agreement with The Project. For legal +entities, the entity making a Contribution and all other entities that control, +are controlled by, or are under common control with that entity are considered +to be a single Contributor. For the purposes of this definition, "control" means +(i) the power, direct or indirect, to cause the direction or management of such +entity, whether by contract or otherwise, or (ii) ownership of fifty percent +(50%) or more of the outstanding shares, or (iii) beneficial ownership of such +entity. + +"Contribution" shall mean the code, documentation or other original works of +authorship expressly identified in Schedule B, as well as any original work of +authorship, including any modifications or additions to an existing work, that +is intentionally submitted by You to The Project for inclusion in, or +documentation of, any of the products owned or managed by the Foundation (the +"Work"). For the purposes of this definition, "submitted" means any form of +electronic, verbal, or written communication sent to The Project or its +representatives, including but not limited to communication on electronic +mailing lists, source code control systems, and issue tracking systems that are +managed by, or on behalf of, The Project for the purpose of discussing and +improving the Work, but excluding communication that is conspicuously marked or +otherwise designated in writing by You as "Not a Contribution." + +2. Grant of Copyright License. Subject to the terms and conditions of this + Agreement, You hereby grant to The Project and to recipients of software + distributed by The Project a perpetual, worldwide, non-exclusive, no-charge, + royalty-free, irrevocable copyright license to reproduce, prepare derivative + works of, publicly display, publicly perform, sublicense, and distribute Your + Contributions and such derivative works. + +3. Grant of Patent License. Subject to the terms and conditions of this + Agreement, You hereby grant to The Project and to recipients of software + distributed by The Project a perpetual, worldwide, non-exclusive, no-charge, + royalty-free, irrevocable (except as stated in this section) patent license + to make, have made, use, offer to sell, sell, import, and otherwise transfer + the Work, where such license applies only to those patent claims licensable + by You that are necessarily infringed by Your Contribution(s) alone or by + combination of Your Contribution(s) with the Work to which such + Contribution(s) were submitted. If any entity institutes patent litigation + against You or any other entity (including a cross-claim or counterclaim in a + lawsuit) alleging that Your Contribution, or the Work to which You have + contributed, constitutes direct or contributory patent infringement, then any + patent licenses granted to that entity under this Agreement for that + Contribution or Work shall terminate as of the date such litigation is filed. + +4. You represent that You are legally entitled to grant the above license. If + You are an individual, if Your employer(s) has rights to intellectual + property that You create that includes Your Contributions, You represent that + You have received permission to make Contributions on behalf of that + employer, that Your employer has waived such rights for Your Contributions to + The Project, or that Your employer has executed a separate CLA with The + Project. If You are a legal entity, You represent further that each employee + of the Corporation designated on Schedule A below (or in a subsequent written + modification to that Schedule) is authorized to submit Contributions on + behalf of the Corporation. + +5. You represent that each of Your Contributions is Your original creation (see + section 7 for submissions on behalf of others). + +6. You are not expected to provide support for Your Contributions, except to the + extent You desire to provide support. You may provide support for free, for a + fee, or not at all. Unless required by applicable law or agreed to in + writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, + without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, + MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +7. Should You wish to submit work that is not Your original creation, You may + submit it to the Foundation separately from any Contribution, identifying the + complete details of its source and of any license or other restriction + (including, but not limited to, related patents, trademarks, and license + agreements) of which You are personally aware, and conspicuously marking the + work as "Submitted on behalf of a third-party: [named here]". + +8. You agree to notify The Project of any facts or circumstances of which You + become aware that would make these representations inaccurate in any respect. + If You are a legal entity, it is Your responsibility to notify The Project + when any change is required to the list of designated employees authorized to + submit Contributions on behalf of the Corporation, or to the Corporation's + Point of Contact with The Project. + +9. Should You wish to submit work that is not Your original creation, You may submit it to the Project separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as “Submitted on behalf of a third-party: [named here]”. + +10. You agree to notify the Project of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 346619325..148a10077 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,29 +1,34 @@ -How Do I Submit A Good Bug Report? ----------------------------------- +# How Do I Submit A Good Bug Report? Please, don't send direct emails to Stéphane Raimbault unless you want commercial support. -Take care to read the documentation at http://libmodbus.org/documentation/. +Take care to read the documentation at http://libmodbus.org/. -- *Be sure it's a bug before creating an issue*, in doubt, post a message on - https://groups.google.com/forum/#!forum/libmodbus or send an email to - libmodbus@googlegroups.com +- _Be sure it's a bug before creating an issue_, in doubt, post a message on + or send an email to + -- *Use a clear and decriptive title* for the issue to identify +- _Use a clear and descriptive title_ for the issue to identify -- *Which version of libmodbus are you using?* you can obtain this information -from your package manager or by running `pkg-config --modversion libmodbus`. -You can provide the sha1 of the commit if you have fetched the code with `git`. +- _Which version of libmodbus are you using?_ you can obtain this information + from your package manager or by running `pkg-config --modversion libmodbus`. + You can provide the sha1 of the commit if you have fetched the code with `git`. -- *Which operating system are you using?* +- _Which operating system are you using?_ -- *Describe the exact steps which reproduce the problem* in as many details as -possible. For example, the software/equipement which runs the Modbus server, how -the clients are connected (TCP, RTU, ASCII) and the source code you are using. +- _Describe the exact steps which reproduce the problem_ in as many details as + possible. For example, the software/equipment which runs the Modbus server, how + the clients are connected (TCP, RTU, ASCII) and the source code you are using. -- *Enable the debug mode*, libmodbus provides a function to display the content -of the Modbus messages and it's very convenient to analyze issues -(http://libmodbus.org/docs/latest/modbus_set_debug.html). +- _Enable the debug mode_, libmodbus provides a function to display the content + of the Modbus messages and it's very convenient to analyze issues + (http://libmodbus.org/docs/modbus_set_debug/). Good bug reports provide right and quick fixes! + +## Contributor License Agreement + +By submitting a contribution to libmodbus, you agree to the terms of the +[Contributor License Agreement](CLA.txt). Please read it before submitting a +pull request. diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index 06d145756..9d87f4ba0 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -1,40 +1,40 @@ Please read the following carefully before submitting this new issue. - - Please ensure, that you are really reporting a bug. When in doubt, - post a message on https://groups.google.com/forum/#!forum/libmodbus - or send an email to libmodbus@googlegroups.com +- Please ensure, that you are really reporting a bug. When in doubt, post a + message on or send an + email to libmodbus@googlegroups.com - - Please do not open issues to ask questions about using libmodbus. - Use the mailing list for this as there are many more people reading - that list, who could help you. +- Please do not open issues to ask questions about using libmodbus. Use the + mailing list for this as there are many more people reading that list, who + could help you. - - When using libmodbus from a distribution (Debian, Fedora...), please - report the bug first in the bug tracker of the distribution. The - reason for doing so is that the package maintainer should have a chance - to look at the issue first as it might be a packaging error. If/when - the package maintainer comes to the conclusion that is really an upstream - bug, then he/she will usually report it here by himself/herself. - This is because he/she is interested in staying in the notification chain - to decide about a backport as soon as a bugfix is available. - Otherwise you (distribution user) will be asked to do so explicitely. +- When using libmodbus from a distribution (Debian, Fedora...), please report + the bug first in the bug tracker of the distribution. The reason for doing so + is that the package maintainer should have a chance to look at the issue first + as it might be a packaging error. If/when the package maintainer comes to the + conclusion that is really an upstream bug, then he/she will usually report it + here by himself/herself. This is because he/she is interested in staying in + the notification chain to decide about a backport as soon as a bugfix is + available. Otherwise you (distribution user) will be asked to do so + explicitly. -When you get here and you are still convinced that you want report a bug: +When you get here and you are still convinced that you want to report a bug: - - *Use a clear and decriptive title* for the issue to identify +- *Use a clear and descriptive title* for the issue to identify - - *Which version of libmodbus are you using?* you can obtain this information +- *Which version of libmodbus are you using?* you can obtain this information from your package manager or by running `pkg-config --modversion libmodbus`. You can provide the sha1 of the commit if you have fetched the code with `git`. - - *Which operating system are you using?* +- *Which operating system are you using?* - - *Describe the exact steps which reproduce the problem* in as many details as - possible. For example, the software/equipement which runs the Modbus server, how +- *Describe the exact steps which reproduce the problem* in as many details as + possible. For example, the software/equipment which runs the Modbus server, how the clients are connected (TCP, RTU, ASCII) and the source code you are using. - - *Enable the debug mode*, libmodbus provides a function to display the content +- *Enable the debug mode*, libmodbus provides a function to display the content of the Modbus messages and it's very convenient to analyze issues - (http://libmodbus.org/docs/latest/modbus_set_debug.html). + (). Good bug reports provide right and quick fixes! @@ -62,11 +62,11 @@ everything up to and including the following line which starts with ---. <...> -## Expected behaviour +## Actual behavior if applicable <...> -## Actual behaviour +## Expected behavior or suggestion <...> diff --git a/MIGRATION b/MIGRATION deleted file mode 100644 index 095703c1c..000000000 --- a/MIGRATION +++ /dev/null @@ -1,53 +0,0 @@ -============================================= -Migration notes from the 2.0 series (for 3.0) -============================================= - -The 3.0 release use a brand new API and this document covers only the general -changes: - -- the structure modbus_param_t is gone and is replaced by a new opaque and -dynamically allocated structure modbus_t. - -- the slave argument is no more an argument of the Modbus functions, you need to - call modbus_set_slave first. - -- the public header file is smaller so some internal defines aren't accessible - anymore. - -- all function and constants are respectively prefixed by modbus_ or MODBUS_. - -- the POSIX error conventions are used (if an error occured, -1 or NULL is - returned and errno is set accordingly). - -- coil status and discretes inputs are just bits and force/preset actions have - been renamed to write actions. - -We hope you'll enjoy the new API to accept the migration burden! - -============================================= -Migration notes from the 1.2 series (for 2.0) -============================================= - -Init -==== - -modbus_init_tcp requires a third new argument, the port number. - -modbus_init_tcp(modbus_param_t *mb_param, char *ip_address, int port) - -Set the port to MODBUS_TCP_DEFAULT_PORT to use the default one -(502). It's convenient to use a port number greater than or equal to -1024 because it's not necessary to be root to use this port number. - - -Pointers of data -================ - -The coil and input status are now stored in an array of type uint8_t -(in 1.2.X series, array of type int was used). So now, you need to -pass a pointer of type uint8_t to use read_coil_status(), for example. - -The holding and input registers are now stored in an array of type -uint16_t. - -These changes reduce the memory consumption. diff --git a/Makefile.am b/Makefile.am index a0a165e18..a80e70a69 100644 --- a/Makefile.am +++ b/Makefile.am @@ -7,10 +7,28 @@ pkgconfig_DATA = libmodbus.pc EXTRA_DIST = libmodbus.pc.in CLEANFILES += libmodbus.pc -dist_doc_DATA = MIGRATION README.md +dist_doc_DATA = AUTHORS NEWS.md README.md -SUBDIRS = src doc +SUBDIRS = src if BUILD_TESTS SUBDIRS += tests endif + +# Code coverage (gcov/lcov) +# Usage: make CFLAGS="--coverage -O0" LDFLAGS="--coverage" && make coverage + +.PHONY: coverage + +coverage: + -find $(top_builddir) -name '*.gcda' -delete + $(MAKE) check + lcov --capture --directory $(top_builddir)/src --output-file coverage.info --ignore-errors source + lcov --remove coverage.info '/usr/*' --output-file coverage.info + genhtml coverage.info --output-directory coverage + +CLEANFILES += coverage.info +clean-local: + -rm -rf coverage + -find $(top_builddir) -name '*.gcda' -delete + -find $(top_builddir) -name '*.gcno' -delete diff --git a/NEWS b/NEWS deleted file mode 100644 index 9e68da778..000000000 --- a/NEWS +++ /dev/null @@ -1,558 +0,0 @@ -libmodbus 3.1.6 (2019-07-31) -============================ - -- Fix awful typo in fix for VD-1301 vulnerability. - Thank you @karlp. - -libmodbus 3.1.5 (2019-07-29) -============================ - -- Fix URL -- Add appveyor.com config -- Update libmodbus.txt -- Change arg value from int to const uint16_t in modbus_write_register -- Mark raw_req argument as const in modbus_send_raw_request (closes #479) -- Add unit tests for VD-1301 and VD-1302 vulnerabilities -- Fix VD-1301 and VD-1302 vulnerabilities -- Create CODE_OF_CONDUCT.md -- modbus_mapping_t is now a named typedef (to allow forward declaration) -- Rename type as flags for consistency between functions -- Add missing SOCK_CLOEXEC flag on socket creation -- Remove wrong and harmful checks in configure.ac -- Oops fix OR on RS485 settings (1c5d969) -- Only set SER_RS485_ENABLED bit of existing RS485 settings -- Add .vscode/ to .gitignore -- Fix wrong function name -- Remove a duplicated semi-colon -- Fix a small typo in test message -- Fix modbus_reply for TCP when unit id == 0 (fixes #376) -- New functions to define the indication timeout (#95) -- Remove CYGWIN condition to provide bswap16 fallback (#383) -- typo fix -- Use builtin bswap16 of gcc 4.8 (#377) -- Handle out-of-memory conditions more gracefully -- [doc] Add entry points for accept/listen in libmodbus index -- Fix typo -- Refine issue template for Github -- Move WINVER definition before other includes (#350) -- Add new function modbus_get_slave() -- Fix LIBMODBUS_VERSION_HEX encoding (closes #345) -- tcp_modbus_accept mustn't close socket on error (closes #333) - -libmodbus 3.1.4 (2016-05-28) -============================ - -- C_PROG_RANLIB is rendered obsolete by LT_INIT -- Improve ifdef around bswap_16 for __CYGWIN__ -- Improve Cygwin support. Thanks to StalderT. -- Another round of DRY in modbus_reply() -- Rename raw_rep to raw_rsp in unit-test-client -- Fix wrong function name in debug message -- Fix handling of invalid function code (closes #315) -- Add debug message on unknown function and new unit test -- DRY in modbus_reply by improving response_exception() -- Fix typo in 3053bd0adb -- CID 69145 - Argument cannot be negative in unit-test-server -- CID 69142 - Unchecked return value in unit-test-server -- Fix CID 69140 - Bad bit shift operation (coverity) in tests -- Rewrite and rename README as README.md in tests/ -- Github's contributing and issue template files -- Fix small leak (64 bytes in TCP) in unit-test-client -- Add unit-tests.sh to tarball -- Add ./configure.scan to .gitignore -- Move setting of option inside the relevant conditional group -- Add links to new modbus_*_float_* functions in index -- Slight change to modbus_report_slave_id doc. - -libmodbus 3.1.3 (2016-05-11) -============================ - -- New bswap macros for Max OSX by Jakob Bysewski. -- Fix "wildcard address" in TCP IPv6 by Shoichi Sakane. -- Introduce offsets in modbus mappings with modbus_mapping_new_start_address. - Thanks to Michael Heimpold and Stéphane Raimbault. -- Fix address range in random-test-client. - Thanks to Martin Galvan. -- Add an option to disable tests compilation by Yegor Yefremov. -- Define MSG_DONTWAIT to MSG_NONBLOCK on AIX (#294). - Thanks to Fabrice Cantos. -- Fix building when byteswap.h is not defined by Tomasz Mon. -- Add some more macros for data manipulation and documentation. -- Remove duplicate install of modbus.h (closes #290). - Thanks to Daniel Sutcliffe. -- Move MIGRATION and README.md to dist_doc_DATA target. -- Change order of few functions in modbus RTU code. -- Add entries for modbus_rtu_[get|set]_delay in documentation index. -- Implemented runtime configurable RTS delay by Jimmy Bergström. -- Add an entry in libmodbus index page for modbus_rtu_set_custom_rts. -- Add support for user defined RTS toggle function by Jimmy - Bergström. -- Added ILLEGAL_DATA_ADDRESS tests for modbus_write_register[|s]. - Thanks to Andrey Skvortsov. -- Update documentation of modbus_rtu_set_rts -- Fix rts signal switch time by Hiromasa Ihara. -- Improve new_rtu and set_slave documentation (related to #276). -- Fix late check of ctx in modbus_reply[|_exception] (closes #269). -- Wait the server for 1 second before running tests (help Travis). -- A libmodbus context isn't thread safe and won't be (closes #246). -- Fix buffer overflow in modbus_mask_write_register (#265). -- Minor adjustments to README about AsciiDoc. -- Export MODBUS_MAX_ADU_LENGTH and documentation (ref #241). -- Explicit check against Modbus broadcast address. -- Do not reply on broadcast requests (fixes #153). Thanks to Michael. -- Add Travis support. -- Run unit tests with standard: make check (closes #205, closes #238). - This patch has been developed by Andrey Skvortsov, Michael Heimpold - and Stéphane Raimbault. -- modbus_send_raw_request: limit request length (fixes #207). - Thanks to Hanno Neuer for spotting this security flaw. -- Add new contributors to AUTHORS -- Introduce SPDX license identifiers. Thanks to Michael Heimpold. - -libmodbus 3.1.2 (2015-02-13) -============================ - -If you still want to listen any addresses in your TCP IPv4 server, you must now -set the IP address to NULL in modbus_new_tcp before listening. Now, -modbus_tcp_listen only listen the IP address set in the Modbus context (see -documentation). - -This release introduces API changes on modbus_get_byte_timeout, -modbus_get_response_timeout, modbus_set_byte_timeout, -modbus_set_response_timeout to ease writing of language bindings. - -- Rewrite documentation building system -- Fix timeouts in unit tests -- Don't flush on illegal address errors in single write functions -- Fix compilation on compilers not supporting c99 mode. - Thanks to Michael Heimpold. -- Update license for the tests in Debian packaging (#221) -- Move check of device earlier to avoid a free call -- Unit test for baud rate check and error message. -- Fix crash modbus_new_rtu when baud is 0. - Thank you to Daniel Schürmann. -- Removed function prototype without implementation - Thank you Andrej Skvortzov. -- Switch test programs to a BSD license -- Fix remote buffer overflow vulnerability on write requests -- Avoid twice connect() in source code (closes #194) -- Fix compilation with MinGW (GCC 4.8.1) under Win7 (closes #163) - Thank you MarjanTomas and SwissKnife. -- Fix TCP IPv4 modbus_connect() on win32 (closes #100 and #165) - Thank you Petr Gladkiy and Marjan Tomas. -- Fix 24a05ebd3c0 - win32: init of modbus_tcp_pi_listen (#187) -- INADDR_* macros are defined in host byte order -- Filter of IP addresses in IPv4 server (closes #190) -- Allow to listen any hosts in IPv6 (closes #32) -- Define and public export of MODBUS_MAX_PDU_LENGTH (closes #167) -- Truncate data from response in report_slave_id to new max arg (closes #167) -- Fix response timeout modification on connect (closes #80) -- New API to set/get response and byte timeouts. - New unit tests and updated documentation. -- Export Modbus function codes supported by libmodbus -- Fix bandwidth-server-one (closes #152) -- Check debug flag in RTU code -- Remove warnings caused by shadowed 'index' variable. - Thanks to Åke Forslund. -- Use accept4 in TCP PI if available -- Add documentation for tcp[_pi]_accept (closes #31) -- Fix mistake in modbus_tcp_listen documentation -- Add documentation for modbus_tcp_pi_listen -- Fix for MinGW and Windows (#144, #169, #175, #180, #181, #187) - Thanks to Marjan Tomas. -- Many other fixes (#134, #157, #158, #183, #184) and improvements. - -libmodbus 3.1.1 (2013-10-06) -============================ - -More robust to exploits, more compliant and better handling of connect/close -sequences. Windows support still broken. - -- New unit tests -- Fix remote buffer overflow vulnerability (closes #25, #105) -- Explain how to define response timeouts when many RTU slaves -- Fix receiving of incorrect queries in write_single and mask_write_register - Thanks to James Nutaro. -- Check return value of autoreconf. - Thanks to Lauri Nurmi -- Constant for broacast and test ordering -- Fix the fix of device string check -- Various changes to try to improve *broken* Windows support -- Try to fix MinGW compilation -- Portable use of bswap_32 -- Improve support of MacOS X -- Fix socket value on init/close -- Returns -1 on invalid mode in modbus_rtu_set_rts -- Protect all public functions against invalid context -- Sleep for delay of response timeout before reconnect (closes #77). - Thanks to Karl Palsson. -- Baud rate until 4,000,000 (POSIX), 1,000,000 (Windows) (closes #93) -- New modbus_get|set_float_dcba to get|set float in inversed byte order -- Remove unsupported -Wtype-limits for GCC < 4.3.5 (closes #109) -- Enable out-of-source build. Thanks to Yegor Yefremov. -- Fix alignment problem on ARMv5 platform -- Improvement to Debian package. Thanks to Alexander Klauer. -- Improve support of VS 2005. Thanks to Petr Gladkiy. -- Add documentation for modbus_mask_write_register (closes #91). - Thanks to Martijn de Gouw. -- Avoid C99 declaration in win32 section code (closes #92). - Thanks to oldfaber and endrelovas. -- Add a windows scripting host configure file. - Thanks to oldfaber and Stéphane Raimbault. -- Fix typo in modbus_strerror documentation. Thanks to Mirko Rajkovaca. -- Rename reserved C++ keywords of modbus_mask_write_register. - Thanks Tobias Doerffel. -- Another quick workaround for deficient OS (closes #63) -- Add support for Mask Write Register -- Fix missing close on socket in random-test-server. - Thanks to Damian Zieliński. -- Use nonblocking sockets on Win32 and OS X/iOS too. - Thanks to Julian Raschke. -- Fix all compilations warnings spotted by new compilation flags -- Major update of build system -- Calculate RTS activation time by send length -- Dynamic memory allocation of device name (closes #11) -- Add unit tests for modbus_mapping_new -- Add Visual Studio 2008 project files by oldfaber -- Fix missing argument in synopsis section of modbus_rtu_set_serial_mode -- Fix wrong constant names to create version number -- More compilation fixes for Windows by oldfaber. -- Fix wrong constant names to create version number. - Thanks to Denis Davydov. - -libmodbus 3.1.0 (2012-06-22) -============================ - -Major changes to handle many slaves in RTU mode, non blocking connections and -RTS flow control. - -- Fixes for Microsoft Visual C++ compiler -- Fixes for Windows. Thanks to oldfaber -- Return value of _modbus_tcp_pi_connect() on failure (closes #61) -- Avoid ioctl call on non-RS485 ports. Thanks to Michael Haberler -- Display node and service in PI and port in IPv4 at connection -- Return -1 on getaddrinfo error and print error in debug mode -- More robust way to establish the connection in non blocking mode -- TCP - Socket in non blocking mode by default. Thanks to Thomas Stalder -- Apply CLOEXEC flag for TCP protocol independent too (IPv6) -- New RTU receive() to ignore confirmation from other slaves (closes #18) -- Move RTU filtering in CRC check to avoid useless call to modbus_reply -- Unique transaction identifier by TCP connection -- Use accept4 with SOCK_CLOEXEC when available (Linux) -- Open fd and socket with the CLOEXEC flag when available -- Exception response on report slave ID wasn't detected (closes #27) -- Provides a way to disable the byte timeout (Alex Stapleton) -- Added slave ID check for response messages (Alex Stapleton) -- RTS flow control with modbus_rtu_set_rts and modbus_rtu_get_rts - functions by Torello Querci and Stéphane Raimbault. - -libmodbus 3.0.4 (2012-05-08) -============================ - -- autogen.sh creates symlinks instead of copies -- Add missing m4 directory (closes #103) -- Fix alignment problem on ARMv5 platform - Thanks to Alexander Dahl -- Remove useless test on error_recovery argument - Thanks to Carlos Tangerino - -libmodbus 3.0.3 (2012-05-25) -============================ - -- Fix another Visual C++ 2008/2010 deficiency (closes #53) -- Add -lsocket to compile on QNX -- Fix TCP PI init under Windows. - Thanks to oldfaber. -- Fix a missing free in random-test-client - Thanks again to Stefan Finzel. -- Fix OMG bug in modbus_mapping_free not freeing memory. - Thanks to Stefan Finzel for the bug report. -- Fix semicolon typo and unistd.h include under Windows. - Thanks to Andrew Kravchuk. - -libmodbus 3.0.2 (2012-01-16) -============================ - -- Update Debian package -- Documentation fixes and additions -- Add missing C++ macros in public headers. - Thanks to Bernhard Agthe. -- Protects modbus_mapping_free against NULL argument. - Thanks to Andrea Mattia -- Fix check on file doc/libmodbus.7 in acinclude.m4 (closes #28) -- Close file descriptor when the settings don't apply in RTU. - Original patch provided by Thomas Stalder. -- unit-test.h is now generated to avoid config.h dependency. -- Request for Windows Sockets specification version 2.2 instead of 2.0 - Thanks to Pavel Mazniker for the report. - -libmodbus 3.0.1 (2011-07-18) -============================ - -- Avoid useless serial_mode integer when TIOCSRS485 isn't supported -- Fix compilation failure on Windows (RS485 support) by Tobias Doerffel - -- Properly check TIOCSRS485 define by Matthijs Kool -- Rename package to libmodbus5 to fix lintian warning - -libmodbus 3.0.0 (2011-07-11) -============================ - -- Revert libmodbus licence from LGPLv3 to LGPLv2.1 to avoid - incompatibility with GPLv2 program. This change has been approved - by Tobias Doerffel, Florian octo Forster and Hannu Vuolasaho. -- Enable RS485 support only when available -- Export modbus_set/get_serial_mode functions on all platforms -- API change for read/write multiple registers function: - * modbus_read_and_write_registers -> modbus_write_and_read_registers - The function name was confusing because the write operation is performed - before the read. Take care to swap the arguments in the migration process. -- Documentation of modbus_write_and_read_registers, modbus_mapping_new/free, - report_slave_id. modbus_get_byte_from_bits, modbus_set_bits_from_byte(s), - modbus_[gs]et_float, modbus_reply and modbus_reply_exception. -- Enhanced report slave ID -- New RPM spec file to package documentation and development files - -libmodbus 2.9.4 (2011-06-05) -============================ - -- IPv6 support - Make the TCP implementation "protocol independent" by Florian Forster - and Stéphane Raimbault. -- Fix compilation on Windows 7 (x64) with MinGW/MSYS and GCC 4.5 - Reported by Patsy Kaye. -- Documentation of libmodbus functions with AsciiDoc (man and HTML) by Stéphane - Raimbault -- Avoid an iteration in flush function -- New functions to send and receive raw requests (modbus_send_raw_request, - modbus_receive_confirmation) -- Fix flush function of TCP backend on Windows -- API changes for server/slave: - * modbus_receive doesn't take socket/fd argument anymore - * new function modbus_set_socket to set socket/fd -- API changes for timeout functions: - * modbus_get_timeout_begin -> modbus_get_response_timeout - * modbus_set_timeout_begin -> modbus_set_response_timeout - * modbus_get_timeout_end -> modbus_get_byte_timeout - * modbus_set_timeout_end -> modbus_set_byte_timeout -- Fix longstanding limitation of server to wait forever -- New functions modbus_set/get_serial_mode by Manfred Gruber and Stéphane - Raimbault for RS485 communications -- Improved recovery mode (see modbus_set_error_recovery documentation) for - data link and protocol errors. -- Fix compilation issue with Microsoft Visual Studio 2008. - Reported by Allan Cornet. - -libmodbus 2.9.3 (2011-01-14) -============================ - -- Major rewriting of the message reading (no more timeouts on exception) - by Stéphane Raimbault -- New function to reply to an indication with an exception message - modbus_reply_exception() -- New function modbus_get_header_length(modbus_t *ctx) -- New functions to manipulate data: - * MODBUS_GET_INT32_FROM_INT16 - * MODBUS_GET_INT16_FROM_INT8 - * MODBUS_SET_INT16_TO_INT8 -- Fix GH-2. Read/write were swapped in _FC_READ_AND_WRITE_REGISTERS -- Install an ignore handler for SIGPIPE on *BSD - Original patch by Jason Oster. -- Fix closing of Win32 socket. - Reported by Petr Parýzek. -- Fix unit identifier not copied by the TCP server. - Reported by Antti Manninen. -- Fix missing modbus_flush() in unit tests -- Fixes for OpenBSD by Barry Grumbine and Jason Oster - - -libmodbus 2.9.2 (2010-12-05) -============================ - -- Win32 support by Tobias Doerffel -- Split source code around RTU and TCP (backends) -- Rename modbus_[listen|accept] to modbus_tcp_[listen|accept] -- Remove slave argument from modbus_new_rtu() -- Check received function code -- Fix segfault in bandwidth-server-many-up on inet_ntoa() call -- Fix unit test of report slave ID in RTU -- Fix GH-3. Remove inclusion of config.h in modbus.h -- Correctly detect if we are cross-compiling for win32 by Kirill Smelkov. -- Fix setting of the broadcast address - - -libmodbus 2.9.1 (2010-08-16) -============================ - -- Brand new API (see MIGRATION notes)! -- Remove the internal function set_message_length_tcp -- Restore slave ID (server ID) argument in functions -- Error conventions of POSIX systems and error recover -- Parity setting is now a single char ('N', 'E' or 'O') -- Report slave ID server side -- OpenBSD support by Anibal Limón. -- New read and write registers function by Hannu Vuolasaho. -- Versioning infrastructure - Inspired by the Clutter project and the work done by Florian Forster. -- Fix the broadcast constant (255 -> 0) - Reported by David Olivari. -- Fix #463299 - New functions to define the timeouts of begin and end of trame - Original patch by Sisyph (eric-paul). -- Fix #591142 - Slave id check should be disabled in TCP connection - Reported by aladdinwu. - - -libmodbus 2.1.0 (2010-03-24) -============================ - -- New API to read and write float values by Stéphane Raimbault and Florian - Forster. -- New API for slave server (see MIGRATION) -- New slave server able to handle multiple connections -- Slave only replies to broadcast queries or queries with its slave ID -- Improved Modbus protocol conformance -- modbus_param_t is smaller (2 int removed) -- Better error management and SIGPIPE signal is blocked -- Faster -- Fix #333455 - Cygwin IPTOS_LOWDELAY not supported on cygwin - Reported by Jeff Laughlin and Yishin Li. -- Fix #375926 - modbus.c:164: error: `MSG_DONTWAIT' undeclared - Reported and tested by Yishin Li. -- Fix #378981 - CRC error on RTU response doesn't return negative value - Reported by Henrik Munktell. -- Fix report slave ID request - Patch (bzr) provided by Paul Fertser. -- Fix #425604 - Conditional jump or move depends on uninitialised value(s) - Occurs on first occurence of slave timeout. - Reported by Henrik Munktell. -- Fix #457200 - FreeBSD support - Patch provided by Norbert Koch. - -Other changes: - -- The code is now published and developed on - http://github.com/stephane/libmodbus -- Waf support has been removed - - -libmodbus 2.0.3 (2009-03-22) -============================ - -- Fix CRC error when a slave RTU send a response. - Thanks to Justin Carroll to have reported and tested my patch. -- Remove an assignment in compute_response_length() -- Remove duplicate counter in read_io_status() -- Fix #274511 reported by 'Kylesch' - Invalid error check in modbus_init_listen_tcp - - -libmodbus 2.0.2 (2008-08-10) -============================ -- Fix a bug reported by email by Davide Pippa - The function modbus_receive must check the number of values - indicated in the response accordingly to the query. -- Fix #241006 reported by Jesus Hernandez Tapia - modbus_check_response() crashes on an invalid exception code -- Reduce the number of function calls (read_reg_response and - preset_response have been removed) -- Add a new unit test for bad response -- Catch the timeout even if the length is equal to a exception trame -- Test only msg_length_computed on change -- Many comments and cleanups - - -libmodbus 2.0.1 (2008-07-02) -============================ - -- Include libmodbus.spec in the tarball -- Fix #241006 reported by Jesus Hernandez Tapia - modbus_check_response() crashes on an invalid exception code - - -libmodbus 2.0.0 (2008-05-18) -============================ - -- Slave API - https://blueprints.launchpad.net/libmodbus/+spec/slave-api -- No more glib dependency - https://blueprints.launchpad.net/libmodbus/+spec/glib-dependency -- Unit testing and many test programs -- Waf build support - https://blueprints.launchpad.net/libmodbus/+spec/waf-support -- MacOS X support by Matthew Butch - https://blueprints.launchpad.net/libmodbus/+spec/macosx-support -- Unit testing (unit-test-slave and unit-test-master) -- Port number is now defined at initialisation by Dirk Reusch -- Better memory management (uint8_t *data and packing of - modbus_param_t) -- Better error management -- Declare many static functions and const arrays -- Enhance an integer division -- The GNU licences LGPL and GPL are in version 3 -- Debian and RPM packages (#224496) -- Many cleanups -- Fix #159443 reported by Stefan Bisanz - Index of incoming data in force multiple coils function -- Fix #161989 reported by Konstantinos Togias - Serial device paths more than 10 chars long (eg. /dev/ttyUSB0) don't - fit to modbus_param_t -> device char[11] var. -- Fix #188189 reported by Chris Hellyar - Compute_response_size() no entry for read_input_status() -- Fix #191039 reported by Todd Denniston - modbus.h is not installed at prefix. -- Fix #211460 reported by Todd Denniston - With TCP, automatic reconnect on error may not be desired. -- Fix #224485 reported by Todd Denniston - libmodbus does not link with c++ code. -- Fix #224496 reported by Todd Denniston - It is easier to install on rpm based systems with a spec file. - - -libmodbus 1.2.5 (2008-05-18) -============================ - -- Fix #224485 reported by Todd Denniston - libmodbus does not link with c++ code. - - -libmodbus 1.2.4 (2008-03-14) -============================ - -- Fix #191039 reported by Todd Denniston - modbus.h is not installed at prefix. - - -libmodbus 1.2.3 (2008-02-03) -============================ - -- Fix #188189 reported by Chris Hellyar - Compute_response_size() no entry for read_input_status() -- Fix #181887 reported by Jesus Hernandez Tapia. - Slave address in build_request_packet_tcp() is hardcoded as 0xFF. - - -libmodbus 1.2.2 (2007-11-12) -============================ - -- Fix #161989 reported by Konstantinos Togias - Serial device paths more than 10 chars long (eg. /dev/ttyUSB0) don't - fit to modbus_param_t -> device char[11] var. -- Structure is also bit better 'packed' to conserve memory (see the - trunk for a real enhancement). - - -libmodbus 1.2.1 (2007-11-02) -============================ - -- Fix #159443 reported by Stefan Bisanz - Index of incoming data in force multiple coils function -- Deleted useless code in check_crc16() -- Untabify source code -- Changed author's email to Stéphane Raimbault - - -libmodbus 1.2.0 (2007-05-10) -============================ - -- FIX Compilation GCC-4.0 -- Project name in autogen.sh diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 000000000..04ef07e8a --- /dev/null +++ b/NEWS.md @@ -0,0 +1,684 @@ +# NEWS + +## libmodbus 3.2.0 (2026-07-02) + +- Add `modbus_proxy()` to bridge two Modbus backends (closes #70). +- Add termios2 support to allow custom baud rates (RTU). +- Return an exception response for READ EXCEPTION STATUS (0x07). +- Use `EMBXILVAL` instead of `EMBMDATA` for illegal data value errors. +- `modbus_reply`: reject NULL `req` and `mb_mapping` arguments. +- `modbus_reply`: validate request length before reading per-function fields. +- Fix signed integer overflow in `modbus_get_float_*()` (reported by Dominik + Blain and Henrik Brodin). +- Fix signed integer overflow in the `MODBUS_GET_INT*_FROM_INT*` macros; + `MODBUS_GET_INT16_FROM_INT8()` now returns a signed `int16_t` in every context + (reported by Henrik Brodin - Trail of Bits). +- Fix strict aliasing violation in `modbus_set_float_*()` by using `memcpy` + (reported by Henrik Brodin). +- Remove dead code (Coverity CID 561619). +- modbus-rtu: don't use `O_EXCL` when opening the device (#760). +- WIN32 compatibility for `setsockopt()` in modbus-tcp.c (#800). +- Check if `ai_list` is null before freeing it (#831). +- Respect user-provided CFLAGS (closes #836). +- Fix test to use `ctx` instead of `invalid_ctx` (closes #791, #792). +- Update documentation of float functions (closes #838). +- Fix documentation examples of `modbus_get_float_*` functions. +- `modbus_reply_exception`: reject a NULL request pointer. +- `modbus_mask_write_register`: validate that `ctx` is not NULL. +- `modbus_write_and_read_registers`: validate `src`/`dest` pointers and counts. +- `modbus_report_slave_id`: reject a NULL `dest` buffer. +- `modbus_get_*_timeout`: validate that the output pointers are not NULL. +- `modbus_mapping_new_start_address`: cap table dimensions. +- `modbus_mapping_new`: reject negative counts. +- `modbus_rtu_set_custom_rts`: reject a NULL callback. +- modbus-rtu: avoid integer overflow in the RTS post-send delay. +- modbus-rtu: check the return values of the RTS ioctl calls. +- modbus-rtu: check the return value when saving termios settings on connect. +- modbus-rtu: report a failure to restore termios settings on close. +- `modbus_new_rtu`: reject negative baud rates. +- modbus-tcp: guard `freeaddrinfo()` against NULL on resolver failure. +- `modbus_new_tcp`: NUL-terminate the IP string when `ip` is NULL. +- `modbus_reply`: validate FC22 request length before writing the register. +- modbus-tcp: keep `ctx->s` consistent across failed or repeated connects (no + false success on a stale descriptor, no socket leak on reconnect). +- modbus-tcp: don't override the user error recovery mode in the flush overflow + path. +- modbus-tcp: fully initialize the sigaction struct for the SIGPIPE handler on + BSD. +- modbus-rtu: fix the Windows serial handle lifecycle (initialize the handle, + guard close against a never-opened handle and reset it after close). +- modbus-rtu: sleep the RTS turnaround delay in chunks to stay portable with + `usleep()`. +- `modbus_new_rtu`: validate `data_bit` and `stop_bit` arguments. +- `modbus_send_raw_request_tid`: reject NULL `raw_req` with `EINVAL`. +- `modbus_get_byte_from_bits`: remove an always-false assert and clamp `nb_bits` + consistently in debug and release builds. +- Preserve errno across the Windows error-recovery paths. +- tests: don't kill the unit test server on RTU bad-CRC frames. +- tests: fix integer overflow in the bandwidth-client rate computation. + +## libmodbus 3.1.12 (2026-02-13) + +- Fix FD_SET overflow when socket fd >= FD_SETSIZE. +- Check dest pointer not null and nb in read functions. +- NULL check for src and nb < 1 validation in write functions. +- `modbus_reply`: don't compute address for FC 0x07/0x11. +- Use O_NONBLOCK instead of deprecated O_NDELAY (closes #710). +- Explicit cast for Coverity CID 416366. +- Document required buffer size of `modbus_receive`. +- Document macros for error codes corresponding to Modbus exceptions (#758). +- Fix example of `modbus_rtu_set_serial_mode` (closes #552). +- Test filesystem provides symlink in autogen.sh (closes #414). +- Sync API signatures with the documentation. +- Many documentation fixes and typo corrections. +- Add coverage target and helper script. + +## libmodbus 3.1.11 (2024-10-22) + +- RTU - Check CRC before filtering on slave ID +- HAVE_NETINET_IN_H as guard around header (closes #765) +- Use default port 502 in documentation. +- Fix float endianness issue introduced in v3.1.8 (49af73d). Thank you @ghorwin + for your excellent work on the subject. Closes #665, #694, #757, #770. +- Proper display of used CFLAGS. +- Don't build with debug flag by default anymore. +- Check request length in `modbus_reply` when used in `memcpy`. Thank you Nozomi + Networks Labs Advisory for the report. +- Fix insecure data handling. CID 416366: INTEGER_OVERFLOW found with Coverity + Scan. +- Remove useless cast in setsockopt call (closes #721) +- Link against socket and nsl libs when building on SunOS. Thank you @lanurmi. +- Update documentation about tcp-pi requiring 1 KiB of extra memory (closes + #715). Thank you @psychon. +- Documentation improvements by @cedricboudinet and @mhei. +- Revert TCP checks for recovery (closes #711). +- Don't use loop initial declaration (closes #752). +- Fix wrong generated version for double number in win32 (closes #675). +- Don't use gai_strerror if not available. +- Add checks for netinet/ip.h and gai_strerror (#745) +- Log error in read input registers if debug (closes #755). +- Fix errno value on timeout with TCP connect (closes #736, #756). Thank you + @kyllingstad for the issue and @psychon for the fix. +- Free addrinfo struct on getaddrinfo() gerrors. +- Fix doc of modbus_mapping_new_start_address (#615). + +## libmodbus 3.1.10 (2022-12-07) + +Urgent release to fix a regression. + +- Fix baud rate handling with RTU under Linux +- Fix modbus_disable_quirks signature + +## libmodbus 3.1.9 (2022-12-03) + +- Remove Travis CI +- Move content about migration to libmodbus.org +- Avoid negative value in FD_SET call +- Test socket against positive value instead of -1 +- Improvements to autotools by @ndim +- Fix doc links by @jordanjohnson56 +- Test the protocol id for 0 by @metapsychologe +- Fix double negative in tests +- New quirks handler (closes #38 #533) +- Fix bitwise OR in documentation +- Improve doc about slave ID in RTU +- Add .clang-format and format code (closes #394) +- Remove constraints on baud rate values +- Accept IP or device in arg of unit test progs +- Avoid compilation issue with VS2022 with strdup +- Display created files in configure.js +- Use strcpy_s under Windows +- Replace inet_addr by inet_pton calls +- Replace inet_ntoa by inet_ptop +- Update configure.ac/config.h.win32 for new inet functions +- Instructions to build libmodbus in a VS project +- Fix connection check for Windows RTU (closes #660, #662) +- Add CIFuzz Github action +- Convert a few int to unsigned int (#402) + +## libmodbus 3.1.8 (2022-08-17) + +- Major rewrite of documentation with Material for mkdocs +- Reduce memory use of TCP PI backend (closes #621) +- Fixed MODBUS_ERROR_RECOVERY_LINK not working on Windows (@embeddedmz) +- Replace Travis CI by GitHub CI +- Fix linker error for Windows (VCLinkerTool) +- Address check in single register / coil responses added (#463) +- Swap CRC bytes in request data but not at CRC computing (#397) +- Fix float endianness issue on big endian architecture +- Fix comment about EMBUNKEXC (closes #566) +- Fix network library detection on Haiku +- Fix typos (closes #620) + +## libmodbus 3.1.7 (2022-01-09) + +- modbus_reply: fix copy & paste error in sanity check (fixes #614) +- Add SECURITY.md (closes #613) +- Fix typo in comment (closes #588) +- Replace obsolete AC_PROG_CC_STDC by AC_PROG_CC +- Fix position of CC flags in documentation (closes #599) +- Remove duplicate ';' (closes #602) +- Add the baud rate of 256k for Windows (closes #603) +- cosmetic changes in man page standardizing itemization +- Fix many typos +- Replace .dir-locals.el (Emacs) by .editorconfig +- Include the test LICENSE in tarball +- Install the NEWS and AUTHORS files +- Update README.md +- docs: fix simple typo, reponse -> response +- Add modbus\_[get|set]\_indication_timeout to doc build +- Fix warning issues +- Move malloc before starting unit tests +- Fixed MODBUS*GET*\* macros in case of negative values +- SPDX: change LGPL-2.1+ to LGPL-2.1-or-later + +Thank you to @yegorich, @i-ky, @jobol, @timgates42, @anton-bondarev, +@richardash1981, @stefannilsson, @Zugschlus, @qqq89513 and @mhei. + +## libmodbus 3.1.6 (2019-07-31) + +- Fix awful typo in fix for VD-1301 vulnerability. Thank you @karlp. + +## libmodbus 3.1.5 (2019-07-29) + +- Fix URL +- Add appveyor.com config +- Update libmodbus.txt +- Change arg value from int to const uint16_t in modbus_write_register +- Mark raw_req argument as const in modbus_send_raw_request (closes #479) +- Add unit tests for VD-1301 and VD-1302 vulnerabilities +- Fix VD-1301 and VD-1302 vulnerabilities +- Create CODE_OF_CONDUCT.md +- modbus_mapping_t is now a named typedef (to allow forward declaration) +- Rename type as flags for consistency between functions +- Add missing SOCK_CLOEXEC flag on socket creation +- Remove wrong and harmful checks in configure.ac +- Oops fix OR on RS485 settings (1c5d969) +- Only set SER_RS485_ENABLED bit of existing RS485 settings +- Add .vscode/ to .gitignore +- Fix wrong function name +- Remove a duplicated semi-colon +- Fix a small typo in test message +- Fix modbus_reply for TCP when unit id == 0 (fixes #376) +- New functions to define the indication timeout (#95) +- Remove CYGWIN condition to provide bswap16 fallback (#383) +- typo fix +- Use builtin bswap16 of gcc 4.8 (#377) +- Handle out-of-memory conditions more gracefully +- [doc] Add entry points for accept/listen in libmodbus index +- Fix typo +- Refine issue template for Github +- Move WINVER definition before other includes (#350) +- Add new function modbus_get_slave() +- Fix LIBMODBUS_VERSION_HEX encoding (closes #345) +- tcp_modbus_accept mustn't close socket on error (closes #333) + +## libmodbus 3.1.4 (2016-05-28) + +- C_PROG_RANLIB is rendered obsolete by LT_INIT +- Improve ifdef around bswap_16 for **CYGWIN** +- Improve Cygwin support. Thanks to StalderT. +- Another round of DRY in modbus_reply() +- Rename raw_rep to raw_rsp in unit-test-client +- Fix wrong function name in debug message +- Fix handling of invalid function code (closes #315) +- Add debug message on unknown function and new unit test +- DRY in modbus_reply by improving response_exception() +- Fix typo in 3053bd0adb +- CID 69145 - Argument cannot be negative in unit-test-server +- CID 69142 - Unchecked return value in unit-test-server +- Fix CID 69140 - Bad bit shift operation (coverity) in tests +- Rewrite and rename README as README.md in tests/ +- Github's contributing and issue template files +- Fix small leak (64 bytes in TCP) in unit-test-client +- Add unit-tests.sh to tarball +- Add ./configure.scan to .gitignore +- Move setting of option inside the relevant conditional group +- Add links to new modbus*\*\_float*\* functions in index +- Slight change to modbus_report_slave_id doc. + +## libmodbus 3.1.3 (2016-05-11) + +- New bswap macros for Max OSX by Jakob Bysewski. +- Fix "wildcard address" in TCP IPv6 by Shoichi Sakane. +- Introduce offsets in modbus mappings with modbus_mapping_new_start_address. + Thanks to Michael Heimpold and Stéphane Raimbault. +- Fix address range in random-test-client. Thanks to Martin Galvan. +- Add an option to disable tests compilation by Yegor Yefremov. +- Define MSG_DONTWAIT to MSG_NONBLOCK on AIX (#294). Thanks to Fabrice Cantos. +- Fix building when byteswap.h is not defined by Tomasz Mon. +- Add some more macros for data manipulation and documentation. +- Remove duplicate install of modbus.h (closes #290). Thanks to Daniel + Sutcliffe. +- Move MIGRATION and README.md to dist_doc_DATA target. +- Change order of few functions in modbus RTU code. +- Add entries for modbus*rtu*[get|set]\_delay in documentation index. +- Implemented runtime configurable RTS delay by Jimmy Bergström. +- Add an entry in libmodbus index page for modbus_rtu_set_custom_rts. +- Add support for user defined RTS toggle function by Jimmy Bergström. +- Added ILLEGAL_DATA_ADDRESS tests for modbus_write_register[|s]. Thanks to + Andrey Skvortsov. +- Update documentation of modbus_rtu_set_rts +- Fix rts signal switch time by Hiromasa Ihara. +- Improve new_rtu and set_slave documentation (related to #276). +- Fix late check of ctx in modbus_reply[|_exception] (closes #269). +- Wait the server for 1 second before running tests (help Travis). +- A libmodbus context isn't thread safe and won't be (closes #246). +- Fix buffer overflow in modbus_mask_write_register (#265). +- Minor adjustments to README about AsciiDoc. +- Export MODBUS_MAX_ADU_LENGTH and documentation (ref #241). +- Explicit check against Modbus broadcast address. +- Do not reply on broadcast requests (fixes #153). Thanks to Michael. +- Add Travis support. +- Run unit tests with standard: make check (closes #205, closes #238). This + patch has been developed by Andrey Skvortsov, Michael Heimpold and Stéphane + Raimbault. +- modbus_send_raw_request: limit request length (fixes #207). Thanks to Hanno + Neuer for spotting this security flaw. +- Add new contributors to AUTHORS +- Introduce SPDX license identifiers. Thanks to Michael Heimpold. + +## libmodbus 3.1.2 (2015-02-13) + +If you still want to listen any addresses in your TCP IPv4 server, you must now +set the IP address to NULL in modbus_new_tcp before listening. Now, +modbus_tcp_listen only listen the IP address set in the Modbus context (see +documentation). + +This release introduces API changes on modbus_get_byte_timeout, +modbus_get_response_timeout, modbus_set_byte_timeout, +modbus_set_response_timeout to ease writing of language bindings. + +- Rewrite documentation building system +- Fix timeouts in unit tests +- Don't flush on illegal address errors in single write functions +- Fix compilation on compilers not supporting c99 mode. Thanks to Michael + Heimpold. +- Update license for the tests in Debian packaging (#221) +- Move check of device earlier to avoid a free call +- Unit test for baud rate check and error message. +- Fix crash modbus_new_rtu when baud is 0. Thank you to Daniel Schürmann. +- Removed function prototype without implementation Thank you Andrej Skvortzov. +- Switch test programs to a BSD license +- Fix remote buffer overflow vulnerability on write requests +- Avoid twice connect() in source code (closes #194) +- Fix compilation with MinGW (GCC 4.8.1) under Win7 (closes #163). Thank you + MarjanTomas and SwissKnife. +- Fix TCP IPv4 modbus_connect() on win32 (closes #100 and #165). Thank you Petr + Gladkiy and Marjan Tomas. +- Fix 24a05ebd3c0 - win32: init of modbus_tcp_pi_listen (#187) +- INADDR\_\* macros are defined in host byte order +- Filter of IP addresses in IPv4 server (closes #190) +- Allow to listen any hosts in IPv6 (closes #32) +- Define and public export of MODBUS_MAX_PDU_LENGTH (closes #167) +- Truncate data from response in report_slave_id to new max arg (closes #167) +- Fix response timeout modification on connect (closes #80) +- New API to set/get response and byte timeouts. New unit tests and updated + documentation. +- Export Modbus function codes supported by libmodbus +- Fix bandwidth-server-one (closes #152) +- Check debug flag in RTU code +- Remove warnings caused by shadowed 'index' variable. Thanks to Åke Forslund. +- Use accept4 in TCP PI if available +- Add documentation for tcp[_pi]\_accept (closes #31) +- Fix mistake in modbus_tcp_listen documentation +- Add documentation for modbus_tcp_pi_listen +- Fix for MinGW and Windows (#144, #169, #175, #180, #181, #187). Thanks to + Marjan Tomas. +- Many other fixes (#134, #157, #158, #183, #184) and improvements. + +## libmodbus 3.1.1 (2013-10-06) + +More robust to exploits, more compliant and better handling of connect/close +sequences. Windows support still broken. + +- New unit tests +- Fix remote buffer overflow vulnerability (closes #25, #105) +- Explain how to define response timeouts when many RTU slaves +- Fix receiving of incorrect queries in write_single and mask_write_register + Thanks to James Nutaro. +- Check return value of autoreconf. Thanks to Lauri Nurmi +- Constant for broacast and test ordering +- Fix the fix of device string check +- Various changes to try to improve _broken_ Windows support +- Try to fix MinGW compilation +- Portable use of bswap_32 +- Improve support of MacOS X +- Fix socket value on init/close +- Returns -1 on invalid mode in modbus_rtu_set_rts +- Protect all public functions against invalid context +- Sleep for delay of response timeout before reconnect (closes #77). Thanks to + Karl Palsson. +- Baud rate until 4,000,000 (POSIX), 1,000,000 (Windows) (closes #93) +- New modbus_get|set_float_dcba to get|set float in inversed byte order +- Remove unsupported -Wtype-limits for GCC < 4.3.5 (closes #109) +- Enable out-of-source build. Thanks to Yegor Yefremov. +- Fix alignment problem on ARMv5 platform +- Improvement to Debian package. Thanks to Alexander Klauer. +- Improve support of VS 2005. Thanks to Petr Gladkiy. +- Add documentation for modbus_mask_write_register (closes #91). Thanks to + Martijn de Gouw. +- Avoid C99 declaration in win32 section code (closes #92). Thanks to oldfaber + and endrelovas. +- Add a windows scripting host configure file. Thanks to oldfaber and Stéphane + Raimbault. +- Fix typo in modbus_strerror documentation. Thanks to Mirko Rajkovaca. +- Rename reserved C++ keywords of modbus_mask_write_register. Thanks Tobias + Doerffel. +- Another quick workaround for deficient OS (closes #63) +- Add support for Mask Write Register +- Fix missing close on socket in random-test-server. Thanks to Damian Zieliński. +- Use nonblocking sockets on Win32 and OS X/iOS too. Thanks to Julian Raschke. +- Fix all compilations warnings spotted by new compilation flags +- Major update of build system +- Calculate RTS activation time by send length +- Dynamic memory allocation of device name (closes #11) +- Add unit tests for modbus_mapping_new +- Add Visual Studio 2008 project files by oldfaber +- Fix missing argument in synopsis section of modbus_rtu_set_serial_mode +- Fix wrong constant names to create version number +- More compilation fixes for Windows by oldfaber. +- Fix wrong constant names to create version number. Thanks to Denis Davydov. + +## libmodbus 3.1.0 (2012-06-22) + +Major changes to handle many slaves in RTU mode, non blocking connections and +RTS flow control. + +- Fixes for Microsoft Visual C++ compiler +- Fixes for Windows. Thanks to oldfaber +- Return value of \_modbus_tcp_pi_connect() on failure (closes #61) +- Avoid ioctl call on non-RS485 ports. Thanks to Michael Haberler +- Display node and service in PI and port in IPv4 at connection +- Return -1 on getaddrinfo error and print error in debug mode +- More robust way to establish the connection in non blocking mode +- TCP - Socket in non blocking mode by default. Thanks to Thomas Stalder +- Apply CLOEXEC flag for TCP protocol independent too (IPv6) +- New RTU receive() to ignore confirmation from other slaves (closes #18) +- Move RTU filtering in CRC check to avoid useless call to modbus_reply +- Unique transaction identifier by TCP connection +- Use accept4 with SOCK_CLOEXEC when available (Linux) +- Open fd and socket with the CLOEXEC flag when available +- Exception response on report slave ID wasn't detected (closes #27) +- Provides a way to disable the byte timeout (Alex Stapleton) +- Added slave ID check for response messages (Alex Stapleton) +- RTS flow control with modbus_rtu_set_rts and modbus_rtu_get_rts functions by + Torello Querci and Stéphane Raimbault. + +## libmodbus 3.0.8 (2019-07-31) + +- Fix awful typo in fix for VD-1301 vulnerability. Thank you @karlp. + +## libmodbus 3.0.7 (2019-07-29) + +- Backport fixes for VD-1301 and VD-1302 vulnerabilities +- Move WINVER definition before other includes (closes #350) +- Replace signed int by unsigned + +## libmodbus 3.0.6 (2014-02-21) + +- Backport fix remote buffer overflow vulnerability on write requests +- Replace deprecated INCLUDES by AM_CPPFLAGS + +## libmodbus 3.0.5 (2013-10-06) + +- Fix remote buffer overflow vulnerability +- Fix receiving of incorrect queries in write_single + +## libmodbus 3.0.4 (2012-05-08) + +- autogen.sh creates symlinks instead of copies +- Add missing m4 directory (closes #103) +- Fix alignment problem on ARMv5 platform Thanks to Alexander Dahl +- Remove useless test on error_recovery argument Thanks to Carlos Tangerino + +## libmodbus 3.0.3 (2012-05-25) + +- Fix another Visual C++ 2008/2010 deficiency (closes #53) +- Add -lsocket to compile on QNX +- Fix TCP PI init under Windows. Thanks to oldfaber. +- Fix a missing free in random-test-client Thanks again to Stefan Finzel. +- Fix OMG bug in modbus_mapping_free not freeing memory. Thanks to Stefan Finzel + for the bug report. +- Fix semicolon typo and unistd.h include under Windows. Thanks to Andrew + Kravchuk. + +## libmodbus 3.0.2 (2012-01-16) + +- Update Debian package +- Documentation fixes and additions +- Add missing C++ macros in public headers. Thanks to Bernhard Agthe. +- Protects modbus_mapping_free against NULL argument. Thanks to Andrea Mattia +- Fix check on file doc/libmodbus.7 in acinclude.m4 (closes #28) +- Close file descriptor when the settings don't apply in RTU. Original patch + provided by Thomas Stalder. +- unit-test.h is now generated to avoid config.h dependency. +- Request for Windows Sockets specification version 2.2 instead of 2.0 Thanks to + Pavel Mazniker for the report. + +## libmodbus 3.0.1 (2011-07-18) + +- Avoid useless serial_mode integer when TIOCSRS485 isn't supported +- Fix compilation failure on Windows (RS485 support) by Tobias Doerffel + +- Properly check TIOCSRS485 define by Matthijs Kool +- Rename package to libmodbus5 to fix lintian warning + +## libmodbus 3.0.0 (2011-07-11) + +- Revert libmodbus licence from LGPLv3 to LGPLv2.1 to avoid incompatibility with + GPLv2 program. This change has been approved by Tobias Doerffel, Florian octo + Forster and Hannu Vuolasaho. +- Enable RS485 support only when available +- Export modbus_set/get_serial_mode functions on all platforms +- API change for read/write multiple registers function: + - modbus_read_and_write_registers -> modbus_write_and_read_registers The + function name was confusing because the write operation is performed + before the read. Take care to swap the arguments in the migration process. +- Documentation of modbus*write_and_read_registers, modbus_mapping_new/free, + report_slave_id. modbus_get_byte_from_bits, modbus_set_bits_from_byte(s), + modbus*[gs]et_float, modbus_reply and modbus_reply_exception. +- Enhanced report slave ID +- New RPM spec file to package documentation and development files + +## libmodbus 2.9.4 (2011-06-05) + +- IPv6 support Make the TCP implementation "protocol independent" by Florian + Forster and Stéphane Raimbault. +- Fix compilation on Windows 7 (x64) with MinGW/MSYS and GCC 4.5 Reported by + Patsy Kaye. +- Documentation of libmodbus functions with AsciiDoc (man and HTML) by Stéphane + Raimbault +- Avoid an iteration in flush function +- New functions to send and receive raw requests (modbus_send_raw_request, + modbus_receive_confirmation) +- Fix flush function of TCP backend on Windows +- API changes for server/slave: + - modbus_receive doesn't take socket/fd argument anymore + - new function modbus_set_socket to set socket/fd +- API changes for timeout functions: + - modbus_get_timeout_begin -> modbus_get_response_timeout + - modbus_set_timeout_begin -> modbus_set_response_timeout + - modbus_get_timeout_end -> modbus_get_byte_timeout + - modbus_set_timeout_end -> modbus_set_byte_timeout +- Fix longstanding limitation of server to wait forever +- New functions modbus_set/get_serial_mode by Manfred Gruber and Stéphane + Raimbault for RS485 communications +- Improved recovery mode (see modbus_set_error_recovery documentation) for data + link and protocol errors. +- Fix compilation issue with Microsoft Visual Studio 2008. Reported by Allan + Cornet. + +## libmodbus 2.9.3 (2011-01-14) + +- Major rewriting of the message reading (no more timeouts on exception) by + Stéphane Raimbault +- New function to reply to an indication with an exception message + modbus_reply_exception() +- New function modbus_get_header_length(modbus_t \*ctx) +- New functions to manipulate data: + - MODBUS_GET_INT32_FROM_INT16 + - MODBUS_GET_INT16_FROM_INT8 + - MODBUS_SET_INT16_TO_INT8 +- Fix GH-2. Read/write were swapped in \_FC_READ_AND_WRITE_REGISTERS +- Install an ignore handler for SIGPIPE on \*BSD Original patch by Jason Oster. +- Fix closing of Win32 socket. Reported by Petr Parýzek. +- Fix unit identifier not copied by the TCP server. Reported by Antti Manninen. +- Fix missing modbus_flush() in unit tests +- Fixes for OpenBSD by Barry Grumbine and Jason Oster + +## libmodbus 2.9.2 (2010-12-05) + +- Win32 support by Tobias Doerffel +- Split source code around RTU and TCP (backends) +- Rename modbus*[listen|accept] to modbus_tcp*[listen|accept] +- Remove slave argument from modbus_new_rtu() +- Check received function code +- Fix segfault in bandwidth-server-many-up on inet_ntoa() call +- Fix unit test of report slave ID in RTU +- Fix GH-3. Remove inclusion of config.h in modbus.h +- Correctly detect if we are cross-compiling for win32 by Kirill Smelkov. +- Fix setting of the broadcast address + +## libmodbus 2.9.1 (2010-08-16) + +- Brand new API (see MIGRATION notes)! +- Remove the internal function set_message_length_tcp +- Restore slave ID (server ID) argument in functions +- Error conventions of POSIX systems and error recover +- Parity setting is now a single char ('N', 'E' or 'O') +- Report slave ID server side +- OpenBSD support by Anibal Limón. +- New read and write registers function by Hannu Vuolasaho. +- Versioning infrastructure Inspired by the Clutter project and the work done by + Florian Forster. +- Fix the broadcast constant (255 -> 0). Reported by David Olivari. +- Fix #463299 - New functions to define the timeouts of begin and end of trame + Original patch by Sisyph (eric-paul). +- Fix #591142 - Slave id check should be disabled in TCP connection Reported by + aladdinwu. + +## libmodbus 2.1.0 (2010-03-24) + +- New API to read and write float values by Stéphane Raimbault and Florian + Forster. +- New API for slave server (see MIGRATION) +- New slave server able to handle multiple connections +- Slave only replies to broadcast queries or queries with its slave ID +- Improved Modbus protocol conformance +- modbus_param_t is smaller (2 int removed) +- Better error management and SIGPIPE signal is blocked +- Faster +- Fix #333455 - Cygwin IPTOS_LOWDELAY not supported on cygwin. Reported by Jeff + Laughlin and Yishin Li. +- Fix #375926 - modbus.c:164: error: `MSG_DONTWAIT' undeclared. Reported and + tested by Yishin Li. +- Fix #378981 - CRC error on RTU response doesn't return negative value. + Reported by Henrik Munktell. +- Fix report slave ID request Patch (bzr) provided by Paul Fertser. +- Fix #425604 - Conditional jump or move depends on uninitialised value(s) + Occurs on first occurrence of slave timeout. Reported by Henrik Munktell. +- Fix #457200 - FreeBSD support Patch provided by Norbert Koch. + +Other changes: + +- The code is now published and developed on + +- Waf support has been removed + +## libmodbus 2.0.3 (2009-03-22) + +- Fix CRC error when a slave RTU send a response. Thanks to Justin Carroll to + have reported and tested my patch. +- Remove an assignment in compute_response_length() +- Remove duplicate counter in read_io_status() +- Fix #274511 reported by 'Kylesch' Invalid error check in + modbus_init_listen_tcp + +## libmodbus 2.0.2 (2008-08-10) + +- Fix a bug reported by email by Davide Pippa The function modbus_receive must + check the number of values indicated in the response accordingly to the query. +- Fix #241006 reported by Jesus Hernandez Tapia modbus_check_response() crashes + on an invalid exception code +- Reduce the number of function calls (read_reg_response and preset_response + have been removed) +- Add a new unit test for bad response +- Catch the timeout even if the length is equal to a exception trame +- Test only msg_length_computed on change +- Many comments and cleanups + +## libmodbus 2.0.1 (2008-07-02) + +- Include libmodbus.spec in the tarball +- Fix #241006 reported by Jesus Hernandez Tapia modbus_check_response() crashes + on an invalid exception code + +## libmodbus 2.0.0 (2008-05-18) + +- Slave API +- No more glib dependency + +- Unit testing and many test programs +- Waf build support + +- MacOS X support by Matthew Butch + +- Unit testing (unit-test-slave and unit-test-master) +- Port number is now defined at initialisation by Dirk Reusch +- Better memory management (uint8_t \*data and packing of modbus_param_t) +- Better error management +- Declare many static functions and const arrays +- Enhance an integer division +- The GNU licences LGPL and GPL are in version 3 +- Debian and RPM packages (#224496) +- Many cleanups +- Fix #159443 reported by Stefan Bisanz Index of incoming data in force multiple + coils function +- Fix #161989 reported by Konstantinos Togias Serial device paths more than 10 + chars long (eg. /dev/ttyUSB0) don't fit to modbus_param_t -> device char[11] + var. +- Fix #188189 reported by Chris Hellyar Compute_response_size() no entry for + read_input_status() +- Fix #191039 reported by Todd Denniston modbus.h is not installed at prefix. +- Fix #211460 reported by Todd Denniston With TCP, automatic reconnect on error + may not be desired. +- Fix #224485 reported by Todd Denniston libmodbus does not link with c++ code. +- Fix #224496 reported by Todd Denniston It is easier to install on rpm based + systems with a spec file. + +## libmodbus 1.2.5 (2008-05-18) + +- Fix #224485 reported by Todd Denniston libmodbus does not link with c++ code. + +## libmodbus 1.2.4 (2008-03-14) + +- Fix #191039 reported by Todd Denniston modbus.h is not installed at prefix. + +## libmodbus 1.2.3 (2008-02-03) + +- Fix #188189 reported by Chris Hellyar Compute_response_size() no entry for + read_input_status() +- Fix #181887 reported by Jesus Hernandez Tapia. Slave address in + build_request_packet_tcp() is hardcoded as 0xFF. + +## libmodbus 1.2.2 (2007-11-12) + +- Fix #161989 reported by Konstantinos Togias Serial device paths more than 10 + chars long (eg. /dev/ttyUSB0) don't fit to modbus_param_t -> device char[11] + var. +- Structure is also bit better 'packed' to conserve memory (see the trunk for a + real enhancement). + +## libmodbus 1.2.1 (2007-11-02) + +- Fix #159443 reported by Stefan Bisanz Index of incoming data in force multiple + coils function +- Deleted useless code in check_crc16() +- Untabify source code +- Changed author's email to Stéphane Raimbault + +## libmodbus 1.2.0 (2007-05-10) + +- FIX Compilation GCC-4.0 +- Project name in autogen.sh diff --git a/README.md b/README.md index ad8ea4af3..479da4c63 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ -A groovy modbus library -======================= +# A groovy modbus library -[![Build Status](https://travis-ci.org/stephane/libmodbus.svg?branch=master)](https://travis-ci.org/stephane/libmodbus) +![Build Status](https://github.com/stephane/libmodbus/actions/workflows/build.yml/badge.svg) +[![Coverity Scan Build Status](https://scan.coverity.com/projects/8058/badge.svg)](https://scan.coverity.com/projects/libmodbus) -Overview --------- +## Overview libmodbus is a free software library to send/receive data with a device which respects the Modbus protocol. This library can use a serial port or an Ethernet @@ -13,21 +12,17 @@ connection. The functions included in the library have been derived from the Modicon Modbus Protocol Reference Guide which can be obtained from [www.modbus.org](http://www.modbus.org). -The license of libmodbus is *LGPL v2.1 or later*. +The license of libmodbus is _LGPL v2.1 or later_. -The documentation is available as manual pages (`man libmodbus` to read general -description and list of available functions) or Web pages -[www.libmodbus.org/documentation/](http://libmodbus.org/documentation/). The -documentation is licensed under the Creative Commons Attribution-ShareAlike -License 3.0 (Unported) (). +The official website is [www.libmodbus.org](http://www.libmodbus.org). The +website contains the latest version of the documentation. -The official website is [www.libmodbus.org](http://www.libmodbus.org). - -The library is written in C and designed to run on Linux, Mac OS X, FreeBSD and +The library is written in C and designed to run on Linux, Mac OS X, FreeBSD, Embox, QNX and Windows. -Installation ------------- +You can use the library on MCUs with Embox RTOS. + +## Installation You will only need to install automake, autoconf, libtool and a C compiler (gcc or clang) to compile the library and asciidoc and xmlto to generate the @@ -38,41 +33,31 @@ To install, just run the usual dance, `./configure && make install`. Run You can change installation directory with prefix option, eg. `./configure --prefix=/usr/local/`. You have to check that the installation library path is -properly set up on your system (*/etc/ld.so.conf.d*) and library cache is up to +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 _libmodbus.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 - to fill the absence of stdint.h. +If you want to compile with Microsoft Visual Studio, you should follow the +instructions in `./src/win32/README.md`. To compile under Windows, install [MinGW](http://www.mingw.org/) and MSYS then select the common packages (gcc, automake, libtool, etc). The directory -*./src/win32/* contains a Visual C project. +_./src/win32/_ contains a Visual C project. To compile under OS X with [homebrew](http://mxcl.github.com/homebrew/), you will need to install the following dependencies first: `brew install autoconf automake libtool`. -Documentation -------------- - -The documentation is available [online](http://libmodbus.org/documentation) or -as manual pages after installation. - -The documentation is based on -[AsciiDoc](http://www.methods.co.nz/asciidoc/). Only man pages are built -by default with `make` command, you can run `make htmldoc` in *doc* directory -to generate HTML files. +To build under Embox, you have to use its build system. -Testing -------- +## Testing -Some tests are provided in *tests* directory, you can freely edit the source +Some tests are provided in _tests_ directory, you can freely edit the source code to fit your needs (it's Free Software :). -See *tests/README* for a description of each program. +See _tests/README_ for a description of each program. For a quick test of libmodbus, you can run the following programs in two shells: @@ -83,7 +68,17 @@ By default, all TCP unit tests will be executed (see --help for options). It's also possible to run the unit tests with `make check`. -To report a bug or to contribute --------------------------------- +## To report a bug or to contribute See [CONTRIBUTING](CONTRIBUTING.md) document. + +By contributing to libmodbus, you agree to the [Contributor License Agreement](CLA.txt). + +## Documentation + +You can serve the local documentation with: + +```shell +pip install mkdocs-material +mkdocs serve +``` diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..2dff79e46 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,18 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 3.1.x | :white_check_mark: | +| 3.0.x | :x: | + +## Reporting a Vulnerability + +To report a vulnerability, you can send a mail to the maintainer at +Stéphane Raimbault . + +Once the vulnerability is fixed, new releases will be published and an issue +will be created to disclose the vulnerability. + +The reporter name will be credited (if you wish). diff --git a/acinclude.m4 b/acinclude.m4 deleted file mode 100644 index 47c797135..000000000 --- a/acinclude.m4 +++ /dev/null @@ -1,29 +0,0 @@ -dnl ############################################################################## -dnl # AC_LIBMODBUS_CHECK_BUILD_DOC # -dnl # Check whether to build documentation and install man-pages # -dnl ############################################################################## -AC_DEFUN([AC_LIBMODBUS_CHECK_BUILD_DOC], [{ - # Allow user to disable doc build - AC_ARG_WITH([documentation], [AS_HELP_STRING([--without-documentation], - [disable documentation build even if asciidoc and xmlto are present [default=no]])]) - - if test "x$with_documentation" = "xno"; then - ac_libmodbus_build_doc="no" - else - # Determine whether or not documentation should be built and installed. - ac_libmodbus_build_doc="yes" - # Check for asciidoc and xmlto and don't build the docs if these are not installed. - AC_CHECK_PROG(ac_libmodbus_have_asciidoc, asciidoc, yes, no) - AC_CHECK_PROG(ac_libmodbus_have_xmlto, xmlto, yes, no) - if test "x$ac_libmodbus_have_asciidoc" = "xno" -o "x$ac_libmodbus_have_xmlto" = "xno"; then - ac_libmodbus_build_doc="no" - fi - fi - - AC_MSG_CHECKING([whether to build documentation]) - AC_MSG_RESULT([$ac_libmodbus_build_doc]) - if test "x$ac_libmodbus_build_doc" = "xno"; then - AC_MSG_WARN([The tools to build the documentation aren't installed]) - fi - AM_CONDITIONAL(BUILD_DOC, test "x$ac_libmodbus_build_doc" = "xyes") -}]) diff --git a/autogen.sh b/autogen.sh index c49e3bc92..1d6f3ca82 100755 --- a/autogen.sh +++ b/autogen.sh @@ -1,14 +1,21 @@ #! /bin/sh -if autoreconf --install --symlink --force; then +do_symlink= +if ln -s autogen-symlink-test autogen-symlink-test-link > /dev/null 2>&1; then + do_symlink=--symlink + rm autogen-symlink-test-link +fi +if autoreconf --install $do_symlink --force; then echo echo "------------------------------------------------------" echo "Initialized build system. You can now run ./configure " echo "------------------------------------------------------" echo else + s="$?" echo echo "--------------------------" echo "Running autoreconf failed." echo "--------------------------" echo + exit "$s" fi diff --git a/configure.ac b/configure.ac index 070879c9a..d77167025 100644 --- a/configure.ac +++ b/configure.ac @@ -12,8 +12,8 @@ # http://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html # m4_define([libmodbus_version_major], [3]) -m4_define([libmodbus_version_minor], [1]) -m4_define([libmodbus_version_micro], [6]) +m4_define([libmodbus_version_minor], [2]) +m4_define([libmodbus_version_micro], [0]) m4_define([libmodbus_release_status], [m4_if(m4_eval(libmodbus_version_minor % 2), [1], [snapshot], [release])]) @@ -29,8 +29,12 @@ AC_INIT([libmodbus], [http://libmodbus.org/]) AC_CONFIG_SRCDIR([src/modbus.c]) AC_CONFIG_AUX_DIR([build-aux]) -AM_INIT_AUTOMAKE([check-news foreign 1.11 -Wall -Wno-portability silent-rules tar-pax subdir-objects]) -AC_PROG_CC_STDC +AM_INIT_AUTOMAKE([foreign 1.11 silent-rules tar-pax subdir-objects]) + +# Check if user provided CFLAGS before AC_PROG_CC sets defaults +user_set_cflags="${CFLAGS+set}" + +AC_PROG_CC AC_USE_SYSTEM_EXTENSIONS AC_SYS_LARGEFILE AC_CONFIG_MACRO_DIR([m4]) @@ -59,6 +63,7 @@ AC_CANONICAL_HOST os_win32="false" os_cygwin="false" os_qnx="false" +os_sunos="false" case "${host_os}" in *mingw32*) os_win32="true" @@ -68,12 +73,17 @@ case "${host_os}" in ;; *cygwin*) os_cygwin="true" + ;; + *solaris*) + os_sunos="true" ;; esac AM_CONDITIONAL(OS_WIN32, test "$os_win32" = "true") AM_CONDITIONAL(OS_QNX, test "$os_qnx" = "true") +m4_pattern_forbid([^LT_INIT])dnl LT_INIT([disable-static win32-dll pic-only]) + AC_CHECK_HEADERS([ \ arpa/inet.h \ byteswap.h \ @@ -83,6 +93,7 @@ AC_CHECK_HEADERS([ \ linux/serial.h \ netdb.h \ netinet/in.h \ + netinet/ip.h \ netinet/tcp.h \ sys/ioctl.h \ sys/params.h \ @@ -94,15 +105,15 @@ AC_CHECK_HEADERS([ \ unistd.h \ ]) -# Check whether to build docs / install man pages -AC_LIBMODBUS_CHECK_BUILD_DOC - # Cygwin defines IPTOS_LOWDELAY but can't handle that flag so it's necessary to # workaround that problem and Cygwin doesn't define MSG_DONTWAIT. AC_CHECK_DECLS([__CYGWIN__]) +# Check for network function in libnetwork for Haiku +AC_SEARCH_LIBS(accept, network socket) + # Checks for library functions. -AC_CHECK_FUNCS([accept4 getaddrinfo gettimeofday inet_ntoa select socket strerror strlcpy]) +AC_CHECK_FUNCS([accept4 gai_strerror getaddrinfo gettimeofday inet_pton inet_ntop select socket strerror strlcpy]) # Required for MinGW with GCC v4.8.1 on Win7 AC_DEFINE(WINVER, 0x0501, _) @@ -130,19 +141,25 @@ if test "$os_cygwin" = "false"; then fi fi +if test "$os_sunos" = "true"; then + LIBS="$LIBS -lnsl -lsocket" + AC_SUBST(LIBS) +fi + # Check for RS485 support (Linux kernel version 2.6.28+) AC_CHECK_DECLS([TIOCSRS485], [], [], [[#include ]]) # Check for RTS flags AC_CHECK_DECLS([TIOCM_RTS], [], [], [[#include ]]) +# Check for termios2 support +AC_CHECK_TYPES([struct termios2], [], [], [[#include ]]) -# Wtype-limits is not supported by gcc 4.2 (default on recent Mac OS X) -my_CFLAGS="-Wall \ +WARNING_CFLAGS="-Wall \ -Wmissing-declarations -Wmissing-prototypes \ -Wnested-externs -Wpointer-arith \ --Wpointer-arith -Wsign-compare -Wchar-subscripts \ +-Wsign-compare -Wchar-subscripts \ -Wstrict-prototypes -Wshadow \ -Wformat-security" -AC_SUBST([my_CFLAGS]) +AC_SUBST([WARNING_CFLAGS]) # Build options AC_ARG_ENABLE(tests, @@ -158,10 +175,28 @@ AC_CONFIG_FILES([ src/modbus-version.h src/win32/modbus.dll.manifest tests/Makefile - doc/Makefile libmodbus.pc ]) +AC_ARG_ENABLE([debug], + [AS_HELP_STRING([--enable-debug], + [Enable debug symbols (default is no)])], + [enable_debug=$enableval], + [enable_debug=no]) + +# Set project-specific optimization flags based on debug mode. +# These are added via AM_CFLAGS in Makefile.am, preserving user's CFLAGS. +AS_IF([test "x$enable_debug" = "xyes"], [ + LIBMODBUSCFLAGS="-g -O0" + # Clear AC_PROG_CC's default -O2 so debug mode works, unless user set CFLAGS + AS_IF([test "x$user_set_cflags" != "xset"], [ + CFLAGS="-g" + ]) +], [ + LIBMODBUSCFLAGS="-O2" +]) +AC_SUBST([LIBMODBUSCFLAGS]) + AC_OUTPUT AC_MSG_RESULT([ $PACKAGE $VERSION @@ -173,9 +208,8 @@ AC_MSG_RESULT([ includedir: ${includedir} compiler: ${CC} - cflags: ${CFLAGS} + cflags: ${LIBMODBUSCFLAGS} ${WARNING_CFLAGS} ${CFLAGS} ldflags: ${LDFLAGS} - documentation: ${ac_libmodbus_build_doc} tests: ${enable_tests} ]) diff --git a/doc/Makefile.am b/doc/Makefile.am deleted file mode 100644 index 5a52c0409..000000000 --- a/doc/Makefile.am +++ /dev/null @@ -1,86 +0,0 @@ -TXT3 = \ - modbus_close.txt \ - modbus_connect.txt \ - modbus_flush.txt \ - modbus_free.txt \ - modbus_get_byte_from_bits.txt \ - modbus_get_byte_timeout.txt \ - modbus_get_float.txt \ - modbus_get_float_abcd.txt \ - modbus_get_float_badc.txt \ - modbus_get_float_cdab.txt \ - modbus_get_float_dcba.txt \ - modbus_get_header_length.txt \ - modbus_get_response_timeout.txt \ - modbus_get_slave.txt \ - modbus_get_socket.txt \ - modbus_mapping_free.txt \ - modbus_mapping_new.txt \ - 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_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_reply.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_set_bits_from_byte.txt \ - modbus_set_byte_timeout.txt \ - modbus_set_debug.txt \ - modbus_set_error_recovery.txt \ - modbus_set_float.txt \ - modbus_set_float_abcd.txt \ - modbus_set_float_badc.txt \ - modbus_set_float_cdab.txt \ - modbus_set_float_dcba.txt \ - modbus_set_response_timeout.txt \ - modbus_set_slave.txt \ - modbus_set_socket.txt \ - modbus_strerror.txt \ - modbus_tcp_accept.txt \ - modbus_tcp_pi_accept.txt \ - modbus_tcp_listen.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 -TXT7 = libmodbus.txt - -EXTRA_DIST = asciidoc.conf $(TXT3) $(TXT7) - -MAN3 = $(TXT3:%.txt=%.3) -MAN7 = $(TXT7:%.txt=%.7) - -if BUILD_DOC -man3_MANS = $(MAN3) -man7_MANS = $(MAN7) -endif - -HTML = $(TXT3:%.txt=%.html) $(TXT7:%.txt=%.html) - -htmldoc: $(HTML) - -.txt.html: - asciidoc -d manpage -b xhtml11 -f asciidoc.conf -alibmodbus_version=@LIBMODBUS_VERSION@ $< - -.txt.3 .txt.7: - a2x --doctype manpage --format manpage -alibmodbus_version=@LIBMODBUS_VERSION@ $< - -CLEANFILES = *.3 *.7 *.html diff --git a/doc/asciidoc.conf b/doc/asciidoc.conf deleted file mode 100644 index 39e67e441..000000000 --- a/doc/asciidoc.conf +++ /dev/null @@ -1,51 +0,0 @@ -[paradef-default] -literal-style=template="literalparagraph" - -[macros] -(?su)[\\]?(?Plinkmb):(?P\S*?)\[(?P.*?)\]= - -ifdef::backend-docbook[] -[linkmb-inlinemacro] -{0%{target}} -{0#} -{0#{target}{0}} -{0#} -endif::backend-docbook[] - -ifdef::backend-xhtml11[] -[linkmb-inlinemacro] -{target}{0?({0})} -endif::backend-xhtml11[] - -ifdef::doctype-manpage[] -ifdef::backend-docbook[] -[header] -template::[header-declarations] - - -{mantitle} -{manvolnum} -libmodbus -v{libmodbus_version} -libmodbus Manual - - - {manname} - {manpurpose} - -endif::backend-docbook[] -endif::doctype-manpage[] - -ifdef::backend-xhtml11[] -[footer] - -{disable-javascript%

} - - - -endif::backend-xhtml11[] diff --git a/doc/libmodbus.txt b/doc/libmodbus.txt deleted file mode 100644 index 241203712..000000000 --- a/doc/libmodbus.txt +++ /dev/null @@ -1,287 +0,0 @@ -libmodbus(7) -============ - - -NAME ----- -libmodbus - a fast and portable Modbus library - - -SYNOPSIS --------- -*#include * - -*cc* \`pkg-config --cflags --libs libmodbus` 'files' - - -DESCRIPTION ------------ -libmodbus is a library to send/receive data with a device which respects the -Modbus protocol. This library contains various backends to communicate over -different networks (eg. serial in RTU mode or Ethernet in TCP/IPv6). The -http://www.modbus.org site provides documentation about the protocol at -http://www.modbus.org/specs.php. - -libmodbus provides an abstraction of the lower communication layers and offers -the same API on all supported platforms. - -This documentation presents an overview of libmodbus concepts, describes how -libmodbus abstracts Modbus communication with different hardware and platforms -and provides a reference manual for the functions provided by the libmodbus -library. - - -Contexts -~~~~~~~~ -The Modbus protocol contains many variants (eg. serial RTU or Ethernet TCP), to -ease the implementation of a variant, the library was designed to use a backend -for each variant. The backends are also a convenient way to fulfill other -requirements (eg. real-time operations). Each backend offers a specific function -to create a new 'modbus_t' context. The 'modbus_t' context is an opaque -structure containing all necessary information to establish a connection with -other Modbus devices according to the selected variant. - -You can choose the best context for your needs among: - -RTU Context -^^^^^^^^^^^ -The RTU backend (Remote Terminal Unit) is used in serial communication and makes -use of a compact, binary representation of the data for protocol -communication. The RTU format follows the commands/data with a cyclic redundancy -check checksum as an error check mechanism to ensure the reliability of -data. Modbus RTU is the most common implementation available for Modbus. A -Modbus RTU message must be transmitted continuously without inter-character -hesitations (extract from Wikipedia, Modbus, http://en.wikipedia.org/wiki/Modbus -(as of Mar. 13, 2011, 20:51 GMT). - -The Modbus RTU framing calls a slave, a device/service which handle Modbus -requests, and a master, a client which send requests. The communication is -always initiated by the master. - -Many Modbus devices can be connected together on the same physical link so -before sending a message, you must set the slave (receiver) with -linkmb:modbus_set_slave[3]. If you're running a slave, its slave number will be -used to filter received messages. - -The libmodbus implementation of RTU isn't time based as stated in original -Modbus specification, instead all bytes are sent as fast as possible and a -response or an indication is considered complete when all expected characters -have been received. This implementation offers very fast communication but you -must take care to set a response timeout of slaves less than response timeout of -master (ortherwise other slaves may ignore master requests when one of the slave -is not responding). - -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] - - -TCP (IPv4) Context -^^^^^^^^^^^^^^^^^^ -The TCP backend implements a Modbus variant used for communications over -TCP/IPv4 networks. It does not require a checksum calculation as lower layer -takes care of the same. - -Create a Modbus TCP context:: - linkmb:modbus_new_tcp[3] - - -TCP PI (IPv4 and IPv6) Context -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -The TCP PI (Protocol Independent) backend implements a Modbus variant used for -communications over TCP IPv4 and IPv6 networks. It does not require a checksum -calculation as lower layer takes care of the same. - -Contrary to the TCP IPv4 only backend, the TCP PI backend offers hostname -resolution but it consumes about 1Kb of additional memory. - -Create a Modbus TCP context:: - linkmb:modbus_new_tcp_pi[3] - - -Common -^^^^^^ -Before using any libmodbus functions, the caller must allocate and initialize a -'modbus_t' context with functions explained above, then the following functions -are provided to modify and free a 'context': - -Free libmodbus context:: - linkmb:modbus_free[3] - -Set slave ID:: - linkmb:modbus_set_slave[3] - -Enable debug mode:: - linkmb:modbus_set_debug[3] - -Timeout settings:: - linkmb:modbus_get_byte_timeout[3] - linkmb:modbus_set_byte_timeout[3] - linkmb:modbus_get_response_timeout[3] - linkmb:modbus_set_response_timeout[3] - linkmb:modbus_get_indication_timeout[3] - linkmb:modbus_set_indication_timeout[3] - -Error recovery mode:: - linkmb:modbus_set_error_recovery[3] - -Setter/getter of internal socket:: - linkmb:modbus_set_socket[3] - linkmb:modbus_get_socket[3] - -Information about header:: - linkmb:modbus_get_header_length[3] - -Macros for data manipulation:: - -- MODBUS_GET_HIGH_BYTE(data), extracts the high byte from a byte -- MODBUS_GET_LOW_BYTE(data), extracts the low byte from a byte -- MODBUS_GET_INT64_FROM_INT16(tab_int16, index), builds an int64 from the four - first int16 starting at tab_int16[index] -- MODBUS_GET_INT32_FROM_INT16(tab_int16, index), builds an int32 from the two - first int16 starting at tab_int16[index] -- MODBUS_GET_INT16_FROM_INT8(tab_int8, index), builds an int16 from the two - first int8 starting at tab_int8[index] -- MODBUS_SET_INT16_TO_INT8(tab_int8, index, value), set an int16 value into - the two first bytes starting at tab_int8[index] -- MODBUS_SET_INT32_TO_INT16(tab_int16, index, value), set an int32 value into - the two first int16 starting at tab_int16[index] -- MODBUS_SET_INT64_TO_INT16(tab_int16, index, value), set an int64 value into - the four first int16 starting at tab_int16[index] - -Handling of bits and bytes:: - linkmb:modbus_set_bits_from_byte[3] - linkmb:modbus_set_bits_from_bytes[3] - linkmb:modbus_get_byte_from_bits[3] - -Set or get float numbers:: - linkmb:modbus_get_float_abcd[3] - linkmb:modbus_set_float_abcd[3] - linkmb:modbus_get_float_badc[3] - linkmb:modbus_set_float_badc[3] - linkmb:modbus_get_float_cdab[3] - linkmb:modbus_set_float_cdab[3] - linkmb:modbus_get_float_dcba[3] - linkmb:modbus_set_float_dcba[3] - linkmb:modbus_get_float[3] (deprecated) - linkmb:modbus_set_float[3] (deprecated) - - - -Connection -~~~~~~~~~~ -The following functions are provided to establish and close a connection with -Modbus devices: - -Establish a connection:: - linkmb:modbus_connect[3] - -Close a connection:: - linkmb:modbus_close[3] - -Flush a connection:: - linkmb:modbus_flush[3] - - -Client -~~~~~~ -The Modbus protocol defines different data types and functions to read and write -them from/to remote devices. The following functions are used by the clients to -send Modbus requests: - -Read data:: - linkmb:modbus_read_bits[3] - linkmb:modbus_read_input_bits[3] - linkmb:modbus_read_registers[3] - linkmb:modbus_read_input_registers[3] - linkmb:modbus_report_slave_id[3] - -Write data:: - linkmb:modbus_write_bit[3] - linkmb:modbus_write_register[3] - linkmb:modbus_write_bits[3] - linkmb:modbus_write_registers[3] - -Write and read data:: - linkmb:modbus_write_and_read_registers[3] - -Raw requests:: - linkmb:modbus_send_raw_request[3] - linkmb:modbus_receive_confirmation[3] - -Reply an exception:: - linkmb:modbus_reply_exception[3] - - -Server -~~~~~~ -The server is waiting for request from clients and must answer when it is -concerned by the request. - -In TCP mode, you must not use the usual linkmb:modbus_connect[3] to establish the connection but a pair of accept/listen calls:: - linkmb:modbus_tcp_listen[3] - linkmb:modbus_tcp_accept[3] - linkmb:modbus_tcp_pi_listen[3] - linkmb:modbus_tcp_pi_accept[3] - -then the data can be received with:: - linkmb:modbus_receive[3] - -and a response can be send with:: - linkmb:modbus_reply[3] - linkmb:modbus_reply_exception[3] - -To handle the mapping of your Modbus data, you must use: - linkmb:modbus_mapping_new[3] - linkmb:modbus_mapping_free[3] - - -ERROR HANDLING --------------- -The libmodbus functions handle errors using the standard conventions found on -POSIX systems. Generally, this means that upon failure a libmodbus function -shall return either a NULL value (if returning a pointer) or a negative value -(if returning an integer), and the actual error code shall be stored in the -'errno' variable. - -The *modbus_strerror()* function is provided to translate libmodbus-specific -error codes into error message strings; for details refer to -linkmb:modbus_strerror[3]. - - -MISCELLANEOUS -------------- -The _LIBMODBUS_VERSION_STRING_ constant indicates the libmodbus version the -program has been compiled against. The variables 'libmodbus_version_major', -'libmodbus_version_minor', 'libmodbus_version_micro' give the version the -program is linked against. - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - - - -RESOURCES ---------- -Main web site: - -Report bugs on the issue tracker at -. - - -COPYING -------- -Free use of this software is granted under the terms of the GNU Lesser General -Public License (LGPL v2.1+). For details see the file `COPYING.LESSER` included -with the libmodbus distribution. diff --git a/doc/modbus_free.txt b/doc/modbus_free.txt deleted file mode 100644 index f7b12f070..000000000 --- a/doc/modbus_free.txt +++ /dev/null @@ -1,33 +0,0 @@ -modbus_free(3) -============== - - -NAME ----- -modbus_free - free a libmodbus context - - -SYNOPSIS --------- -*void modbus_free(modbus_t *'ctx');* - - -DESCRIPTION ------------ -The *modbus_free()* function shall free an allocated modbus_t structure. - - -RETURN VALUE ------------- -There is no return values. - - -SEE ALSO --------- -linkmb:libmodbus[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_get_byte_from_bits.txt b/doc/modbus_get_byte_from_bits.txt deleted file mode 100644 index e0828f1a6..000000000 --- a/doc/modbus_get_byte_from_bits.txt +++ /dev/null @@ -1,35 +0,0 @@ -modbus_get_byte_from_bits(3) -============================ - -NAME ----- -modbus_get_byte_from_bits - get the value from many bits - - -SYNOPSIS --------- -*uint8_t modbus_get_byte_from_bits(const uint8_t *'src', int 'index', unsigned int 'nb_bits');* - - -DESCRIPTION ------------ -The *modbus_get_byte_from_bits()* function shall extract a value from many -bits. All _nb_bits_ bits from _src_ at position _index_ will be read as a -single value. To obtain a full byte, set nb_bits to 8. - - -RETURN VALUE ------------- -The function shall return a byte containing the bits read. - - -SEE ALSO --------- -linkmb:modbus_set_bits_from_byte[3] -linkmb:modbus_set_bits_from_bytes[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_get_byte_timeout.txt b/doc/modbus_get_byte_timeout.txt deleted file mode 100644 index d7ba0704e..000000000 --- a/doc/modbus_get_byte_timeout.txt +++ /dev/null @@ -1,50 +0,0 @@ -modbus_get_byte_timeout(3) -========================== - - -NAME ----- -modbus_get_byte_timeout - get timeout between bytes - - -SYNOPSIS --------- -*int modbus_get_byte_timeout(modbus_t *'ctx', uint32_t *'to_sec', uint32_t *'to_usec');* - - -DESCRIPTION ------------ -The *modbus_get_byte_timeout()* function shall store the timeout interval -between two consecutive bytes of the same message in the _to_sec_ and _to_usec_ -arguments. - - -RETURN VALUE ------------- -The function shall return 0 if successful. Otherwise it shall return -1 and set -errno. - - -EXAMPLE -------- -[source,c] -------------------- -uint32_t to_sec; -uint32_t to_usec; - -/* Save original timeout */ -modbus_get_byte_timeout(ctx, &to_sec, &to_usec); -------------------- - - -SEE ALSO --------- -linkmb:modbus_set_byte_timeout[3] -linkmb:modbus_get_response_timeout[3] -linkmb:modbus_set_response_timeout[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_get_float.txt b/doc/modbus_get_float.txt deleted file mode 100644 index 023c0bb50..000000000 --- a/doc/modbus_get_float.txt +++ /dev/null @@ -1,39 +0,0 @@ -modbus_get_float(3) -=================== - - -NAME ----- -modbus_get_float - get a float value from 2 registers - - -SYNOPSIS --------- -*float modbus_get_float(const uint16_t *'src');* - -Warning, this function is *deprecated* since libmodbus v3.2.0 and has been -replaced by *modbus_get_float_dcba()*. - -DESCRIPTION ------------ -The *modbus_get_float()* function shall get a float from 4 bytes in Modbus -format (DCBA byte order). The _src_ array must be a pointer on two 16 bits -values, for example, if the first word is set to 0x4465 and the second to -0x229a, the float value will be 916.540649. - - -RETURN VALUE ------------- -The function shall return a float. - - -SEE ALSO --------- -linkmb:modbus_set_float[3] -linkmb:modbus_set_float_dcba[3] -linkmb:modbus_get_float_dcba[3] - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_get_float_abcd.txt b/doc/modbus_get_float_abcd.txt deleted file mode 100644 index 6a491b29f..000000000 --- a/doc/modbus_get_float_abcd.txt +++ /dev/null @@ -1,39 +0,0 @@ -modbus_get_float_abcd(3) -======================== - - -NAME ----- -modbus_get_float_abcd - get a float value from 2 registers in ABCD byte order - - -SYNOPSIS --------- -*float modbus_get_float_abcd(const uint16_t *'src');* - - -DESCRIPTION ------------ -The *modbus_get_float_abcd()* function shall get a float from 4 bytes in usual -Modbus format. The _src_ array must be a pointer on two 16 bits values, for -example, if the first word is set to 0x0020 and the second to 0xF147, the float -value will be read as 123456.0. - - -RETURN VALUE ------------- -The function shall return a float. - - -SEE ALSO --------- -linkmb:modbus_set_float_abcd[3] -linkmb:modbus_get_float_badc[3] -linkmb:modbus_get_float_cdab[3] -linkmb:modbus_get_float_dcba[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_get_float_badc.txt b/doc/modbus_get_float_badc.txt deleted file mode 100644 index 317770d47..000000000 --- a/doc/modbus_get_float_badc.txt +++ /dev/null @@ -1,39 +0,0 @@ -modbus_get_float_badc(3) -======================== - - -NAME ----- -modbus_get_float_badc - get a float value from 2 registers in BADC byte order - - -SYNOPSIS --------- -*float modbus_get_float_badc(const uint16_t *'src');* - - -DESCRIPTION ------------ -The *modbus_get_float_badc()* function shall get a float from 4 bytes with -swapped bytes (BADC instead of ABCD). The _src_ array must be a pointer on two -16 bits values, for example, if the first word is set to 0x2000 and the second -to 0x47F1, the float value will be read as 123456.0. - - -RETURN VALUE ------------- -The function shall return a float. - - -SEE ALSO --------- -linkmb:modbus_set_float_badc[3] -linkmb:modbus_get_float_abcd[3] -linkmb:modbus_get_float_cdab[3] -linkmb:modbus_get_float_dcba[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_get_float_cdab.txt b/doc/modbus_get_float_cdab.txt deleted file mode 100644 index 82261b726..000000000 --- a/doc/modbus_get_float_cdab.txt +++ /dev/null @@ -1,39 +0,0 @@ -modbus_get_float_cdab(3) -======================== - - -NAME ----- -modbus_get_float_cdab - get a float value from 2 registers in CDAB byte order - - -SYNOPSIS --------- -*float modbus_get_float_cdab(const uint16_t *'src');* - - -DESCRIPTION ------------ -The *modbus_get_float_cdab()* function shall get a float from 4 bytes with -swapped words (CDAB order instead of ABCD). The _src_ array must be a pointer on -two 16 bits values, for example, if the first word is set to F147 and the second -to 0x0020, the float value will be read as 123456.0. - - -RETURN VALUE ------------- -The function shall return a float. - - -SEE ALSO --------- -linkmb:modbus_set_float_cdab[3] -linkmb:modbus_get_float_abcd[3] -linkmb:modbus_get_float_badc[3] -linkmb:modbus_get_float_dcba[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_get_float_dcba.txt b/doc/modbus_get_float_dcba.txt deleted file mode 100644 index cd98a048c..000000000 --- a/doc/modbus_get_float_dcba.txt +++ /dev/null @@ -1,39 +0,0 @@ -modbus_get_float_dcba(3) -======================== - - -NAME ----- -modbus_get_float_dcba - get a float value from 2 registers in DCBA byte order - - -SYNOPSIS --------- -*float modbus_get_float_dcba(const uint16_t *'src');* - - -DESCRIPTION ------------ -The *modbus_get_float_dcba()* function shall get a float from 4 bytes in -inversed Modbus format (DCBA order instead of ABCD). The _src_ array must be a -pointer on two 16 bits values, for example, if the first word is set to 0x47F1 -and the second to 0x2000, the float value will be read as 123456.0. - - -RETURN VALUE ------------- -The function shall return a float. - - -SEE ALSO --------- -linkmb:modbus_set_float_dcba[3] -linkmb:modbus_get_float_abcd[3] -linkmb:modbus_get_float_badc[3] -linkmb:modbus_get_float_cdab[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_get_header_length.txt b/doc/modbus_get_header_length.txt deleted file mode 100644 index 79f7cd96d..000000000 --- a/doc/modbus_get_header_length.txt +++ /dev/null @@ -1,35 +0,0 @@ -modbus_get_header_length(3) -=========================== - - -NAME ----- -modbus_get_header_length - retrieve the current header length - - -SYNOPSIS --------- -*int modbus_get_header_length(modbus_t *'ctx');* - - -DESCRIPTION ------------ -The *modbus_get_header_length()* function shall retrieve the current header -length from the backend. This function is convenient to manipulate a message and -so its limited to low-level operations. - - -RETURN VALUE ------------- -The header length as integer value. - - -SEE ALSO --------- -linkmb:libmodbus[7] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_get_indication_timeout.txt b/doc/modbus_get_indication_timeout.txt deleted file mode 100644 index 3af39d9cd..000000000 --- a/doc/modbus_get_indication_timeout.txt +++ /dev/null @@ -1,53 +0,0 @@ -modbus_get_indication_timeout(3) -================================ - - -NAME ----- -modbus_get_indication_timeout - get timeout used to wait for an indication (request received by a server). - -SYNOPSIS --------- -*int modbus_get_indication_timeout(modbus_t *'ctx', uint32_t *'to_sec', uint32_t *'to_usec');* - - -DESCRIPTION ------------ - -The *modbus_get_indication_timeout()* function shall store the timeout interval -used to wait for an indication in the _to_sec_ and _to_usec_ arguments. -Indication is the term used by the Modbus protocol to designate a request -received by the server. - -The default value is zero, it means the server will wait forever. - - -RETURN VALUE ------------- -The function shall return 0 if successful. Otherwise it shall return -1 and set -errno. - - -EXAMPLE -------- -[source,c] -------------------- -uint32_t to_sec; -uint32_t to_usec; - -/* Save original timeout */ -modbus_get_indication_timeout(ctx, &to_sec, &to_usec); -------------------- - - -SEE ALSO --------- -linkmb:modbus_set_indication_timeout[3] -linkmb:modbus_get_response_timeout[3] -linkmb:modbus_set_response_timeout[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_get_response_timeout.txt b/doc/modbus_get_response_timeout.txt deleted file mode 100644 index 33558022c..000000000 --- a/doc/modbus_get_response_timeout.txt +++ /dev/null @@ -1,52 +0,0 @@ -modbus_get_response_timeout(3) -============================== - - -NAME ----- -modbus_get_response_timeout - get timeout for response - - -SYNOPSIS --------- -*int modbus_get_response_timeout(modbus_t *'ctx', uint32_t *'to_sec', uint32_t *'to_usec');* - - -DESCRIPTION ------------ -The *modbus_get_response_timeout()* function shall return the timeout interval -used to wait for a response in the _to_sec_ and _to_usec_ arguments. - - -RETURN VALUE ------------- -The function shall return 0 if successful. Otherwise it shall return -1 and set -errno. - - -EXAMPLE -------- -[source,c] -------------------- -uint32_t old_response_to_sec; -uint32_t old_response_to_usec; - -/* Save original timeout */ -modbus_get_response_timeout(ctx, &old_response_to_sec, &old_response_to_usec); - -/* Define a new and too short timeout! */ -modbus_set_response_timeout(ctx, 0, 0); -------------------- - - -SEE ALSO --------- -linkmb:modbus_set_response_timeout[3] -linkmb:modbus_get_byte_timeout[3] -linkmb:modbus_set_byte_timeout[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_get_slave.txt b/doc/modbus_get_slave.txt deleted file mode 100644 index 89e2ac019..000000000 --- a/doc/modbus_get_slave.txt +++ /dev/null @@ -1,41 +0,0 @@ -modbus_get_slave(3) -=================== - - -NAME ----- -modbus_get_slave - get slave number in the context - - -SYNOPSIS --------- -*int modbus_get_slave(modbus_t *'ctx');* - - -DESCRIPTION ------------ -The *modbus_get_slave()* function shall get the slave number in the libmodbus -context. - - -RETURN VALUE ------------- -The function shall return the slave number if successful. Otherwise it shall return -1 -and set errno to one of the values defined below. - - -ERRORS ------- -*EINVAL*:: -The libmodbus context is undefined. - - -SEE ALSO --------- -linkmb:modbus_set_slave[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_get_socket.txt b/doc/modbus_get_socket.txt deleted file mode 100644 index 8645fa4e2..000000000 --- a/doc/modbus_get_socket.txt +++ /dev/null @@ -1,35 +0,0 @@ -modbus_get_socket(3) -==================== - - -NAME ----- -modbus_get_socket - get the current socket of the context - - -SYNOPSIS --------- -*int modbus_get_socket(modbus_t *'ctx');* - - -DESCRIPTION ------------ -The *modbus_get_socket()* function shall return the current socket or file -descriptor of the libmodbus context. - - -RETURN VALUE ------------- -The function returns the current socket or file descriptor of the context if -successful. Otherwise it shall return -1 and set errno. - - -SEE ALSO --------- -linkmb:modbus_set_socket[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_mapping_free.txt b/doc/modbus_mapping_free.txt deleted file mode 100644 index 0a99f6233..000000000 --- a/doc/modbus_mapping_free.txt +++ /dev/null @@ -1,34 +0,0 @@ -modbus_mapping_free(3) -===================== - - -NAME ----- -modbus_mapping_free - free a modbus_mapping_t structure - - -SYNOPSIS --------- -*void modbus_mapping_free(modbus_mapping_t *'mb_mapping');* - - -DESCRIPTION ------------ -The function shall free the four arrays of mb_mapping_t structure and finally -the mb_mapping_t referenced by _mb_mapping_. - - -RETURN VALUE ------------- -There is no return values. - - -SEE ALSO --------- -linkmb:modbus_mapping_new[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_mapping_new.txt b/doc/modbus_mapping_new.txt deleted file mode 100644 index 71cde1d63..000000000 --- a/doc/modbus_mapping_new.txt +++ /dev/null @@ -1,69 +0,0 @@ -modbus_mapping_new(3) -===================== - - -NAME ----- -modbus_mapping_new - allocate four arrays of bits and registers - - -SYNOPSIS --------- -*modbus_mapping_t* modbus_mapping_new(int 'nb_bits', int 'nb_input_bits', int 'nb_registers', int 'nb_input_registers');* - - -DESCRIPTION ------------ -The *modbus_mapping_new()* function shall allocate four arrays to store bits, -input bits, registers and inputs registers. The pointers are stored in -modbus_mapping_t structure. All values of the arrays are initialized to zero. - -This function is equivalent to a call of the -linkmb:modbus_mapping_new_start_address[3] function with all start addresses to -`0`. - -If it isn't necessary to allocate an array for a specific type of data, you can -pass the zero value in argument, the associated pointer will be NULL. - -This function is convenient to handle requests in a Modbus server/slave. - - -RETURN VALUE ------------- -The function shall return the new allocated structure if successful. Otherwise -it shall return NULL and set errno. - - -ERRORS ------- -*ENOMEM*:: -Not enough memory - - -EXAMPLE -------- -[source,c] -------------------- -/* The first value of each array is accessible from the 0 address. */ -mb_mapping = modbus_mapping_new(BITS_ADDRESS + BITS_NB, - INPUT_BITS_ADDRESS + INPUT_BITS_NB, - REGISTERS_ADDRESS + REGISTERS_NB, - INPUT_REGISTERS_ADDRESS + INPUT_REGISTERS_NB); -if (mb_mapping == NULL) { - fprintf(stderr, "Failed to allocate the mapping: %s\n", - modbus_strerror(errno)); - modbus_free(ctx); - return -1; -} -------------------- - -SEE ALSO --------- -linkmb:modbus_mapping_free[3] -linkmb:modbus_mapping_new_start_address[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_mapping_new_start_address.txt b/doc/modbus_mapping_new_start_address.txt deleted file mode 100644 index ec7bfdb35..000000000 --- a/doc/modbus_mapping_new_start_address.txt +++ /dev/null @@ -1,81 +0,0 @@ -modbus_mapping_new_start_address(3) -=================================== - - -NAME ----- -modbus_mapping_new_start_address - allocate four arrays of bits and registers accessible from their starting addresses - - -SYNOPSIS --------- -*modbus_mapping_t* modbus_mapping_new_start_address(int 'start_bits', int 'nb_bits', - int 'start_input_bits', int 'nb_input_bits', - int 'start_registers', int 'nb_registers', - int 'start_input_registers', int 'nb_input_registers');* - - -DESCRIPTION ------------ -The _modbus_mapping_new_start_address()_ function shall allocate four arrays to -store bits, input bits, registers and inputs registers. The pointers are stored -in modbus_mapping_t structure. All values of the arrays are initialized to zero. - -The different starting adresses make it possible to place the mapping at any -address in each address space. This way, you can give access to values stored -at high adresses without allocating memory from the address zero, for eg. to -make available registers from 10000 to 10009, you can use: - -[source,c] -------------------- -mb_mapping = modbus_mapping_new_start_address(0, 0, 0, 0, 10000, 10, 0, 0); -------------------- - -With this code, only 10 registers (`uint16_t`) are allocated. - -If it isn't necessary to allocate an array for a specific type of data, you can -pass the zero value in argument, the associated pointer will be NULL. - -This function is convenient to handle requests in a Modbus server/slave. - - -RETURN VALUE ------------- -The _modbus_mapping_new_start_address()_ function shall return the new allocated structure if -successful. Otherwise it shall return NULL and set errno. - - -ERRORS ------- -ENOMEM:: -Not enough memory - - -EXAMPLE -------- -[source,c] -------------------- -/* The first value of each array is accessible at the defined address. - The end address is ADDRESS + NB - 1. */ -mb_mapping = modbus_mapping_new_start_address(BITS_ADDRESS, BITS_NB, - INPUT_BITS_ADDRESS, INPUT_BITS_NB, - REGISTERS_ADDRESS, REGISTERS_NB, - INPUT_REGISTERS_ADDRESS, INPUT_REGISTERS_NB); -if (mb_mapping == NULL) { - fprintf(stderr, "Failed to allocate the mapping: %s\n", - modbus_strerror(errno)); - modbus_free(ctx); - return -1; -} -------------------- - -SEE ALSO --------- -linkmb:modbus_mapping_new[3] -linkmb:modbus_mapping_free[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_mask_write_register.txt b/doc/modbus_mask_write_register.txt deleted file mode 100644 index 7365503ca..000000000 --- a/doc/modbus_mask_write_register.txt +++ /dev/null @@ -1,41 +0,0 @@ -modbus_mask_write_register(3) -============================= - - -NAME ----- -modbus_mask_write_register - mask a single register - - -SYNOPSIS --------- -*int modbus_mask_write_register(modbus_t *'ctx', int 'addr', uint16_t 'and', uint16_t 'or');* - - -DESCRIPTION ------------ -The *modbus_mask_write_register()* function shall modify the value of the -holding register at the address 'addr' of the remote device using the algorithm: - - new value = (current value AND 'and') OR ('or' AND (NOT 'and')) - -The function uses the Modbus function code 0x16 (mask single register). - - -RETURN VALUE ------------- -The function shall return 1 if successful. Otherwise it shall return -1 and set -errno. - - -SEE ALSO --------- -linkmb:modbus_read_registers[3] -linkmb:modbus_write_registers[3] - - -AUTHORS -------- -Martijn de Gouw -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_new_rtu.txt b/doc/modbus_new_rtu.txt deleted file mode 100644 index 7a8eaacf3..000000000 --- a/doc/modbus_new_rtu.txt +++ /dev/null @@ -1,91 +0,0 @@ -modbus_new_rtu(3) -================= - - -NAME ----- -modbus_new_rtu - create a libmodbus context for RTU - - -SYNOPSIS --------- -*modbus_t *modbus_new_rtu(const char *'device', int 'baud', char 'parity', int 'data_bit', int 'stop_bit');* - - - -DESCRIPTION ------------ -The *modbus_new_rtu()* function shall allocate and initialize a _modbus_t_ -structure to communicate in RTU mode on a serial line. - -The _device_ argument specifies the name of the serial port handled by the OS, -eg. "/dev/ttyS0" or "/dev/ttyUSB0". On Windows, it's necessary to prepend COM -name with "\\.\" for COM number greater than 9, eg. "\\\\.\\COM10". See -http://msdn.microsoft.com/en-us/library/aa365247(v=vs.85).aspx for details - -The _baud_ argument specifies the baud rate of the communication, eg. 9600, -19200, 57600, 115200, etc. - -The _parity_ argument can have one of the following values::: -* _N_ for none -* _E_ for even -* _O_ for odd - -The _data_bits_ argument specifies the number of bits of data, the allowed -values are 5, 6, 7 and 8. - -The _stop_bits_ argument specifies the bits of stop, the allowed values are 1 -and 2. - -Once the _modbus_t_ structure is initialized, you must set the slave of your -device with linkmb:modbus_set_slave[3] and connect to the serial bus with -linkmb:modbus_connect[3]. - -RETURN VALUE ------------- -The function shall return a pointer to a _modbus_t_ structure if -successful. Otherwise it shall return NULL and set errno to one of the values -defined below. - - -ERRORS ------- -*EINVAL*:: -An invalid argument was given. - -*ENOMEM*:: -Out of memory. Possibly, the application hits its memory limit and/or whole -system is running out of memory. - - -EXAMPLE -------- -[source,c] -------------------- -modbus_t *ctx; - -ctx = modbus_new_rtu("/dev/ttyUSB0", 115200, 'N', 8, 1); -if (ctx == NULL) { - fprintf(stderr, "Unable to create the libmodbus context\n"); - return -1; -} - -modbus_set_slave(ctx, YOUR_DEVICE_ID); - -if (modbus_connect(ctx) == -1) { - fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno)); - modbus_free(ctx); - return -1; -} -------------------- - -SEE ALSO --------- -linkmb:modbus_new_tcp[3] -linkmb:modbus_free[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_new_tcp.txt b/doc/modbus_new_tcp.txt deleted file mode 100644 index 5447aa63a..000000000 --- a/doc/modbus_new_tcp.txt +++ /dev/null @@ -1,75 +0,0 @@ -modbus_new_tcp(3) -================= - - -NAME ----- -modbus_new_tcp - create a libmodbus context for TCP/IPv4 - - -SYNOPSIS --------- -*modbus_t *modbus_new_tcp(const char *'ip', int 'port');* - - -DESCRIPTION ------------ -The *modbus_new_tcp()* function shall allocate and initialize a modbus_t -structure to communicate with a Modbus TCP IPv4 server. - -The _ip_ argument specifies the IP address of the server to which the client -wants to establish a connection. A NULL value can be used to listen any addresses in -server mode. - -The _port_ argument is the TCP port to use. Set the port to -`MODBUS_TCP_DEFAULT_PORT` to use the default one (502). It’s convenient to use a -port number greater than or equal to 1024 because it’s not necessary to have -administrator privileges. - - -RETURN VALUE ------------- -The function shall return a pointer to a *modbus_t* structure if -successful. Otherwise it shall return NULL and set errno to one of the values -defined below. - - -ERRORS ------- -*EINVAL*:: -An invalid IP address was given. - -*ENOMEM*:: -Out of memory. Possibly, the application hits its memory limit and/or whole -system is running out of memory. - - -EXAMPLE -------- -[source,c] -------------------- -modbus_t *ctx; - -ctx = modbus_new_tcp("127.0.0.1", 1502); -if (ctx == NULL) { - fprintf(stderr, "Unable to allocate libmodbus context\n"); - return -1; -} - -if (modbus_connect(ctx) == -1) { - fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno)); - modbus_free(ctx); - return -1; -} -------------------- - -SEE ALSO --------- -linkmb:modbus_tcp_listen[3] -linkmb:modbus_free[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_new_tcp_pi.txt b/doc/modbus_new_tcp_pi.txt deleted file mode 100644 index 494c49382..000000000 --- a/doc/modbus_new_tcp_pi.txt +++ /dev/null @@ -1,77 +0,0 @@ -modbus_new_tcp_pi(3) -==================== - - -NAME ----- -modbus_new_tcp_pi - create a libmodbus context for TCP Protocol Independent - - -SYNOPSIS --------- -*modbus_t *modbus_new_tcp_pi(const char *'node', const char *'service');* - - -DESCRIPTION ------------ -The *modbus_new_tcp_pi()* function shall allocate and initialize a modbus_t -structure to communicate with a Modbus TCP IPv4 or IPv6 server. - -The _node_ argument specifies the host name or IP address of the host to connect -to, eg. "192.168.0.5" , "::1" or "server.com". A NULL value can be used to -listen any addresses in server mode. - -The _service_ argument is the service name/port number to connect to. To use the -default Modbus port use the string "502". On many Unix systems, it’s -convenient to use a port number greater than or equal to 1024 because it’s not -necessary to have administrator privileges. - - -RETURN VALUE ------------- -The function shall return a pointer to a *modbus_t* structure if -successful. Otherwise it shall return NULL and set errno to one of the values -defined below. - - -ERRORS ------- -*EINVAL*:: -The node string is empty or has been truncated. The service string is empty or -has been truncated. - -*ENOMEM*:: -Out of memory. Possibly, the application hits its memory limit and/or whole -system is running out of memory. - - -EXAMPLE -------- -[source,c] -------------------- -modbus_t *ctx; - -ctx = modbus_new_tcp_pi("::1", "1502"); -if (ctx == NULL) { - fprintf(stderr, "Unable to allocate libmodbus context\n"); - return -1; -} - -if (modbus_connect(ctx) == -1) { - fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno)); - modbus_free(ctx); - return -1; -} -------------------- - -SEE ALSO --------- -linkmb:modbus_new_tcp[3] -linkmb:modbus_tcp_pi_listen[3] -linkmb:modbus_free[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_read_bits.txt b/doc/modbus_read_bits.txt deleted file mode 100644 index 1e7fd1e0f..000000000 --- a/doc/modbus_read_bits.txt +++ /dev/null @@ -1,48 +0,0 @@ -modbus_read_bits(3) -=================== - - -NAME ----- -modbus_read_bits - read many bits - - -SYNOPSIS --------- -*int modbus_read_bits(modbus_t *'ctx', int 'addr', int 'nb', uint8_t *'dest');* - - -DESCRIPTION ------------ -The *modbus_read_bits()* function shall read the status of the _nb_ bits (coils) -to the address _addr_ of the remote device. The result of reading is stored in -_dest_ array as unsigned bytes (8 bits) set to `TRUE` or `FALSE`. - -You must take care to allocate enough memory to store the results in _dest_ -(at least _nb_ * sizeof(uint8_t)). - -The function uses the Modbus function code 0x01 (read coil status). - - -RETURN VALUE ------------- -The function shall return the number of read bits if successful. Otherwise it -shall return -1 and set errno. - - -ERRORS ------- -*EMBMDATA*:: -Too many bits requested - - -SEE ALSO --------- -linkmb:modbus_write_bit[3] -linkmb:modbus_write_bits[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_read_input_bits.txt b/doc/modbus_read_input_bits.txt deleted file mode 100644 index d7fd3e0a7..000000000 --- a/doc/modbus_read_input_bits.txt +++ /dev/null @@ -1,47 +0,0 @@ -modbus_read_input_bits(3) -========================= - - -NAME ----- -modbus_read_input_bits - read many input bits - - -SYNOPSIS --------- -*int modbus_read_input_bits(modbus_t *'ctx', int 'addr', int 'nb', uint8_t *'dest');* - - -DESCRIPTION ------------ -The *modbus_read_input_bits()* function shall read the content of the _nb_ input -bits to the address _addr_ of the remote device. The result of reading is stored -in _dest_ array as unsigned bytes (8 bits) set to _TRUE_ or _FALSE_. - -You must take care to allocate enough memory to store the results in _dest_ -(at least _nb_ * sizeof(uint8_t)). - -The function uses the Modbus function code 0x02 (read input status). - - -RETURN VALUE ------------- -The function shall return the number of read input status if -successful. Otherwise it shall return -1 and set errno. - - -ERRORS ------- -*EMBMDATA*:: -Too many discrete inputs requested - - -SEE ALSO --------- -linkmb:modbus_read_input_registers[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_read_input_registers.txt b/doc/modbus_read_input_registers.txt deleted file mode 100644 index 20a537672..000000000 --- a/doc/modbus_read_input_registers.txt +++ /dev/null @@ -1,51 +0,0 @@ -modbus_read_input_registers(3) -============================== - - -NAME ----- -modbus_read_input_registers - read many input registers - - -SYNOPSIS --------- -*int modbus_read_input_registers(modbus_t *'ctx', int 'addr', int 'nb', uint16_t *'dest');* - - -DESCRIPTION ------------ -The *modbus_read_input_registers()* function shall read the content of the _nb_ -input registers to address _addr_ of the remote device. The result of the -reading is stored in _dest_ array as word values (16 bits). - -You must take care to allocate enough memory to store the results in _dest_ (at -least _nb_ * sizeof(uint16_t)). - -The function uses the Modbus function code 0x04 (read input registers). The -holding registers and input registers have different historical meaning, but -nowadays it's more common to use holding registers only. - - -RETURN VALUE ------------- -The function shall return the number of read input registers if -successful. Otherwise it shall return -1 and set errno. - - -ERRORS ------- -*EMBMDATA*:: -Too many bits requested - - -SEE ALSO --------- -linkmb:modbus_read_input_bits[3] -linkmb:modbus_write_register[3] -linkmb:modbus_write_registers[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_read_registers.txt b/doc/modbus_read_registers.txt deleted file mode 100644 index dd29fdcd7..000000000 --- a/doc/modbus_read_registers.txt +++ /dev/null @@ -1,79 +0,0 @@ -modbus_read_registers(3) -======================== - - -NAME ----- -modbus_read_registers - read many registers - - -SYNOPSIS --------- -*int modbus_read_registers(modbus_t *'ctx', int 'addr', int 'nb', uint16_t *'dest');* - - -DESCRIPTION ------------ -The *modbus_read_registers()* function shall read the content of the _nb_ -holding registers to the address _addr_ of the remote device. The result of -reading is stored in _dest_ array as word values (16 bits). - -You must take care to allocate enough memory to store the results in _dest_ -(at least _nb_ * sizeof(uint16_t)). - -The function uses the Modbus function code 0x03 (read holding registers). - - -RETURN VALUE ------------- -The function shall return the number of read registers -if successful. Otherwise it shall return -1 and set errno. - - -ERRORS ------- -*EMBMDATA*:: -Too many registers requested - - -EXAMPLE -------- -[source,c] -------------------- -modbus_t *ctx; -uint16_t tab_reg[64]; -int rc; -int i; - -ctx = modbus_new_tcp("127.0.0.1", 1502); -if (modbus_connect(ctx) == -1) { - fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno)); - modbus_free(ctx); - return -1; -} - -rc = modbus_read_registers(ctx, 0, 10, tab_reg); -if (rc == -1) { - fprintf(stderr, "%s\n", modbus_strerror(errno)); - return -1; -} - -for (i=0; i < rc; i++) { - printf("reg[%d]=%d (0x%X)\n", i, tab_reg[i], tab_reg[i]); -} - -modbus_close(ctx); -modbus_free(ctx); -------------------- - - -SEE ALSO --------- -linkmb:modbus_write_register[3] -linkmb:modbus_write_registers[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_receive.txt b/doc/modbus_receive.txt deleted file mode 100644 index 460d19b75..000000000 --- a/doc/modbus_receive.txt +++ /dev/null @@ -1,42 +0,0 @@ -modbus_receive(3) -================= - - -NAME ----- -modbus_receive - receive an indication request - - -SYNOPSIS --------- -*int modbus_receive(modbus_t *'ctx', uint8_t *'req');* - - -DESCRIPTION ------------ -The *modbus_receive()* function shall receive an indication request from the -socket of the context _ctx_. This function is used by Modbus slave/server to -receive and analyze indication request sent by the masters/clients. - -If you need to use another socket or file descriptor than the one defined in the -context _ctx_, see the function linkmb:modbus_set_socket[3]. - - -RETURN VALUE ------------- -The function shall store the indication request in _req_ and return the request -length if successful. The returned request length can be zero if the indication -request is ignored (eg. a query for another slave in RTU mode). Otherwise it -shall return -1 and set errno. - - -SEE ALSO --------- -linkmb:modbus_set_socket[3] -linkmb:modbus_reply[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_receive_confirmation.txt b/doc/modbus_receive_confirmation.txt deleted file mode 100644 index 118ee2b5c..000000000 --- a/doc/modbus_receive_confirmation.txt +++ /dev/null @@ -1,53 +0,0 @@ -modbus_receive_confirmation(3) -============================== - - -NAME ----- -modbus_receive_confirmation - receive a confirmation request - - -SYNOPSIS --------- -*int modbus_receive_confirmation(modbus_t *'ctx', uint8_t *'rsp');* - - -DESCRIPTION ------------ -The *modbus_receive_confirmation()* function shall receive a request via the -socket of the context _ctx_. This function must be used for debugging purposes -because the received response isn't checked against the initial request. This -function can be used to receive request not handled by the library. - -The maximum size of the response depends on the used backend, in RTU the _rsp_ -array must be _MODBUS_RTU_MAX_ADU_LENGTH_ bytes and in TCP it must be -_MODBUS_TCP_MAX_ADU_LENGTH_ bytes. If you want to write code compatible with -both, you can use the constant _MODBUS_MAX_ADU_LENGTH_ (maximum value of all -libmodbus backends). Take care to allocate enough memory to store responses to -avoid crashes of your server. - - -RETURN VALUE ------------- -The function shall store the confirmation request in _rsp_ and return the -response length if sucessful. The returned request length can be zero if the -indication request is ignored (eg. a query for another slave in RTU -mode). Otherwise it shall return -1 and set errno. - -EXAMPLE -------- -[source,c] -------------------- -uint8_t rsp[MODBUS_MAX_ADU_LENGTH]; -rc = modbus_receive_confirmation(ctx, rsp); -------------------- - -SEE ALSO --------- -linkmb:modbus_send_raw_request[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_reply.txt b/doc/modbus_reply.txt deleted file mode 100644 index 0b29d6f2c..000000000 --- a/doc/modbus_reply.txt +++ /dev/null @@ -1,52 +0,0 @@ -modbus_reply(3) -=============== - -NAME ----- -modbus_reply - send a reponse to the received request - - -SYNOPSIS --------- -*int modbus_reply(modbus_t *'ctx', const uint8_t *'req', int 'req_length', modbus_mapping_t *'mb_mapping'); - - -DESCRIPTION ------------ -The *modbus_reply()* function shall send a response to received request. The -request _req_ given in argument is analyzed, a response is then built and sent -by using the information of the modbus context _ctx_. - -If the request indicates to read or write a value the operation will done in the -modbus mapping _mb_mapping_ according to the type of the manipulated data. - -If an error occurs, an exception response will be sent. - -This function is designed for Modbus server. - - -RETURN VALUE ------------- -The function shall return the length of the response sent if -successful. Otherwise it shall return -1 and set errno. - - -ERRORS ------- -*EMBMDATA*:: -Sending has failed - -See also the errors returned by the syscall used to send the response (eg. send -or write). - - -SEE ALSO --------- -linkmb:modbus_reply_exception[3] -linkmb:libmodbus[7] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_reply_exception.txt b/doc/modbus_reply_exception.txt deleted file mode 100644 index 7e6324f94..000000000 --- a/doc/modbus_reply_exception.txt +++ /dev/null @@ -1,57 +0,0 @@ -modbus_reply_exception(3) -========================= - -NAME ----- -modbus_reply_exception - send an exception reponse - - -SYNOPSIS --------- -*int modbus_reply_exception(modbus_t *'ctx', const uint8_t *'req', unsigned int 'exception_code'); - - -DESCRIPTION ------------ -The *modbus_reply_exception()* function shall send an exception response based -on the 'exception_code' in argument. - -The libmodbus provides the following exception codes: - -* MODBUS_EXCEPTION_ILLEGAL_FUNCTION (1) -* MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS (2) -* MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE (3) -* MODBUS_EXCEPTION_SLAVE_OR_SERVER_FAILURE (4) -* MODBUS_EXCEPTION_ACKNOWLEDGE (5) -* MODBUS_EXCEPTION_SLAVE_OR_SERVER_BUSY (6) -* MODBUS_EXCEPTION_NEGATIVE_ACKNOWLEDGE (7) -* MODBUS_EXCEPTION_MEMORY_PARITY (8) -* MODBUS_EXCEPTION_NOT_DEFINED (9) -* MODBUS_EXCEPTION_GATEWAY_PATH (10) -* MODBUS_EXCEPTION_GATEWAY_TARGET (11) - -The initial request _req_ is required to build a valid response. - - -RETURN VALUE ------------- -The function shall return the length of the response sent if -successful. Otherwise it shall return -1 and set errno. - - -ERRORS ------- -*EINVAL*:: -The exception code is invalid - - -SEE ALSO --------- -linkmb:modbus_reply[3] -linkmb:libmodbus[7] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_report_slave_id.txt b/doc/modbus_report_slave_id.txt deleted file mode 100644 index 6aedec679..000000000 --- a/doc/modbus_report_slave_id.txt +++ /dev/null @@ -1,61 +0,0 @@ -modbus_report_slave_id(3) -========================= - - -NAME ----- -modbus_report_slave_id - returns a description of the controller - - -SYNOPSIS --------- -*int modbus_report_slave_id(modbus_t *'ctx', int 'max_dest', uint8_t *'dest');* - - -DESCRIPTION ------------ -The *modbus_report_slave_id()* function shall send a request to the controller -to obtain a description of the controller. - -The response stored in _dest_ contains: - -* the slave ID, this unique ID is in reality not unique at all so it's not - possible to depend on it to know how the information are packed in the - response. -* the run indicator status (0x00 = OFF, 0xFF = ON) -* additional data specific to each controller. For example, libmodbus returns - the version of the library as a string. - -The function writes at most _max_dest_ bytes from the response to _dest_ so -you must ensure that _dest_ is large enough. - -RETURN VALUE ------------- -The function shall return the number of read data if successful. - -If the output was truncated due to the _max_dest_ limit then the return value is -the number of bytes which would have been written to _dest_ if enough space had -been available. Thus, a return value greater than _max_dest_ means that the -response data was truncated. - -Otherwise it shall return -1 and set errno. - -EXAMPLE -------- -[source,c] -------------------- -uint8_t tab_bytes[MODBUS_MAX_PDU_LENGTH]; - -... - -rc = modbus_report_slave_id(ctx, MODBUS_MAX_PDU_LENGTH, tab_bytes); -if (rc > 1) { - printf("Run Status Indicator: %s\n", tab_bytes[1] ? "ON" : "OFF"); -} -------------------- - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - 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_get_serial_mode.txt b/doc/modbus_rtu_get_serial_mode.txt deleted file mode 100644 index b58072836..000000000 --- a/doc/modbus_rtu_get_serial_mode.txt +++ /dev/null @@ -1,53 +0,0 @@ -modbus_rtu_get_serial_mode(3) -============================= - - -NAME ----- -modbus_rtu_get_serial_mode - get the current serial mode - - -SYNOPSIS --------- -*int modbus_rtu_get_serial_mode(modbus_t *'ctx');* - - -DESCRIPTION ------------ -The *modbus_rtu_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 - (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, - 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 - automation because it can be used effectively over long distances and in - 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. - - -RETURN VALUE ------------- -The function shall return `MODBUS_RTU_RS232` or `MODBUS_RTU_RS485` if -successful. Otherwise it shall return -1 and set errno to one of the values -defined below. - - -ERRORS ------- -*EINVAL*:: -The current libmodbus backend is not RTU. - - -AUTHORS -------- -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_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_rtu_set_serial_mode.txt b/doc/modbus_rtu_set_serial_mode.txt deleted file mode 100644 index 7086dc4a8..000000000 --- a/doc/modbus_rtu_set_serial_mode.txt +++ /dev/null @@ -1,56 +0,0 @@ -modbus_rtu_set_serial_mode(3) -============================= - - -NAME ----- -modbus_rtu_set_serial_mode - set the serial mode - - -SYNOPSIS --------- -*int modbus_rtu_set_serial_mode(modbus_t *'ctx', int 'mode');* - - -DESCRIPTION ------------ -The *modbus_rtu_set_serial_mode()* function shall set the selected serial -mode: - -*MODBUS_RTU_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, - 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 - automation because it can be used effectively over long distances and in - electrically noisy environments. - -This function is only supported on Linux kernels 2.6.28 onwards. - - -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 current libmodbus backend is not RTU. - -*ENOTSUP*:: -The function is not supported on your platform. - -If the call to ioctl() fails, the error code of ioctl will be returned. - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_set_bits_from_byte.txt b/doc/modbus_set_bits_from_byte.txt deleted file mode 100644 index 72b2949cb..000000000 --- a/doc/modbus_set_bits_from_byte.txt +++ /dev/null @@ -1,36 +0,0 @@ -modbus_set_bits_from_byte(3) -============================ - - -NAME ----- -modbus_set_bits_from_byte - set many bits from a single byte value - - -SYNOPSIS --------- -*void modbus_set_bits_from_byte(uint8_t *'dest', int 'index', const uint8_t 'value');* - - -DESCRIPTION ------------ -The *modbus_set_bits_from_byte()* function shall set many bits from a single byte. -All 8 bits from the byte _value_ will be written to _dest_ array starting at -_index_ position. - - -RETURN VALUE ------------- -There is no return values. - - -SEE ALSO --------- -linkmb:modbus_set_bits_from_byte[3] -linkmb:modbus_set_bits_from_bytes[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_set_bits_from_bytes.txt b/doc/modbus_set_bits_from_bytes.txt deleted file mode 100644 index cfbb8ed46..000000000 --- a/doc/modbus_set_bits_from_bytes.txt +++ /dev/null @@ -1,36 +0,0 @@ -modbus_set_bits_from_bytes(3) -============================ - - -NAME ----- -modbus_set_bits_from_bytes - set many bits from an array of bytes - - -SYNOPSIS --------- -*void modbus_set_bits_from_bytes(uint8_t *'dest', int 'index', unsigned int 'nb_bits', const uint8_t *'tab_byte');* - - -DESCRIPTION ------------ -The *modbus_set_bits_from_bytes* function shall set bits by reading an array of -bytes. All the bits of the bytes read from the first position of the array -_tab_byte_ are written as bits in the _dest_ array starting at position _index_. - - -RETURN VALUE ------------- -There is no return values. - - -SEE ALSO --------- -linkmb:modbus_set_bits_from_byte[3] -linkmb:modbus_get_byte_from_bits[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_set_float.txt b/doc/modbus_set_float.txt deleted file mode 100644 index f12402283..000000000 --- a/doc/modbus_set_float.txt +++ /dev/null @@ -1,36 +0,0 @@ -modbus_set_float(3) -=================== - -NAME ----- -modbus_set_float - set a float value from 2 registers - - -SYNOPSIS --------- -*void modbus_set_float(float 'f', uint16_t *'dest');* - -Warning, this function is *deprecated* since libmodbus v3.2.0 and has been -replaced by *modbus_set_float_dcba()*. - -DESCRIPTION ------------ -The *modbus_set_float()* function shall set a float to 4 bytes in Modbus format -(ABCD). The _dest_ array must be pointer on two 16 bits values to be able to -store the full result of the conversion. - - -RETURN VALUE ------------- -There is no return values. - - -SEE ALSO --------- -linkmb:modbus_get_float[3] -linkmb:modbus_set_float_dcba[3] - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_set_float_abcd.txt b/doc/modbus_set_float_abcd.txt deleted file mode 100644 index 386cbef54..000000000 --- a/doc/modbus_set_float_abcd.txt +++ /dev/null @@ -1,38 +0,0 @@ -modbus_set_float_abcd(3) -======================== - - -NAME ----- -modbus_set_float_abcd - set a float value in 2 registers using ABCD byte order - - -SYNOPSIS --------- -*void modbus_set_float_abcd(float 'f', uint16_t *'dest');* - - -DESCRIPTION ------------ -The *modbus_set_float_abcd()* function shall set a float to 4 bytes in usual -Modbus format. The _dest_ array must be pointer on two 16 bits values to be able -to store the full result of the conversion. - - -RETURN VALUE ------------- -There is no return values. - - -SEE ALSO --------- -linkmb:modbus_get_float_abcd[3] -linkmb:modbus_set_float_badc[3] -linkmb:modbus_set_float_cdab[3] -linkmb:modbus_set_float_dcba[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_set_float_badc.txt b/doc/modbus_set_float_badc.txt deleted file mode 100644 index 8df8ca958..000000000 --- a/doc/modbus_set_float_badc.txt +++ /dev/null @@ -1,38 +0,0 @@ -modbus_set_float_badc(3) -======================== - - -NAME ----- -modbus_set_float_badc - set a float value in 2 registers using BADC byte order - - -SYNOPSIS --------- -*void modbus_set_float_badc(float 'f', uint16_t *'dest');* - - -DESCRIPTION ------------ -The *modbus_set_float_badc()* function shall set a float to 4 bytes in swapped -bytes Modbus format (BADC insted of ABCD). The _dest_ array must be pointer on -two 16 bits values to be able to store the full result of the conversion. - - -RETURN VALUE ------------- -There is no return values. - - -SEE ALSO --------- -linkmb:modbus_get_float_badc[3] -linkmb:modbus_set_float_abcd[3] -linkmb:modbus_set_float_cdab[3] -linkmb:modbus_set_float_dcba[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_set_float_cdab.txt b/doc/modbus_set_float_cdab.txt deleted file mode 100644 index 3a0372570..000000000 --- a/doc/modbus_set_float_cdab.txt +++ /dev/null @@ -1,39 +0,0 @@ -modbus_set_float_cdab(3) -======================== - - -NAME ----- -modbus_set_float_cdab - set a float value in 2 registers using CDAB byte order - - -SYNOPSIS --------- -*void modbus_set_float_cdab(float 'f', uint16_t *'dest');* - - -DESCRIPTION ------------ -The *modbus_set_float_cdab()* function shall set a float to 4 bytes in swapped -words Modbus format (CDAB order instead of ABCD). The _dest_ array must be -pointer on two 16 bits values to be able to store the full result of the -conversion. - - -RETURN VALUE ------------- -There is no return values. - - -SEE ALSO --------- -linkmb:modbus_get_float_cdab[3] -linkmb:modbus_set_float_abcd[3] -linkmb:modbus_set_float_badc[3] -linkmb:modbus_set_float_dcba[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_set_float_dcba.txt b/doc/modbus_set_float_dcba.txt deleted file mode 100644 index 578ae6f64..000000000 --- a/doc/modbus_set_float_dcba.txt +++ /dev/null @@ -1,37 +0,0 @@ -modbus_set_float_dcba(3) -======================== - - -NAME ----- -modbus_set_float_dcba - set a float value in 2 registers using DCBA byte order - - -SYNOPSIS --------- -*void modbus_set_float_dcba(float 'f', uint16_t *'dest');* - - -DESCRIPTION ------------ -The *modbus_set_float_dcba()* function shall set a float to 4 bytes in inverted -Modbus format (DCBA order). The _dest_ array must be pointer on two 16 bits -values to be able to store the full result of the conversion. - - -RETURN VALUE ------------- -There is no return values. - - -SEE ALSO --------- -linkmb:modbus_get_float_dcba[3] -linkmb:modbus_set_float[3] -linkmb:modbus_get_float[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_set_indication_timeout.txt b/doc/modbus_set_indication_timeout.txt deleted file mode 100644 index 6524bdc57..000000000 --- a/doc/modbus_set_indication_timeout.txt +++ /dev/null @@ -1,48 +0,0 @@ -modbus_set_indication_timeout(3) -================================ - - -NAME ----- -modbus_set_indication_timeout - set timeout between indications - - -SYNOPSIS --------- -*void modbus_set_indication_timeout(modbus_t *'ctx', uint32_t 'to_sec', uint32_t 'to_usec');* - - -DESCRIPTION ------------ -The *modbus_set_indication_timeout()* function shall set the timeout interval used by -a server to wait for a request from a client. - -The value of _to_usec_ argument must be in the range 0 to 999999. - -If both _to_sec_ and _to_usec_ are zero, this timeout will not be used at all. -In this case, the server will wait forever. - - -RETURN VALUE ------------- -The function shall return 0 if successful. Otherwise it shall return -1 and set -errno. - - -ERRORS ------- -*EINVAL*:: -The argument _ctx_ is NULL or _to_usec_ is larger than 1000000. - - -SEE ALSO --------- -linkmb:modbus_get_indication_timeout[3] -linkmb:modbus_get_response_timeout[3] -linkmb:modbus_set_response_timeout[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_set_slave.txt b/doc/modbus_set_slave.txt deleted file mode 100644 index 7f9ecb099..000000000 --- a/doc/modbus_set_slave.txt +++ /dev/null @@ -1,84 +0,0 @@ -modbus_set_slave(3) -=================== - - -NAME ----- -modbus_set_slave - set slave number in the context - - -SYNOPSIS --------- -*int modbus_set_slave(modbus_t *'ctx', int 'slave');* - - -DESCRIPTION ------------ -The *modbus_set_slave()* function shall set the slave number in the libmodbus -context. - -The behavior depends of network and the role of the device: - -*RTU*:: -Define the slave ID of the remote device to talk in master mode or set the -internal slave ID in slave mode. According to the protocol, a Modbus device must -only accept message holding its slave number or the special broadcast number. - -*TCP*:: -The slave number is only required in TCP if the message must reach a device on a -serial network. Some not compliant devices or software (such as modpoll) uses -the slave ID as unit identifier, that's incorrect (cf page 23 of Modbus -Messaging Implementation Guide v1.0b) but without the slave value, the faulty -remote device or software drops the requests! The special value -`MODBUS_TCP_SLAVE` (0xFF) can be used in TCP mode to restore the default value. - -The broadcast address is `MODBUS_BROADCAST_ADDRESS`. This special value must be -use when you want all Modbus devices of the network receive the request. - - -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 slave number is invalid. - - -EXAMPLE -------- -[source,c] -------------------- -modbus_t *ctx; - -ctx = modbus_new_rtu("/dev/ttyUSB0", 115200, 'N', 8, 1); -if (ctx == NULL) { - fprintf(stderr, "Unable to create the libmodbus context\n"); - return -1; -} - -rc = modbus_set_slave(ctx, YOUR_DEVICE_ID); -if (rc == -1) { - fprintf(stderr, "Invalid slave ID\n"); - modbus_free(ctx); - return -1; -} - -if (modbus_connect(ctx) == -1) { - fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno)); - modbus_free(ctx); - return -1; -} -------------------- - -SEE ALSO --------- -linkmb:modbus_get_slave[3] - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_tcp_pi_accept.txt b/doc/modbus_tcp_pi_accept.txt deleted file mode 100644 index a84dc43df..000000000 --- a/doc/modbus_tcp_pi_accept.txt +++ /dev/null @@ -1,56 +0,0 @@ -modbus_tcp_pi_accept(3) -======================= - - -NAME ----- -modbus_tcp_pi_accept - accept a new connection on a TCP PI Modbus socket (IPv6) - - -SYNOPSIS --------- -*int modbus_tcp_pi_accept(modbus_t *'ctx', int *'s);* - - -DESCRIPTION ------------ -The *modbus_tcp_pi_accept()* function shall extract the first connection on the -queue of pending connections, create a new socket and store it in libmodbus -context given in argument. If available, _accept4()_ with `SOCK_CLOEXEC` will be -called instead of *accept()*. - - -RETURN VALUE ------------- -The function shall return a new socket if successful. -Otherwise it shall return -1 and set errno. - - -EXAMPLE -------- -For detailed example, see unit-test-server.c source file in tests directory. - -[source,c] -------------------- -... - -ctx = modbus_new_tcp_pi("::0", 502); -s = modbus_tcp_pi_listen(ctx, 1); -modbus_tcp_pi_accept(ctx, &s); - -... - -close(s) -modbus_free(ctx); -------------------- - -SEE ALSO --------- -linkmb:modbus_tcp_pi_accept[3] -linkmb:modbus_tcp_listen[3] -linkmb:modbus_tcp_pi_listen[3] - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_write_and_read_registers.txt b/doc/modbus_write_and_read_registers.txt deleted file mode 100644 index e9b14bd4a..000000000 --- a/doc/modbus_write_and_read_registers.txt +++ /dev/null @@ -1,51 +0,0 @@ -modbus_write_and_read_registers(3) -================================== - - -NAME ----- -modbus_write_and_read_registers - write and read many registers in a single transaction - - -SYNOPSIS --------- -*int modbus_write_and_read_registers(modbus_t *'ctx', int 'write_addr', int 'write_nb', const uint16_t *'src', int 'read_addr', int 'read_nb', const uint16_t *'dest');* - - -DESCRIPTION ------------ -The *modbus_write_and_read_registers()* function shall write the content of the -_write_nb_ holding registers from the array 'src' to the address _write_addr_ of -the remote device then shall read the content of the _read_nb_ holding registers -to the address _read_addr_ of the remote device. The result of reading is stored -in _dest_ array as word values (16 bits). - -You must take care to allocate enough memory to store the results in _dest_ -(at least _nb_ * sizeof(uint16_t)). - -The function uses the Modbus function code 0x17 (write/read registers). - - -RETURN VALUE ------------- -The function shall return the number of read registers if successful. Otherwise -it shall return -1 and set errno. - - -ERRORS ------- -*EMBMDATA*:: -Too many registers requested, Too many registers to write - - -SEE ALSO --------- -linkmb:modbus_read_registers[3] -linkmb:modbus_write_register[3] -linkmb:modbus_write_registers[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_write_bit.txt b/doc/modbus_write_bit.txt deleted file mode 100644 index 3b4df9e30..000000000 --- a/doc/modbus_write_bit.txt +++ /dev/null @@ -1,38 +0,0 @@ -modbus_write_bit(3) -=================== - - -NAME ----- -modbus_write_bit - write a single bit - - -SYNOPSIS --------- -*int modbus_write_bit(modbus_t *'ctx', int 'addr', int 'status');* - - -DESCRIPTION ------------ -The *modbus_write_bit()* function shall write the status of _status_ at the -address _addr_ of the remote device. The value must be set to `TRUE` or `FALSE`. - -The function uses the Modbus function code 0x05 (force single coil). - - -RETURN VALUE ------------- -The function shall return 1 if successful. Otherwise it shall return -1 and set -errno. - - -SEE ALSO --------- -linkmb:modbus_read_bits[3] -linkmb:modbus_write_bits[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_write_bits.txt b/doc/modbus_write_bits.txt deleted file mode 100644 index 7f1af8488..000000000 --- a/doc/modbus_write_bits.txt +++ /dev/null @@ -1,45 +0,0 @@ -modbus_write_bits(3) -==================== - - -NAME ----- -modbus_write_bits - write many bits - - -SYNOPSIS --------- -*int modbus_write_bits(modbus_t *'ctx', int 'addr', int 'nb', const uint8_t *'src');* - - -DESCRIPTION ------------ -The *modbus_write_bits()* function shall write the status of the _nb_ bits -(coils) from _src_ at the address _addr_ of the remote device. The -_src_ array must contains bytes set to `TRUE` or `FALSE`. - -The function uses the Modbus function code 0x0F (force multiple coils). - - -RETURN VALUE ------------- -The function shall return the number of written bits if successful. Otherwise it -shall return -1 and set errno. - - -ERRORS ------- -*EMBMDATA*:: -Writing too many bits - - -SEE ALSO --------- -linkmb:modbus_read_bits[3] -linkmb:modbus_write_bit[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_write_register.txt b/doc/modbus_write_register.txt deleted file mode 100644 index 781b3af47..000000000 --- a/doc/modbus_write_register.txt +++ /dev/null @@ -1,38 +0,0 @@ -modbus_write_register(3) -======================== - - -NAME ----- -modbus_write_register - write a single register - - -SYNOPSIS --------- -*int modbus_write_register(modbus_t *'ctx', int 'addr', const uint16_t 'value');* - - -DESCRIPTION ------------ -The *modbus_write_register()* function shall write the value of _value_ -holding registers at the address _addr_ of the remote device. - -The function uses the Modbus function code 0x06 (preset single register). - - -RETURN VALUE ------------- -The function shall return 1 if successful. Otherwise it shall return -1 and set -errno. - - -SEE ALSO --------- -linkmb:modbus_read_registers[3] -linkmb:modbus_write_registers[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_write_registers.txt b/doc/modbus_write_registers.txt deleted file mode 100644 index a5654fb98..000000000 --- a/doc/modbus_write_registers.txt +++ /dev/null @@ -1,38 +0,0 @@ -modbus_write_registers(3) -========================= - - -NAME ----- -modbus_write_registers - write many registers - - -SYNOPSIS --------- -*int modbus_write_registers(modbus_t *'ctx', int 'addr', int 'nb', const uint16_t *'src');* - - -DESCRIPTION ------------ -The *modbus_write_registers()* function shall write the content of the _nb_ -holding registers from the array _src_ at address _addr_ of the remote device. - -The function uses the Modbus function code 0x10 (preset multiple registers). - - -RETURN VALUE ------------- -The function shall return the number of written registers if -successful. Otherwise it shall return -1 and set errno. - - -SEE ALSO --------- -linkmb:modbus_write_register[3] -linkmb:modbus_read_registers[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/docs/assets/client-sensors.excalidraw b/docs/assets/client-sensors.excalidraw new file mode 100644 index 000000000..bb8eba605 --- /dev/null +++ b/docs/assets/client-sensors.excalidraw @@ -0,0 +1,606 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "version": 473, + "versionNonce": 1256506895, + "isDeleted": false, + "id": "ox1Blt2bzl0onmQfB7ZAN", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 827.69921875, + "y": 210.68359375, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 251.7109375, + "height": 259.47265625, + "seed": 1508024704, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "sE5xq9Fz5VDTWcJGhJizg", + "type": "arrow" + }, + { + "id": "HZoAI_wR8CyRR9fVaFwSl", + "type": "arrow" + } + ], + "updated": 1660298248381, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 585, + "versionNonce": 720617423, + "isDeleted": false, + "id": "rjHz2X8U0ZDEyckj_tTSw", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 854.0546875, + "y": 287.16015625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 199, + "height": 160, + "seed": 2100367744, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "vJNRjoY0dZcqOBw5RzuHn", + "type": "arrow" + } + ], + "updated": 1660298281789, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 1, + "text": "Reads temperatures\nfrom various\nModbus sensors (servers)\n\nS1: read index 0 -> 28\nS2: read index 0 -> 26\n...\n", + "baseline": 154, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Reads temperatures\nfrom various\nModbus sensors (servers)\n\nS1: read index 0 -> 28\nS2: read index 0 -> 26\n...\n" + }, + { + "type": "rectangle", + "version": 534, + "versionNonce": 2127380463, + "isDeleted": false, + "id": "mDgu1gSg34HbU-NAHPB30", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 420.2109375, + "y": 212.580078125, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 216.16406250000006, + "height": 172.06640624999997, + "seed": 1336837760, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "sE5xq9Fz5VDTWcJGhJizg", + "type": "arrow" + } + ], + "updated": 1660298020282, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 653, + "versionNonce": 2127484513, + "isDeleted": false, + "id": "UxvTnld8qh188IzV4uJ2q", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 443.79296875, + "y": 286.3515625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 178, + "height": 80, + "seed": 759374208, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660298185311, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 1, + "text": "Measures temperature\nand hygrometry:\n0: 28°C\n1: 32% ", + "baseline": 74, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Measures temperature\nand hygrometry:\n0: 28°C\n1: 32% " + }, + { + "type": "text", + "version": 349, + "versionNonce": 1469666511, + "isDeleted": false, + "id": "F6UUk6_B6uALmjxcHLYw4", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 488.19921875, + "y": 237.921875, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 79, + "height": 25, + "seed": 354006656, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660298032950, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Sensor 1", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Sensor 1" + }, + { + "type": "text", + "version": 224, + "versionNonce": 864770191, + "isDeleted": false, + "id": "v7q2uvVFHZBvjr-y_ZJKl", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 884.5546875, + "y": 238.328125, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 150, + "height": 25, + "seed": 2108436864, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660299132232, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "libmodbus client", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "libmodbus client" + }, + { + "type": "arrow", + "version": 925, + "versionNonce": 27208751, + "isDeleted": false, + "id": "sE5xq9Fz5VDTWcJGhJizg", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 656.03125, + "y": 313.29799967005374, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 154.05078125, + "height": 8.294130761394001, + "seed": 455209344, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1660298248381, + "link": null, + "locked": false, + "startBinding": { + "elementId": "mDgu1gSg34HbU-NAHPB30", + "focus": 0.08648410639065775, + "gap": 19.65625 + }, + "endBinding": { + "elementId": "ox1Blt2bzl0onmQfB7ZAN", + "focus": 0.08133464876815498, + "gap": 17.6171875 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 154.05078125, + 8.294130761394001 + ] + ] + }, + { + "type": "text", + "version": 709, + "versionNonce": 650449167, + "isDeleted": false, + "id": "Q6P32mRyop5JlKGPB3tei", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 5.552321937284518, + "x": 679.345703125, + "y": 433.1444738051471, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 79, + "height": 15, + "seed": 1091054019, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660298261423, + "link": null, + "locked": false, + "fontSize": 11.542968749999993, + "fontFamily": 1, + "text": "TCP or serial", + "baseline": 10, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "TCP or serial" + }, + { + "type": "text", + "version": 707, + "versionNonce": 1999616833, + "isDeleted": false, + "id": "vETmEnYJoC4MKv7ysqjAv", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0.06779103263247777, + "x": 683.3417968749999, + "y": 281.21582031250006, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 79, + "height": 15, + "seed": 1043479501, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660298255264, + "link": null, + "locked": false, + "fontSize": 11.542968749999993, + "fontFamily": 1, + "text": "TCP or serial", + "baseline": 10, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "TCP or serial" + }, + { + "type": "rectangle", + "version": 587, + "versionNonce": 85990017, + "isDeleted": false, + "id": "pHhCP3DIU5fE4v3yi3obG", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 426.513671875, + "y": 424.2646484375, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 216.16406250000006, + "height": 172.06640624999997, + "seed": 1733016847, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "sE5xq9Fz5VDTWcJGhJizg", + "type": "arrow" + }, + { + "id": "HZoAI_wR8CyRR9fVaFwSl", + "type": "arrow" + } + ], + "updated": 1660298157321, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 708, + "versionNonce": 1824105825, + "isDeleted": false, + "id": "3oXICnKHoOzNPyb-PxNbt", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 450.095703125, + "y": 498.0361328125, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 178, + "height": 80, + "seed": 1378197345, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660298208836, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 1, + "text": "Measures temperature\nand hygrometry:\n0: 26°C\n1: 40% ", + "baseline": 74, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Measures temperature\nand hygrometry:\n0: 26°C\n1: 40% " + }, + { + "type": "text", + "version": 400, + "versionNonce": 747588289, + "isDeleted": false, + "id": "rD_cbOE0Mwz-sfB0YqcaJ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 494.501953125, + "y": 449.6064453125, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 88, + "height": 25, + "seed": 68109103, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660298172011, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Sensor 2", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Sensor 2" + }, + { + "type": "arrow", + "version": 1150, + "versionNonce": 1550306895, + "isDeleted": false, + "id": "HZoAI_wR8CyRR9fVaFwSl", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 666.49609375, + "y": 532.2655116636315, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 145.8515625, + "height": 133.46442554799017, + "seed": 1409289601, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1660298248381, + "link": null, + "locked": false, + "startBinding": { + "elementId": "pHhCP3DIU5fE4v3yi3obG", + "focus": 0.7718436212944663, + "gap": 23.818359375 + }, + "endBinding": { + "elementId": "ox1Blt2bzl0onmQfB7ZAN", + "focus": 0.28922965891088237, + "gap": 15.3515625 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 145.8515625, + -133.46442554799017 + ] + ] + }, + { + "id": "feq5Rn2_gEHIfIzRPvNu6", + "type": "rectangle", + "x": 832.283203125, + "y": 515.0556640625, + "width": 255, + "height": 76.1171875, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 826152751, + "version": 65, + "versionNonce": 19459567, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "n4_rv3VhjyMXLSFrP52Vw" + }, + { + "id": "vJNRjoY0dZcqOBw5RzuHn", + "type": "arrow" + } + ], + "updated": 1660298281790, + "link": null, + "locked": false + }, + { + "id": "n4_rv3VhjyMXLSFrP52Vw", + "type": "text", + "x": 837.283203125, + "y": 540.6142578125, + "width": 245, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 555916079, + "version": 20, + "versionNonce": 1538604143, + "isDeleted": false, + "boundElements": null, + "updated": 1660298276528, + "link": null, + "locked": false, + "text": "Database or logs", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 18, + "containerId": "feq5Rn2_gEHIfIzRPvNu6", + "originalText": "Database or logs" + }, + { + "id": "vJNRjoY0dZcqOBw5RzuHn", + "type": "arrow", + "x": 958.509765625, + "y": 472.4150390625, + "width": 0, + "height": 39.33984375, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1036508641, + "version": 20, + "versionNonce": 2130978977, + "isDeleted": false, + "boundElements": null, + "updated": 1660298281790, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 39.33984375 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "rjHz2X8U0ZDEyckj_tTSw", + "focus": -0.04979978015075378, + "gap": 25.2548828125 + }, + "endBinding": { + "elementId": "feq5Rn2_gEHIfIzRPvNu6", + "focus": -0.009987745098039217, + "gap": 3.30078125 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/docs/assets/client-sensors.webp b/docs/assets/client-sensors.webp new file mode 100644 index 000000000..c827fb51b Binary files /dev/null and b/docs/assets/client-sensors.webp differ diff --git a/docs/assets/server-grafana.excalidraw b/docs/assets/server-grafana.excalidraw new file mode 100644 index 000000000..96e48b7ca --- /dev/null +++ b/docs/assets/server-grafana.excalidraw @@ -0,0 +1,488 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "version": 938, + "versionNonce": 1589965569, + "isDeleted": false, + "id": "ox1Blt2bzl0onmQfB7ZAN", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 471.66796875, + "y": 61.640625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 251.7109375, + "height": 346.86328125, + "seed": 1508024704, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "sE5xq9Fz5VDTWcJGhJizg", + "type": "arrow" + }, + { + "id": "RdDFVItfRo8k8NarDHSp-", + "type": "arrow" + } + ], + "updated": 1660300922199, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1153, + "versionNonce": 977576399, + "isDeleted": false, + "id": "rjHz2X8U0ZDEyckj_tTSw", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 498.0234375, + "y": 138.1171875, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 187, + "height": 240, + "seed": 2100367744, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660300922199, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 1, + "text": "Collect data from CPU,\nIO and memory with\ntimestamp\n\n0: 0.1 %\n1: 1456 b/s\n2: 4567 b\n3: 1660190233 (time)\n...\n\n100: false (alert)\n", + "baseline": 234, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Collect data from CPU,\nIO and memory with\ntimestamp\n\n0: 0.1 %\n1: 1456 b/s\n2: 4567 b\n3: 1660190233 (time)\n...\n\n100: false (alert)\n" + }, + { + "type": "rectangle", + "version": 772, + "versionNonce": 1558709263, + "isDeleted": false, + "id": "mDgu1gSg34HbU-NAHPB30", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 896.8984375, + "y": 44.853515625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 216.16406250000006, + "height": 172.06640624999997, + "seed": 1336837760, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "sE5xq9Fz5VDTWcJGhJizg", + "type": "arrow" + } + ], + "updated": 1660300917063, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 607, + "versionNonce": 747285039, + "isDeleted": false, + "id": "F6UUk6_B6uALmjxcHLYw4", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 964.88671875, + "y": 70.1953125, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 84, + "height": 25, + "seed": 354006656, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660300917063, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Grafana", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Grafana" + }, + { + "type": "text", + "version": 556, + "versionNonce": 1932158625, + "isDeleted": false, + "id": "v7q2uvVFHZBvjr-y_ZJKl", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 528.5234375, + "y": 89.28515625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 159, + "height": 25, + "seed": 2108436864, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660300922199, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "libmodbus server", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "libmodbus server" + }, + { + "type": "arrow", + "version": 2401, + "versionNonce": 1251011297, + "isDeleted": false, + "id": "sE5xq9Fz5VDTWcJGhJizg", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 748.0898437500001, + "y": 161.19331310212854, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 134.5039062499999, + "height": 30.970574872077407, + "seed": 455209344, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1660300922199, + "link": null, + "locked": false, + "startBinding": { + "elementId": "ox1Blt2bzl0onmQfB7ZAN", + "gap": 24.710937500000114, + "focus": -0.19349510698149744 + }, + "endBinding": { + "elementId": "mDgu1gSg34HbU-NAHPB30", + "gap": 14.3046875, + "focus": 0.2600477394392373 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 134.5039062499999, + -30.970574872077407 + ] + ] + }, + { + "type": "text", + "version": 1184, + "versionNonce": 1589324399, + "isDeleted": false, + "id": "Q6P32mRyop5JlKGPB3tei", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 6.077759617018872, + "x": 746.0369822154072, + "y": 102.90361683493032, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 111, + "height": 29, + "seed": 1091054019, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660300917063, + "link": null, + "locked": false, + "fontSize": 11.542968749999993, + "fontFamily": 1, + "text": "TCP read requests\n(polling)", + "baseline": 25, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "TCP read requests\n(polling)" + }, + { + "id": "ViVqW_nxoEJO1DpluuMzF", + "type": "image", + "x": 923.3162172379032, + "y": 116.828369140625, + "width": 163.32850302419354, + "height": 63.289794921875, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 953641007, + "version": 332, + "versionNonce": 1122873217, + "isDeleted": false, + "boundElements": null, + "updated": 1660300917064, + "link": null, + "locked": false, + "status": "saved", + "fileId": "fe56123c11422301d020f581b74d4397ab49e99c", + "scale": [ + 1, + 1 + ] + }, + { + "id": "RdDFVItfRo8k8NarDHSp-", + "type": "arrow", + "x": 898.5878906250001, + "y": 340.31123325850484, + "width": 158.6875000000001, + "height": 28.44727928318008, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 595270337, + "version": 1096, + "versionNonce": 1842538177, + "isDeleted": false, + "boundElements": null, + "updated": 1660300922199, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -158.6875000000001, + -28.44727928318008 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "ro1CNQcmtpkib2r-uhEJo", + "gap": 10.179687499999886, + "focus": -0.37043558235421187 + }, + "endBinding": { + "elementId": "ox1Blt2bzl0onmQfB7ZAN", + "gap": 16.521484375, + "focus": 0.2615821499314503 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "ro1CNQcmtpkib2r-uhEJo", + "type": "rectangle", + "x": 908.767578125, + "y": 248.8369140625, + "width": 216.01953125, + "height": 154, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1261656367, + "version": 298, + "versionNonce": 866798383, + "isDeleted": false, + "boundElements": [ + { + "id": "RdDFVItfRo8k8NarDHSp-", + "type": "arrow" + }, + { + "type": "text", + "id": "zWdPKja_yF9g4yVY1kvXH" + } + ], + "updated": 1660300917064, + "link": null, + "locked": false + }, + { + "id": "zWdPKja_yF9g4yVY1kvXH", + "type": "text", + "x": 913.767578125, + "y": 253.8369140625, + "width": 206, + "height": 75, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 76196961, + "version": 380, + "versionNonce": 1800831809, + "isDeleted": false, + "boundElements": null, + "updated": 1660300917064, + "link": null, + "locked": false, + "text": "\nTriggers alert when\nservice is down", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 68, + "containerId": "ro1CNQcmtpkib2r-uhEJo", + "originalText": "\nTriggers alert when\nservice is down" + }, + { + "type": "text", + "version": 1386, + "versionNonce": 2109148495, + "isDeleted": false, + "id": "DZ2rUAdMymO7ajB4Xr_Kf", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0.17070993938211565, + "x": 774.3998831194928, + "y": 298.59613347989244, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 107, + "height": 15, + "seed": 109244929, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660300917064, + "link": null, + "locked": false, + "fontSize": 11.542968749999993, + "fontFamily": 1, + "text": "TCP write request", + "baseline": 10, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "TCP write request" + }, + { + "id": "Os6j9M5Ipt4Ay74nFj29L", + "type": "text", + "x": 937.599609375, + "y": 348.8330078125, + "width": 163, + "height": 30, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 577132609, + "version": 307, + "versionNonce": 654734113, + "isDeleted": false, + "boundElements": null, + "updated": 1660300917064, + "link": null, + "locked": false, + "text": "Write True to address 100\non issue", + "fontSize": 12.1, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 26, + "containerId": null, + "originalText": "Write True to address 100\non issue" + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": { + "fe56123c11422301d020f581b74d4397ab49e99c": { + "mimeType": "image/jpeg", + "id": "fe56123c11422301d020f581b74d4397ab49e99c", + "dataURL": "data:image/jpeg;base64,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", + "created": 1660300358240 + } + } +} \ No newline at end of file diff --git a/docs/assets/server-grafana.webp b/docs/assets/server-grafana.webp new file mode 100644 index 000000000..636dc3a6f Binary files /dev/null and b/docs/assets/server-grafana.webp differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 000000000..6308d10ef --- /dev/null +++ b/docs/index.md @@ -0,0 +1,294 @@ +# libmodbus + +A featureful and portable Open Source Modbus library. + +## Description + +libmodbus is a library to send/receive data with a device which respects the +Modbus protocol. This library contains various backends to communicate over +different networks (eg. serial in RTU mode or Ethernet in TCP IPv4/IPv6). The + site provides documentation about the [Modbus +Specifications and Implementation Guides](http://www.modbus.org/specs.php). + +libmodbus provides an abstraction of the lower communication layers and offers +the same API on all supported platforms. + +This documentation presents an overview of libmodbus concepts, describes how +libmodbus abstracts Modbus communication with different hardware and platforms +and provides a reference manual for the functions provided by the libmodbus +library. + +## Use cases + +The library can be used to write a: + +- **client**, the application reads/writes data from various devices. +- **server**, the application provides data to several clients. + +
+ +
A libmodbus client that reads only the temperatures from sensors.
+
+ +
+ +
A libmodbus server that exposes data to a Grafana service.
+
+ +## Contexts + +The Modbus protocol supports several transport protocols (eg. serial RTU, +Ethernet TCP) called backends in *libmodbus*. + +The first step is to allocate and set a `modbus_t` context according to the +required backend (RTU or TCP) with a dedicated function, such as +[modbus_new_rtu](modbus_new_rtu.md). +The function will return an opaque structure called `modbus_t` containing all +necessary information to establish a connection with other Modbus devices +according to the selected backend. + +Once this context has been created, you can use use the common API provided by +*libmodbus* to read/write or set the various timeouts. With this common API, +it's easy to switch the backend of your application from RTU to TCP IPv6 for +example. + +### RTU Context + +The RTU backend (Remote Terminal Unit) is used in serial communication and makes +use of a compact, binary representation of the data for protocol communication. +The RTU format follows the commands/data with a cyclic redundancy check checksum +as an error check mechanism to ensure the reliability of data. Modbus RTU is the +most common implementation available for Modbus. A Modbus RTU message must be +transmitted continuously without inter-character hesitations (extract from +Wikipedia, [Modbus](http://en.wikipedia.org/wiki/Modbus) as of Mar. 13, 2011, +20:51 GMT). + +The Modbus RTU framing calls a slave, a device/service which handle Modbus +requests, and a master, a client which send requests. The communication is +always initiated by the master. + +Many Modbus devices can be connected together on the same physical link so +before sending a message, you must set the slave (receiver) with +[modbus_set_slave](modbus_set_slave.md). If you're running a slave, its slave number +will be used to filter received messages. + +The libmodbus implementation of RTU isn't time based as stated in original +Modbus specification, instead all bytes are sent as fast as possible and a +response or an indication is considered complete when all expected characters +have been received. This implementation offers very fast communication but you +must take care to set a response timeout of slaves less than response timeout of +master (otherwise other slaves may ignore master requests when one of the slaves +is not responding). + +To create a Modbus RTU context, you should use [modbus_new_rtu](modbus_new_rtu.md). + +You can tweak the serial mode with the following functions: + +- [modbus_rtu_get_serial_mode](modbus_rtu_get_serial_mode.md) +- [modbus_rtu_set_serial_mode](modbus_rtu_set_serial_mode.md) +- [modbus_rtu_get_rts](modbus_rtu_get_rts.md) +- [modbus_rtu_set_rts](modbus_rtu_set_rts.md) +- [modbus_rtu_set_custom_rts](modbus_rtu_set_custom_rts.md) +- [modbus_rtu_get_rts_delay](modbus_rtu_get_rts_delay.md) +- [modbus_rtu_set_rts_delay](modbus_rtu_set_rts_delay.md) + +### TCP (IPv4) Context + +The TCP backend implements a Modbus variant used for communications over +TCP/IPv4 networks. It does not require a checksum calculation as lower layer +takes care of the same. + +To create a Modbus TCP context, you should use [modbus_new_tcp](modbus_new_tcp.md). + +### TCP PI (IPv4 and IPv6) Context + +The TCP PI (Protocol Independent) backend implements a Modbus variant used for +communications over TCP IPv4 and IPv6 networks. It does not require a checksum +calculation as lower layer takes care of the same. + +Contrary to the TCP IPv4 only backend, the TCP PI backend offers hostname +resolution. + +Create a Modbus TCP PI context, you should use [modbus_new_tcp_pi](modbus_new_tcp_pi.md). + +## Connection + +The following functions are provided to establish and close a connection with +Modbus devices: + +- [modbus_connect](modbus_connect.md) establishes a connection. +- [modbus_close](modbus_close.md) closes a connection. +- [modbus_flush](modbus_flush.md) flushes a connection. + +In RTU, you should define the slave ID of your client with +[modbus_set_slave](modbus_set_slave.md). + +To analyse the exchanged data, you can enable the debug mode with +[modbus_set_debug](modbus_set_debug.md). + +Once you have completed the communication or at the end of your program, you +should free the resources with the common function, [modbus_free](modbus_free.md) + +## Reads and writes from the client + +The Modbus protocol defines different data types and functions to read and write +them from/to remote devices. The following functions are used by the clients to +send Modbus requests: + +To read data: + +- [modbus_read_bits](modbus_read_bits.md) +- [modbus_read_input_bits](modbus_read_input_bits.md) +- [modbus_read_registers](modbus_read_registers.md) +- [modbus_read_input_registers](modbus_read_input_registers.md) +- [modbus_report_slave_id](modbus_report_slave_id.md) + +To write data: + +- [modbus_write_bit](modbus_write_bit.md) +- [modbus_write_register](modbus_write_register.md) +- [modbus_write_bits](modbus_write_bits.md) +- [modbus_write_registers](modbus_write_registers.md) + +To write and read data in a single operation: + +- [modbus_write_and_read_registers](modbus_write_and_read_registers.md) + +To send and receive low-level requests: + +- [modbus_send_raw_request](modbus_send_raw_request.md) +- [modbus_send_raw_request_tid](modbus_send_raw_request_tid.md) +- [modbus_receive_confirmation](modbus_receive_confirmation.md) + +To reply to an exception: + +- [modbus_reply_exception](modbus_reply_exception.md) + +## Handling requests from server + +The server is waiting for request from clients and must answer when it is +concerned by the request. The libmodbus offers the following functions to +handle requests: + +Data mapping: + +- [modbus_mapping_new](modbus_mapping_new.md) +- [modbus_mapping_free](modbus_mapping_free.md) + +Receive: + +- [modbus_receive](modbus_receive.md) + +Reply: + +- [modbus_reply](modbus_reply.md) +- [modbus_reply_exception](modbus_reply_exception.md) + +Proxy: + +- [modbus_proxy](modbus_proxy.md) + +## Advanced functions + +Timeout settings: + +- [modbus_get_byte_timeout](modbus_get_byte_timeout.md) +- [modbus_set_byte_timeout](modbus_set_byte_timeout.md) +- [modbus_get_response_timeout](modbus_get_response_timeout.md) +- [modbus_set_response_timeout](modbus_set_response_timeout.md) + +Error recovery mode: + +- [modbus_set_error_recovery](modbus_set_error_recovery.md) + +Setter/getter of internal socket: + +- [modbus_set_socket](modbus_set_socket.md) +- [modbus_get_socket](modbus_get_socket.md) + +Information about header: + +- [modbus_get_header_length](modbus_get_header_length.md) + +## Data handling + +Macros for data manipulation: + +- `MODBUS_GET_HIGH_BYTE(data)`, extracts the high byte from a byte +- `MODBUS_GET_LOW_BYTE(data)`, extracts the low byte from a byte +- `MODBUS_GET_INT64_FROM_INT16(tab_int16, index)`, builds a signed int64 from the four first int16 starting at tab_int16[index] +- `MODBUS_GET_INT32_FROM_INT16(tab_int16, index)`, builds a signed int32 from the two first int16 starting at tab_int16[index] +- `MODBUS_GET_INT16_FROM_INT8(tab_int8, index)`, builds a signed int16 from the two first int8 starting at tab_int8[index] +- `MODBUS_SET_INT16_TO_INT8(tab_int8, index, value)`, set an int16 value into the two first bytes starting at tab_int8[index] +- `MODBUS_SET_INT32_TO_INT16(tab_int16, index, value)`, set an int32 value into the two first int16 starting at tab_int16[index] +- `MODBUS_SET_INT64_TO_INT16(tab_int16, index, value)`, set an int64 value into the four first int16 starting at tab_int16[index] + +The `MODBUS_GET_INT*_FROM_INT*` macros return signed integers: an input whose +most significant bit is set is returned as a negative value. Since 3.2.0, +`MODBUS_GET_INT16_FROM_INT8()` returns a proper `int16_t` in every context; a +value `>= 0x8000` is therefore negative. Previously it could evaluate to a +positive `int` when the result was used outside a 16-bit assignment. + +Handling of bits and bytes: + +- [modbus_set_bits_from_byte](modbus_set_bits_from_byte.md) +- [modbus_set_bits_from_bytes](modbus_set_bits_from_bytes.md) +- [modbus_get_byte_from_bits](modbus_get_byte_from_bits.md) + +Set or get float numbers: + +- [modbus_get_float_abcd](modbus_get_float_abcd.md) +- [modbus_set_float_abcd](modbus_set_float_abcd.md) +- [modbus_get_float_badc](modbus_get_float_badc.md) +- [modbus_set_float_badc](modbus_set_float_badc.md) +- [modbus_get_float_cdab](modbus_get_float_cdab.md) +- [modbus_set_float_cdab](modbus_set_float_cdab.md) +- [modbus_get_float_dcba](modbus_get_float_dcba.md) +- [modbus_set_float_dcba](modbus_set_float_dcba.md) +- [modbus_get_float](modbus_get_float.md) **deprecated** +- [modbus_set_float](modbus_set_float.md) **deprecated** + +## Error handling + +The libmodbus functions handle errors using the standard conventions found on +POSIX systems. Generally, this means that upon failure a libmodbus function +shall return either a NULL value (if returning a pointer) or a negative value +(if returning an integer), and the actual error code shall be stored in the +`errno` variable. + +This includes cases where a Modbus server returns an exception response as a +result of a failed read or write operation. The following macros expand to +integer error codes that correspond to Modbus exception responses: + +- `EMBXILFUN`: illegal function (0x01) +- `EMBXILADD`: illegal data address (0x02) +- `EMBXILVAL`: illegal data value (0x03) +- `EMBXSFAIL`: server device failure (0x04) +- `EMBXACK`: acknowledge (0x05) +- `EMBXSBUSY`: server device busy (0x06) +- `EMBXNACK`: negative acknowledge (0x07) +- `EMBXMEMPAR`: memory parity error (0x08) +- `EMBXGPATH`: gateway path unavailable (0x0A) +- `EMBXGTAR`: gateway target device failed to respond (0x0B) + +The *modbus_strerror()* function is provided to translate libmodbus-specific +error codes into error message strings; for details refer to +[modbus_strerror](modbus_strerror.md). + +## Miscellaneous + +To deviate from the Modbus standard, you can enable or disable quirks with: + +- [modbus_disable_quirks](modbus_disable_quirks.md) +- [modbus_enable_quirks](modbus_enable_quirks.md) + +The `_LIBMODBUS_VERSION_STRING_` constant indicates the libmodbus version the +program has been compiled against. The variables 'libmodbus_version_major', +'libmodbus_version_minor', 'libmodbus_version_micro' give the version the +program is linked against. + +## Copying + +Free use of this software is granted under the terms of the GNU Lesser General +Public License (LGPL v2.1+). For details see the file `COPYING.LESSER` included +with the libmodbus distribution. diff --git a/doc/modbus_close.txt b/docs/modbus_close.md similarity index 53% rename from doc/modbus_close.txt rename to docs/modbus_close.md index 5a0afc0fd..05cc080eb 100644 --- a/doc/modbus_close.txt +++ b/docs/modbus_close.md @@ -1,32 +1,27 @@ -modbus_close(3) -=============== +# modbus_close +## Name -NAME ----- modbus_close - close a Modbus connection +## Synopsis -SYNOPSIS --------- -*void modbus_close(modbus_t *'ctx');* +```c +void modbus_close(modbus_t *ctx); +``` +## Description -DESCRIPTION ------------ The *modbus_close()* function shall close the connection established with the backend set in the context. +## Return value -RETURN VALUE ------------- There is no return value. +## Example -EXAMPLE -------- -[source,c] -------------------- +```c modbus_t *ctx; ctx = modbus_new_tcp("127.0.0.1", 502); @@ -38,14 +33,8 @@ if (modbus_connect(ctx) == -1) { modbus_close(ctx); modbus_free(ctx); -------------------- +``` -SEE ALSO --------- -linkmb:modbus_connect[3] +## See also - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - +- [modbus_connect](modbus_connect.md) diff --git a/doc/modbus_connect.txt b/docs/modbus_connect.md similarity index 61% rename from doc/modbus_connect.txt rename to docs/modbus_connect.md index a4437ec96..541d9d982 100644 --- a/doc/modbus_connect.txt +++ b/docs/modbus_connect.md @@ -1,35 +1,30 @@ -modbus_connect(3) -================= +# modbus_connect +## Name -NAME ----- modbus_connect - establish a Modbus connection +## Synopsis -SYNOPSIS --------- -*int modbus_connect(modbus_t *'ctx');* +```c +int modbus_connect(modbus_t *ctx); +``` +## Description -DESCRIPTION ------------ The *modbus_connect()* function shall establish a connection to a Modbus server, a network or a bus using the context information of libmodbus context given in argument. +## Return value -RETURN VALUE ------------- The function shall return 0 if successful. Otherwise it shall return -1 and set errno to one of the values defined by the system calls of the underlying platform. +## Example -EXAMPLE -------- -[source,c] -------------------- +```c modbus_t *ctx; ctx = modbus_new_tcp("127.0.0.1", 502); @@ -38,15 +33,8 @@ if (modbus_connect(ctx) == -1) { modbus_free(ctx); return -1; } -------------------- +``` +## See also -SEE ALSO --------- -linkmb:modbus_close[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - +- [modbus_close](modbus_close.md) diff --git a/docs/modbus_disable_quirks.md b/docs/modbus_disable_quirks.md new file mode 100644 index 000000000..8942e66a4 --- /dev/null +++ b/docs/modbus_disable_quirks.md @@ -0,0 +1,37 @@ +# modbus_disable_quirks + +## Name + +modbus_disable_quirks - disable a list of quirks according to a mask + +## Synopsis + +```c +int modbus_disable_quirks(modbus_t *ctx, unsigned int quirks_mask); +``` + +## Description + +The function shall disable the quirks according to the provided mask. It's +useful to revert changes applied by a previous call to +[modbus_enable_quirks](modbus_enable_quirks.md) + +To reset all quirks, you can use the specific value `MODBUS_QUIRK_ALL`. + +```c +modbus_enable_quirks(ctx, MODBUS_QUIRK_MAX_SLAVE | MODBUS_QUIRK_REPLY_TO_BROADCAST); + +... + +// Reset all quirks +modbus_disable_quirks(ctx, MODBUS_QUIRK_ALL); +``` + +## Return value + +The function shall return 0 if successful. Otherwise it shall return -1 and set +errno. + +## See also + +- [modbus_enable_quirks](modbus_enable_quirks.md) diff --git a/docs/modbus_enable_quirks.md b/docs/modbus_enable_quirks.md new file mode 100644 index 000000000..fb70bc217 --- /dev/null +++ b/docs/modbus_enable_quirks.md @@ -0,0 +1,35 @@ +# modbus_enable_quirks + +## Name + +modbus_enable_quirks - enable a list of quirks according to a mask + +## Synopsis + +```c +int modbus_enable_quirks(modbus_t *ctx, unsigned int quirks_mask); +``` + +## Description + +The function is only useful when you are confronted with equipment which does +not respect the protocol, which behaves strangely or when you wish to move away +from the standard. + +In that case, you can enable a specific quirk to workaround the issue, libmodbus +offers the following flags: + +- `MODBUS_QUIRK_MAX_SLAVE` allows slave addresses between 247 and 255. +- `MODBUS_QUIRK_REPLY_TO_BROADCAST` force a reply to a broadcast request when the + device is a slave in RTU mode (should be enabled on the slave device). + +You can combine the flags by using the bitwise OR operator. + +## Return value + +The function shall return 0 if successful. Otherwise it shall return -1 and set +errno. + +## See also + +- [modbus_disable_quirks](modbus_disable_quirks.md) diff --git a/doc/modbus_flush.txt b/docs/modbus_flush.md similarity index 54% rename from doc/modbus_flush.txt rename to docs/modbus_flush.md index f4f9b166c..5a9f09be6 100644 --- a/doc/modbus_flush.txt +++ b/docs/modbus_flush.md @@ -1,30 +1,21 @@ -modbus_flush(3) -=============== +# modbus_flush +## Name -NAME ----- modbus_flush - flush non-transmitted data +## Synopsis -SYNOPSIS --------- -*int modbus_flush(modbus_t *'ctx');* +```c +int modbus_flush(modbus_t *ctx); +``` +## Description -DESCRIPTION ------------ The *modbus_flush()* function shall discard data received but not read to the socket or file descriptor associated to the context 'ctx'. +## Return value -RETURN VALUE ------------- The function shall return 0 or the number of flushed bytes if successful. Otherwise it shall return -1 and set errno. - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/docs/modbus_free.md b/docs/modbus_free.md new file mode 100644 index 000000000..82bf5c8a4 --- /dev/null +++ b/docs/modbus_free.md @@ -0,0 +1,19 @@ +# modbus_free + +## Name + +modbus_free - free a libmodbus context + +## Synopsis + +```c +void modbus_free(modbus_t *ctx); +``` + +## Description + +The *modbus_free()* function shall free an allocated *modbus_t* structure. + +## Return value + +There is no return value. diff --git a/docs/modbus_get_byte_from_bits.md b/docs/modbus_get_byte_from_bits.md new file mode 100644 index 000000000..d68a5bb2d --- /dev/null +++ b/docs/modbus_get_byte_from_bits.md @@ -0,0 +1,26 @@ +# modbus_get_byte_from_bits + +## Name + +modbus_get_byte_from_bits - get the value from many bits + +## Synopsis + +```c +uint8_t modbus_get_byte_from_bits(const uint8_t *src, int index, unsigned int nb_bits); +``` + +## Description + +The *modbus_get_byte_from_bits()* function shall extract a value from many +bits. All `nb_bits` bits from `src` at position `index` will be read as a +single value. To obtain a full byte, set `nb_bits` to 8. + +## Return value + +The function shall return a byte containing the bits read. + +## See also + +- [modbus_set_bits_from_byte](modbus_set_bits_from_byte.md) +- [modbus_set_bits_from_bytes](modbus_set_bits_from_bytes.md) diff --git a/docs/modbus_get_byte_timeout.md b/docs/modbus_get_byte_timeout.md new file mode 100644 index 000000000..b9a029b0f --- /dev/null +++ b/docs/modbus_get_byte_timeout.md @@ -0,0 +1,42 @@ +# modbus_get_byte_timeout + +## Name + +modbus_get_byte_timeout - get timeout between bytes + +## Synopsis + +```c +int modbus_get_byte_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec); +``` + +## Description + +The *modbus_get_byte_timeout()* function shall store the timeout interval +between two consecutive bytes of the same message in the `to_sec` and `to_usec` +arguments. + +## Return value + +The function shall return 0 if successful. Otherwise it shall return -1 and set +errno. + +## Errors + +- *EINVAL*, the context is NULL or one of `to_sec` / `to_usec` is NULL. + +## Example + +```c +uint32_t to_sec; +uint32_t to_usec; + +/* Save original timeout */ +modbus_get_byte_timeout(ctx, &to_sec, &to_usec); +``` + +## See also + +- [modbus_set_byte_timeout](modbus_set_byte_timeout.md) +- [modbus_get_response_timeout](modbus_get_response_timeout.md) +- [modbus_set_response_timeout](modbus_set_response_timeout.md) diff --git a/docs/modbus_get_float.md b/docs/modbus_get_float.md new file mode 100644 index 000000000..fb0d9a9a0 --- /dev/null +++ b/docs/modbus_get_float.md @@ -0,0 +1,31 @@ +# modbus_get_float + +## Name + +modbus_get_float - get a float value from 2 registers + +## Synopsis + +```c +float modbus_get_float(const uint16_t *src); +``` + +Warning, this function is *deprecated* since libmodbus v3.2.0 and has been +replaced by *modbus_get_float_dcba()*. + +## Description + +The *modbus_get_float()* function shall get a float from 4 bytes in Modbus +format (DCBA byte order). The `src` array must be a pointer on two 16 bits +values, for example, if the first word is set to 0x4465 and the second to +0x229a, the float value will be 916.540649. + +## Return value + +The function shall return a float. + +## See also + +- [modbus_set_float](modbus_set_float.md) +- [modbus_set_float_dcba](modbus_set_float_dcba.md) +- [modbus_get_float_dcba](modbus_get_float_dcba.md) diff --git a/docs/modbus_get_float_abcd.md b/docs/modbus_get_float_abcd.md new file mode 100644 index 000000000..f31e9a555 --- /dev/null +++ b/docs/modbus_get_float_abcd.md @@ -0,0 +1,29 @@ +# modbus_get_float_abcd + +## Name + +modbus_get_float_abcd - get a float value from 2 registers in ABCD byte order + +## Synopsis + +```c +float modbus_get_float_abcd(const uint16_t *src); +``` + +## Description + +The *modbus_get_float_abcd()* function shall get a float from 4 bytes in usual +Modbus format. The `src` array must be a pointer on two 16 bits values, for +example, if the first word is set to 0x47F1 and the second to 0x2000, the float +value will be read as 123456.0. + +## Return value + +The function shall return a float. + +## See also + +- [modbus_set_float_abcd](modbus_set_float_abcd.md) +- [modbus_get_float_badc](modbus_get_float_badc.md) +- [modbus_get_float_cdab](modbus_get_float_cdab.md) +- [modbus_get_float_dcba](modbus_get_float_dcba.md) diff --git a/docs/modbus_get_float_badc.md b/docs/modbus_get_float_badc.md new file mode 100644 index 000000000..a3e64f523 --- /dev/null +++ b/docs/modbus_get_float_badc.md @@ -0,0 +1,29 @@ +# modbus_get_float_badc + +## Name + +modbus_get_float_badc - get a float value from 2 registers in BADC byte order + +## Synopsis + +```c +float modbus_get_float_badc(const uint16_t *src); +``` + +## Description + +The *modbus_get_float_badc()* function shall get a float from 4 bytes with +swapped bytes (BADC instead of ABCD). The `src` array must be a pointer on two +16 bits values, for example, if the first word is set to 0xF147 and the second +to 0x0020, the float value will be read as 123456.0. + +## Return value + +The function shall return a float. + +## See also + +- [modbus_set_float_badc](modbus_set_float_badc.md) +- [modbus_get_float_abcd](modbus_get_float_abcd.md) +- [modbus_get_float_cdab](modbus_get_float_cdab.md) +- [modbus_get_float_dcba](modbus_get_float_dcba.md) diff --git a/docs/modbus_get_float_cdab.md b/docs/modbus_get_float_cdab.md new file mode 100644 index 000000000..8e168a6fb --- /dev/null +++ b/docs/modbus_get_float_cdab.md @@ -0,0 +1,29 @@ +# modbus_get_float_cdab + +## Name + +modbus_get_float_cdab - get a float value from 2 registers in CDAB byte order + +## Synopsis + +```c +float modbus_get_float_cdab(const uint16_t *src); +``` + +## Description + +The *modbus_get_float_cdab()* function shall get a float from 4 bytes with +swapped words (CDAB order instead of ABCD). The `src` array must be a pointer on +two 16 bits values, for example, if the first word is set to 0x2000 and the second +to 0x47F1, the float value will be read as 123456.0. + +## Return value + +The function shall return a float. + +## See also + +- [modbus_set_float_cdab](modbus_set_float_cdab.md) +- [modbus_get_float_abcd](modbus_get_float_abcd.md) +- [modbus_get_float_badc](modbus_get_float_badc.md) +- [modbus_get_float_dcba](modbus_get_float_dcba.md) diff --git a/docs/modbus_get_float_dcba.md b/docs/modbus_get_float_dcba.md new file mode 100644 index 000000000..bcc6928a6 --- /dev/null +++ b/docs/modbus_get_float_dcba.md @@ -0,0 +1,29 @@ +# modbus_get_float_dcba + +## Name + +modbus_get_float_dcba - get a float value from 2 registers in DCBA byte order + +## Synopsis + +```c +float modbus_get_float_dcba(const uint16_t *src); +``` + +## Description + +The *modbus_get_float_dcba()* function shall get a float from 4 bytes in +inverted Modbus format (DCBA order instead of ABCD). The `src` array must be a +pointer on two 16 bits values, for example, if the first word is set to 0x0020 +and the second to 0xF147, the float value will be read as 123456.0. + +## Return value + +The function shall return a float. + +## See also + +- [modbus_set_float_dcba](modbus_set_float_dcba.md) +- [modbus_get_float_abcd](modbus_get_float_abcd.md) +- [modbus_get_float_badc](modbus_get_float_badc.md) +- [modbus_get_float_cdab](modbus_get_float_cdab.md) diff --git a/docs/modbus_get_header_length.md b/docs/modbus_get_header_length.md new file mode 100644 index 000000000..27a8af562 --- /dev/null +++ b/docs/modbus_get_header_length.md @@ -0,0 +1,21 @@ +# modbus_get_header_length + +## Name + +modbus_get_header_length - retrieve the current header length + +## Synopsis + +```c +int modbus_get_header_length(modbus_t *ctx); +``` + +## Description + +The *modbus_get_header_length()* function shall retrieve the current header +length from the backend. This function is convenient to manipulate a message and +so it's limited to low-level operations. + +## Return value + +The header length as integer value. diff --git a/docs/modbus_get_indication_timeout.md b/docs/modbus_get_indication_timeout.md new file mode 100644 index 000000000..be8103d93 --- /dev/null +++ b/docs/modbus_get_indication_timeout.md @@ -0,0 +1,43 @@ +# modbus_get_indication_timeout + +## Name + +modbus_get_indication_timeout - get timeout used to wait for an indication (request received by a server). + +## Synopsis + +```c +int modbus_get_indication_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec); +``` + +## Description + +The *modbus_get_indication_timeout()* function shall store the timeout interval +used to wait for an indication in the `to_sec` and `to_usec` arguments. +Indication is the term used by the Modbus protocol to designate a request +received by the server. + +The default value is zero, it means the server will wait forever. + +## Return value + +The function shall return 0 if successful. Otherwise it shall return -1 and set +errno. + +```c +uint32_t to_sec; +uint32_t to_usec; + +/* Save original timeout */ +modbus_get_indication_timeout(ctx, &to_sec, &to_usec); +``` + +## Errors + +- *EINVAL*, the context is NULL or one of `to_sec` / `to_usec` is NULL. + +## See also + +- [modbus_set_indication_timeout](modbus_set_indication_timeout.md) +- [modbus_get_response_timeout](modbus_get_response_timeout.md) +- [modbus_set_response_timeout](modbus_set_response_timeout.md) diff --git a/docs/modbus_get_response_timeout.md b/docs/modbus_get_response_timeout.md new file mode 100644 index 000000000..c97407a40 --- /dev/null +++ b/docs/modbus_get_response_timeout.md @@ -0,0 +1,44 @@ +# modbus_get_response_timeout + +## Name + +modbus_get_response_timeout - get timeout for response + +## Synopsis + +```c +int modbus_get_response_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec); +``` + +## Description + +The *modbus_get_response_timeout()* function shall return the timeout interval +used to wait for a response in the `to_sec` and `to_usec` arguments. + +## Return value + +The function shall return 0 if successful. Otherwise it shall return -1 and set +errno. + +Example: + +```c +uint32_t old_response_to_sec; +uint32_t old_response_to_usec; + +/* Save original timeout */ +modbus_get_response_timeout(ctx, &old_response_to_sec, &old_response_to_usec); + +/* Define a new and too short timeout! */ +modbus_set_response_timeout(ctx, 0, 0); +``` + +## Errors + +- *EINVAL*, the context is NULL or one of `to_sec` / `to_usec` is NULL. + +## See also + +- [modbus_set_response_timeout](modbus_set_response_timeout.md) +- [modbus_get_byte_timeout](modbus_get_byte_timeout.md) +- [modbus_set_byte_timeout](modbus_set_byte_timeout.md) diff --git a/docs/modbus_get_slave.md b/docs/modbus_get_slave.md new file mode 100644 index 000000000..4e7a95859 --- /dev/null +++ b/docs/modbus_get_slave.md @@ -0,0 +1,29 @@ +# modbus_get_slave + +## Name + +modbus_get_slave - get slave number in the context + +## Synopsis + +```c +int modbus_get_slave(modbus_t *ctx); +``` + +## Description + +The *modbus_get_slave()* function shall get the slave number in the libmodbus +context. + +## Return value + +The function shall return the slave number if successful. Otherwise it shall +return -1 and set errno to one of the values defined below. + +## Errors + +- *EINVAL*, the libmodbus context is undefined. + +## See also + +- [modbus_set_slave](modbus_set_slave.md) diff --git a/docs/modbus_get_socket.md b/docs/modbus_get_socket.md new file mode 100644 index 000000000..a87b5a2f0 --- /dev/null +++ b/docs/modbus_get_socket.md @@ -0,0 +1,25 @@ +# modbus_get_socket + +## Name + +modbus_get_socket - get the current socket of the context + +## Synopsis + +```c +int modbus_get_socket(modbus_t *ctx); +``` + +## Description + +The *modbus_get_socket()* function shall return the current socket or file +descriptor of the libmodbus context. + +## Return value + +The function returns the current socket or file descriptor of the context if +successful. Otherwise it shall return -1 and set errno. + +## See also + +- [modbus_set_socket](modbus_set_socket.md) diff --git a/docs/modbus_mapping_free.md b/docs/modbus_mapping_free.md new file mode 100644 index 000000000..0703d0be3 --- /dev/null +++ b/docs/modbus_mapping_free.md @@ -0,0 +1,24 @@ +# modbus_mapping_free + +## Name + +modbus_mapping_free - free a modbus_mapping_t structure + +## Synopsis + +```c +void modbus_mapping_free(modbus_mapping_t *mb_mapping); +``` + +## Description + +The function shall free the four arrays of *modbus_mapping_t* structure and finally +the *modbus_mapping_t* itself referenced by `mb_mapping`. + +## Return value + +There is no return value. + +## See also + +- [modbus_mapping_new](modbus_mapping_new.md) diff --git a/docs/modbus_mapping_new.md b/docs/modbus_mapping_new.md new file mode 100644 index 000000000..9d3554a90 --- /dev/null +++ b/docs/modbus_mapping_new.md @@ -0,0 +1,61 @@ +# modbus_mapping_new + +## Name + +modbus_mapping_new - allocate four arrays of bits and registers + +## Synopsis + +```c +modbus_mapping_t* modbus_mapping_new(int nb_bits, int nb_input_bits, int nb_registers, int nb_input_registers); +``` + +## Description + +The *modbus_mapping_new()* function shall allocate four arrays to store bits, +input bits, registers and inputs registers. The pointers are stored in +*modbus_mapping_t* structure. All values of the arrays are initialized to zero. + +This function is equivalent to a call of the +[modbus_mapping_new_start_address](modbus_mapping_new_start_address.md) function +with all start addresses to `0`. + +If it isn't necessary to allocate an array for a specific type of data, you can +pass the zero value in argument, the associated pointer will be NULL. + +This function is convenient to handle requests in a Modbus server/slave. + +## Return value + +The function shall return the new allocated structure if successful. Otherwise +it shall return NULL and set errno. + +## Errors + +- *EINVAL*, one of the requested array dimensions exceeds 65536 entries. +- *ENOMEM*, not enough memory. + +## Example + +```c +/* The first value of each array is accessible from the 0 address. */ +mb_mapping = modbus_mapping_new( + BITS_ADDRESS + BITS_NB, + INPUT_BITS_ADDRESS + INPUT_BITS_NB, + REGISTERS_ADDRESS + REGISTERS_NB, + INPUT_REGISTERS_ADDRESS + INPUT_REGISTERS_NB +); +if (mb_mapping == NULL) { + fprintf( + stderr, "Failed to allocate the mapping: %s\n", + modbus_strerror(errno) + ); + modbus_free(ctx); + return -1; +} +``` + +## See also + +- [modbus_mapping_free](modbus_mapping_free.md) +- [modbus_mapping_new_start_address](modbus_mapping_new_start_address.md) diff --git a/docs/modbus_mapping_new_start_address.md b/docs/modbus_mapping_new_start_address.md new file mode 100644 index 000000000..5d209b8fc --- /dev/null +++ b/docs/modbus_mapping_new_start_address.md @@ -0,0 +1,91 @@ +# modbus_mapping_new_start_address + +## Name + +modbus_mapping_new_start_address - allocate four arrays of bits and registers accessible from their starting addresses + +## Synopsis + +```c +modbus_mapping_t* modbus_mapping_new_start_address( + unsigned int start_bits, unsigned int nb_bits, + unsigned int start_input_bits, unsigned int nb_input_bits, + unsigned int start_registers, unsigned int nb_registers, + unsigned int start_input_registers, unsigned int nb_input_registers); +``` + +## Description + +The `modbus_mapping_new_start_address()` function shall allocate four arrays to +store bits, input bits, registers and inputs registers. The pointers are stored +in *modbus_mapping_t* structure. All values of the arrays are initialized to zero. + +The different starting addresses make it possible to place the mapping at any +address in each address space. This way, you can give access to clients to +values stored at high addresses without allocating memory from the address zero, +for example to make available registers from 340 to 349, you can use: + +```c +mb_mapping = modbus_mapping_new_start_address(0, 0, 0, 0, 340, 10, 0, 0); +``` + +The newly created `mb_mapping` will have the following arrays: + +- `tab_bits` set to NULL +- `tab_input_bits` set to NULL +- `tab_registers` allocated to store 10 registers (`uint16_t`) +- `tab_input_registers` set to NULL + +The clients can read the first register by using the address 340 in its request. +On the server side, you should use the first index of the array to set the value +at this client address: + +```c +mb_mapping->tab_registers[0] = 42; +``` + +If it isn't necessary to allocate an array for a specific type of data, you can +pass the zero value in argument, the associated pointer will be NULL. + +Because the Modbus address space is 16-bit, each of the `nb_bits`, +`nb_input_bits`, `nb_registers` and `nb_input_registers` arguments must not +exceed 65536 entries. Larger values are rejected to avoid excessively large +allocations driven by untrusted configuration. + +This function is convenient to handle requests in a Modbus server/slave. + +## Return value + +The `modbus_mapping_new_start_address()` function shall return the new allocated structure if +successful. Otherwise it shall return NULL and set errno. + +## Errors + +- *EINVAL*, one of the requested array dimensions exceeds 65536 entries. +- *ENOMEM*, not enough memory. + +## Example + +```c +/* The first value of each array is accessible at the defined address. +The end address is ADDRESS + NB - 1. */ +mb_mapping = modbus_mapping_new_start_address( + BITS_ADDRESS, BITS_NB, + INPUT_BITS_ADDRESS, INPUT_BITS_NB, + REGISTERS_ADDRESS, REGISTERS_NB, + INPUT_REGISTERS_ADDRESS, INPUT_REGISTERS_NB +); +if (mb_mapping == NULL) { + fprintf( + stderr, "Failed to allocate the mapping: %s\n", + modbus_strerror(errno) + ); + modbus_free(ctx); + return -1; +} +``` + +## See also + +- [modbus_mapping_new](modbus_mapping_new.md) +- [modbus_mapping_free](modbus_mapping_free.md) diff --git a/docs/modbus_mask_write_register.md b/docs/modbus_mask_write_register.md new file mode 100644 index 000000000..b133dac22 --- /dev/null +++ b/docs/modbus_mask_write_register.md @@ -0,0 +1,34 @@ +# modbus_mask_write_register + +## Name + +modbus_mask_write_register - mask a single register + +## Synopsis + +```c +int modbus_mask_write_register(modbus_t *ctx, int addr, uint16_t and_mask, uint16_t or_mask); +``` + +## Description + +The *modbus_mask_write_register()* function shall modify the value of the +holding register at the address 'addr' of the remote device using the algorithm: + + new value = (current value AND 'and_mask') OR ('or_mask' AND (NOT 'and_mask')) + +The function uses the Modbus function code 0x16 (mask single register). + +## Return value + +The function shall return 1 if successful. Otherwise it shall return -1 and set +errno. + +## Errors + +- *EINVAL*, the context is NULL. + +## See also + +- [modbus_read_registers](modbus_read_registers.md) +- [modbus_write_registers](modbus_write_registers.md) diff --git a/docs/modbus_new_rtu.md b/docs/modbus_new_rtu.md new file mode 100644 index 000000000..f31e1c14c --- /dev/null +++ b/docs/modbus_new_rtu.md @@ -0,0 +1,103 @@ +# modbus_new_rtu + +## Name + +modbus_new_rtu - create a libmodbus context for RTU + +## Synopsis + +```c +modbus_t *modbus_new_rtu(const char *device, int baud, char parity, int data_bit, int stop_bit); +``` + +## Description + +The *modbus_new_rtu()* function shall allocate and initialize a `modbus_t` +structure to communicate in RTU mode on a serial line. + +The `device` argument specifies the name of the serial port handled by the OS, +eg. "/dev/ttyS0" or "/dev/ttyUSB0". On Windows, it's necessary to prepend COM +name with "\\.\" for COM number greater than 9, eg. "\\\\.\\COM10". See +http://msdn.microsoft.com/en-us/library/aa365247(v=vs.85).aspx for details + +The `baud` argument specifies the baud rate of the communication, eg. 9600, +19200, 57600, 115200, etc. + +The `parity` argument can have one of the following values: + +- `N` for none +- `E` for even +- `O` for odd + +The `data_bit` argument specifies the number of bits of data, the allowed +values are 5, 6, 7 and 8. + +The `stop_bit` argument specifies the bits of stop, the allowed values are 1 +and 2. + +Once the `modbus_t` structure is initialized, you can connect to the serial bus +with [modbus_connect](modbus_connect.md). + +In RTU, your program can act as server or client: + +- **server** is called *slave* in Modbus terminology, your program will expose + data to the network by processing and answering the requests of one of several + clients. It up to you to define the slave ID of your service with + [modbus_set_slave](modbus_set_slave.md), this ID should be used by the client + to communicate with your program. + +- **client** is called *master* in Modbus terminology, your program will send + requests to servers to read or write data from them. Before issuing the + requests, you should define the slave ID of the remote device with + [modbus_set_slave](modbus_set_slave.md). The slave ID is not an argument of + the read/write functions because it's very frequent to talk with only one + server so you can set it once and for all. The slave ID it not used in TCP + communications so this way the API is common to both. + +## Return value + +The function shall return a pointer to a `modbus_t` structure if +successful. Otherwise it shall return NULL and set errno to one of the values +defined below. + +## Errors + +- *EINVAL*, an invalid argument was given. +- *ENOMEM*, out of memory. Possibly, the application hits its memory limit + and/or whole system is running out of memory. + +## Example + +In this example, the program will open a serial communication on USB. All +subsequent calls such as read or write of registers will be sent on the wire and +the request will be visible to all connected devices. According to the Modbus +protocol, only the master associated to slave ID 10 will process and answer your +requests. + +```c +const int REMOTE_ID = 10; +modbus_t *ctx; +uint16_t tab_reg[10]; + +ctx = modbus_new_rtu("/dev/ttyUSB0", 115200, 'N', 8, 1); +if (ctx == NULL) { + fprintf(stderr, "Unable to create the libmodbus context\n"); + return -1; +} + +if (modbus_connect(ctx) == -1) { + fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno)); + modbus_free(ctx); + return -1; +} + +modbus_set_slave(ctx, REMOTE_ID); + +// Read 2 registers from address 0 of server ID 10. +modbus_read_registers(ctx, 0, 2, tab_reg); +``` + +## See also + +- [modbus_new_tcp](modbus_new_tcp.md) +- [modbus_free](modbus_free.md) diff --git a/docs/modbus_new_tcp.md b/docs/modbus_new_tcp.md new file mode 100644 index 000000000..bb05f9034 --- /dev/null +++ b/docs/modbus_new_tcp.md @@ -0,0 +1,60 @@ +# modbus_new_tcp + +## Name + +modbus_new_tcp - create a libmodbus context for TCP/IPv4 + +## Synopsis + +```c +modbus_t *modbus_new_tcp(const char *ip, int port); +``` + +## Description + +The *modbus_new_tcp()* function shall allocate and initialize a *modbus_t* +structure to communicate with a Modbus TCP IPv4 server. + +The `ip` argument specifies the IP address of the server to which the client +wants to establish a connection. A NULL value can be used to listen on any addresses in +server mode. + +The `port` argument is the TCP port to use. Set the port to +`MODBUS_TCP_DEFAULT_PORT` to use the default one (502). It's convenient to use a +port number greater than or equal to 1024 because it's not necessary to have +administrator privileges. + +## Return value + +The function shall return a pointer to a *modbus_t* structure if +successful. Otherwise it shall return NULL and set errno to one of the values +defined below. + +## Errors + +- *EINVAL*, an invalid IP address was given. +- *ENOMEM*, out of memory. Possibly, the application hits its memory limit + and/or whole system is running out of memory. + +## Example + +```c +modbus_t *ctx; + +ctx = modbus_new_tcp("127.0.0.1", 502); +if (ctx == NULL) { + fprintf(stderr, "Unable to allocate libmodbus context\n"); + return -1; +} + +if (modbus_connect(ctx) == -1) { + fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno)); + modbus_free(ctx); + return -1; +} +``` + +## See also + +- [modbus_tcp_listen](modbus_tcp_listen.md) +- [modbus_free](modbus_free.md) diff --git a/docs/modbus_new_tcp_pi.md b/docs/modbus_new_tcp_pi.md new file mode 100644 index 000000000..6020d5a4b --- /dev/null +++ b/docs/modbus_new_tcp_pi.md @@ -0,0 +1,62 @@ +# modbus_new_tcp_pi + +## Name + +modbus_new_tcp_pi - create a libmodbus context for TCP Protocol Independent + +## Synopsis + +```c +modbus_t *modbus_new_tcp_pi(const char *node, const char *service); +``` + +## Description + +The *modbus_new_tcp_pi()* function shall allocate and initialize a *modbus_t* +structure to communicate with a Modbus TCP IPv4 or IPv6 server. + +The `node` argument specifies the host name or IP address of the host to connect +to, eg. "192.168.0.5" , "::1" or "server.com". A NULL value can be used to +listen any addresses in server mode. + +The `service` argument is the service name/port number to connect to. To use the +default Modbus port, you can provide a NULL value or the string "502". On many +Unix systems, it's convenient to use a port number greater than or equal to 1024 +because it's not necessary to have administrator privileges. + +:octicons-tag-24: v3.1.8 handles NULL value for `service` (no *EINVAL* error). + +## Return value + +The function shall return a pointer to a *modbus_t* structure if +successful. Otherwise it shall return NULL and set errno to one of the values +defined below. + +## Errors + +- *ENOMEM*, out of memory. Possibly, the application hits its memory limit + and/or whole system is running out of memory. + +## Example + +```c +modbus_t *ctx; + +ctx = modbus_new_tcp_pi("::1", "502"); +if (ctx == NULL) { + fprintf(stderr, "Unable to allocate libmodbus context\n"); + return -1; +} + +if (modbus_connect(ctx) == -1) { + fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno)); + modbus_free(ctx); + return -1; +} +``` + +## See also + +- [modbus_new_tcp](modbus_new_tcp.md) +- [modbus_tcp_pi_listen](modbus_tcp_pi_listen.md) +- [modbus_free](modbus_free.md) diff --git a/docs/modbus_proxy.md b/docs/modbus_proxy.md new file mode 100644 index 000000000..b51b5fc35 --- /dev/null +++ b/docs/modbus_proxy.md @@ -0,0 +1,95 @@ +# modbus_proxy + +## Name + +modbus_proxy - forward a request to a backend device and relay the response + +## Synopsis + +```c +int modbus_proxy(modbus_t *frontend_ctx, modbus_t *backend_ctx, const uint8_t *req, int req_length); +``` + +## Description + +The *modbus_proxy()* function shall forward a Modbus request received on +`frontend_ctx` to a device connected through `backend_ctx`, and relay the +response back to the original requester on `frontend_ctx`. + +This function is intended for building Modbus gateways or proxies that bridge +two different backends. A typical use case is a TCP-to-RTU gateway: a client +sends a TCP request which is forwarded to an RTU device on a serial bus, and the +RTU response is relayed back as a TCP response. + +The request `req` of length `req_length` should be a raw request as returned by +[modbus_receive](modbus_receive.md). The slave address is extracted from `req` +and set on `backend_ctx` before forwarding. + +The function handles protocol translation between backends automatically: + +- the PDU (Protocol Data Unit) is extracted from the frontend framing +- it is re-framed for the backend protocol +- the backend response PDU is extracted and re-framed for the frontend protocol +- the transaction identifier from the original request is preserved + +On communication errors, an appropriate Modbus gateway exception response is +sent back to the frontend client: + +- `MODBUS_EXCEPTION_GATEWAY_PATH` (0x0A) if the request cannot be forwarded or + if a non-timeout communication error occurs +- `MODBUS_EXCEPTION_GATEWAY_TARGET` (0x0B) if the backend device does not + respond (timeout) or sends an invalid response + +## Return value + +The function shall return the length of the response sent on `frontend_ctx` if +successful. Otherwise it shall return -1 and set errno. + +## Errors + +- *EINVAL*, `frontend_ctx` or `backend_ctx` is NULL, or `req` is NULL, or + `req_length` is less than 1. +- *EMBBADDATA*, the PDU extracted from the request is invalid (too short or too + long). + +## Example + +```c +modbus_t *tcp_ctx; +modbus_t *rtu_ctx; +uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH]; +int rc; + +tcp_ctx = modbus_new_tcp("0.0.0.0", 1502); +rtu_ctx = modbus_new_rtu("/dev/ttyUSB0", 38400, 'E', 8, 1); + +int s = modbus_tcp_listen(tcp_ctx, 1); +modbus_connect(rtu_ctx); + +for (;;) { + modbus_tcp_accept(tcp_ctx, &s); + + for (;;) { + rc = modbus_receive(tcp_ctx, query); + if (rc < 0) + break; + if (rc == 0) + continue; + + /* Forward the TCP request to the RTU device and relay the response */ + modbus_proxy(tcp_ctx, rtu_ctx, query, rc); + } + + modbus_close(tcp_ctx); +} + +modbus_close(rtu_ctx); +modbus_free(tcp_ctx); +modbus_free(rtu_ctx); +``` + +## See also + +- [modbus_receive](modbus_receive.md) +- [modbus_reply](modbus_reply.md) +- [modbus_reply_exception](modbus_reply_exception.md) diff --git a/docs/modbus_read_bits.md b/docs/modbus_read_bits.md new file mode 100644 index 000000000..e4a3ba8a4 --- /dev/null +++ b/docs/modbus_read_bits.md @@ -0,0 +1,38 @@ +# modbus_read_bits + +## Name + +modbus_read_bits - read many bits + +## Synopsis + +```c +int modbus_read_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest); +``` + +## Description + +The *modbus_read_bits()* function shall read the status of the `nb` bits (coils) +to the address `addr` of the remote device. The result of reading is stored in +`dest` array as unsigned bytes (8 bits) set to `TRUE` or `FALSE`. + +The `dest` array must be allocated with at least `nb * sizeof(uint8_t)` bytes. +It is the caller's responsibility to ensure the buffer is large enough to hold +all the bits to be read. + +The function uses the Modbus function code 0x01 (read coil status). + +## Return value + +The function shall return the number of read bits if successful. Otherwise it +shall return -1 and set errno. + +## Errors + +- *EINVAL*, the `ctx` or `dest` argument is NULL, or `nb` is less than 1. +- *EMBXILVAL*, too many bits requested (nb > MODBUS_MAX_READ_BITS). + +## See also + +- [modbus_write_bit](modbus_write_bit.md) +- [modbus_write_bits](modbus_write_bits.md) diff --git a/docs/modbus_read_input_bits.md b/docs/modbus_read_input_bits.md new file mode 100644 index 000000000..e6f08bacb --- /dev/null +++ b/docs/modbus_read_input_bits.md @@ -0,0 +1,37 @@ +# modbus_read_input_bits + +## Name + +modbus_read_input_bits - read many input bits + +## Synopsis + +```c +int modbus_read_input_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest); +``` + +## Description + +The *modbus_read_input_bits()* function shall read the content of the `nb` input +bits to the address `addr` of the remote device. The result of reading is stored +in `dest` array as unsigned bytes (8 bits) set to `TRUE` or `FALSE`. + +The `dest` array must be allocated with at least `nb * sizeof(uint8_t)` bytes. +It is the caller's responsibility to ensure the buffer is large enough to hold +all the bits to be read. + +The function uses the Modbus function code 0x02 (read input status). + +## Return value + +The function shall return the number of read input status if +successful. Otherwise it shall return -1 and set errno. + +## Errors + +- *EINVAL*, the `ctx` or `dest` argument is NULL, or `nb` is less than 1. +- *EMBXILVAL*, too many discrete inputs requested (nb > MODBUS_MAX_READ_BITS). + +## See also + +- [modbus_read_input_registers](modbus_read_input_registers.md) diff --git a/docs/modbus_read_input_registers.md b/docs/modbus_read_input_registers.md new file mode 100644 index 000000000..3471b606a --- /dev/null +++ b/docs/modbus_read_input_registers.md @@ -0,0 +1,41 @@ +# modbus_read_input_registers + +## Name + +modbus_read_input_registers - read many input registers + +## Synopsis + +```c +int modbus_read_input_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest); +``` + +## Description + +The *modbus_read_input_registers()* function shall read the content of the `nb` +input registers to address `addr` of the remote device. The result of the +reading is stored in `dest` array as word values (16 bits). + +The `dest` array must be allocated with at least `nb * sizeof(uint16_t)` bytes. +It is the caller's responsibility to ensure the buffer is large enough to hold +all the registers to be read. + +The function uses the Modbus function code 0x04 (read input registers). The +holding registers and input registers have different historical meaning, but +nowadays it's more common to use holding registers only. + +## Return value + +The function shall return the number of read input registers if +successful. Otherwise it shall return -1 and set errno. + +## Errors + +- *EINVAL*, the `ctx` or `dest` argument is NULL, or `nb` is less than 1. +- *EMBXILVAL*, too many input registers requested (nb > MODBUS_MAX_READ_REGISTERS). + +## See also + +- [modbus_read_input_bits](modbus_read_input_bits.md) +- [modbus_write_register](modbus_write_register.md) +- [modbus_write_registers](modbus_write_registers.md) diff --git a/docs/modbus_read_registers.md b/docs/modbus_read_registers.md new file mode 100644 index 000000000..d77d0ce0a --- /dev/null +++ b/docs/modbus_read_registers.md @@ -0,0 +1,67 @@ +# modbus_read_registers + +## Name + +modbus_read_registers - read many registers + +## Synopsis + +```c +int modbus_read_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest); +``` + +## Description + +The *modbus_read_registers()* function shall read the content of the `nb` +holding registers to the address `addr` of the remote device. The result of +reading is stored in `dest` array as word values (16 bits). + +The `dest` array must be allocated with at least `nb * sizeof(uint16_t)` bytes. +It is the caller's responsibility to ensure the buffer is large enough to hold +all the registers to be read. + +The function uses the Modbus function code 0x03 (read holding registers). + +## Return value + +The function shall return the number of read registers +if successful. Otherwise it shall return -1 and set errno. + +## Errors + +- *EINVAL*, the `ctx` or `dest` argument is NULL, or `nb` is less than 1. +- *EMBXILVAL*, too many registers requested (nb > MODBUS_MAX_READ_REGISTERS). + +## Example + +```c +modbus_t *ctx; +uint16_t tab_reg[64]; +int rc; +int i; + +ctx = modbus_new_tcp("127.0.0.1", 502); +if (modbus_connect(ctx) == -1) { + fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno)); + modbus_free(ctx); + return -1; +} + +rc = modbus_read_registers(ctx, 0, 10, tab_reg); +if (rc == -1) { + fprintf(stderr, "%s\n", modbus_strerror(errno)); + return -1; +} + +for (i=0; i < rc; i++) { + printf("reg[%d]=%d (0x%X)\n", i, tab_reg[i], tab_reg[i]); +} + +modbus_close(ctx); +modbus_free(ctx); +``` + +## See also + +- [modbus_write_register](modbus_write_register.md) +- [modbus_write_registers](modbus_write_registers.md) diff --git a/docs/modbus_receive.md b/docs/modbus_receive.md new file mode 100644 index 000000000..8339e3bcf --- /dev/null +++ b/docs/modbus_receive.md @@ -0,0 +1,48 @@ +# modbus_receive + +## Name + +modbus_receive - receive an indication request + +## Synopsis + +```c +int modbus_receive(modbus_t *ctx, uint8_t *req); +``` + +## Description + +The *modbus_receive()* function shall receive an indication request from the +socket of the context `ctx`. This function is used by a Modbus slave/server to +receive and analyze indication request sent by the masters/clients. + +If you need to use another socket or file descriptor than the one defined in the +context `ctx`, see the function [modbus_set_socket](modbus_set_socket.md). + +## Return value + +The function shall store the indication request in `req` and return the request +length if successful. The returned request length can be zero if the indication +request is ignored (eg. a query for another slave in RTU mode). Otherwise it +shall return -1 and set errno. + +## Buffer size + +The maximum size of the request depends on the used backend, in RTU the `req` +array must be `MODBUS_RTU_MAX_ADU_LENGTH` bytes and in TCP it must be +`MODBUS_TCP_MAX_ADU_LENGTH` bytes. If you want to write code compatible with +both, you can use the constant `MODBUS_MAX_ADU_LENGTH` (maximum value of all +libmodbus backends). Take care to allocate enough memory to store requests to +avoid crashes of your server. + +## Example + +```c +uint8_t req[MODBUS_MAX_ADU_LENGTH]; +rc = modbus_receive(ctx, req); +``` + +## See also + +- [modbus_set_socket](modbus_set_socket.md) +- [modbus_reply](modbus_reply.md) diff --git a/docs/modbus_receive_confirmation.md b/docs/modbus_receive_confirmation.md new file mode 100644 index 000000000..68ddeff15 --- /dev/null +++ b/docs/modbus_receive_confirmation.md @@ -0,0 +1,43 @@ +# modbus_receive_confirmation + +## Name + +modbus_receive_confirmation - receive a confirmation request + +## Synopsis + +```c +int modbus_receive_confirmation(modbus_t *ctx, uint8_t *rsp); +``` + +## Description + +The *modbus_receive_confirmation()* function shall receive a request via the +socket of the context `ctx`. This function must be used for debugging purposes +because the received response isn't checked against the initial request. This +function can be used to receive request not handled by the library. + +The maximum size of the response depends on the used backend, in RTU the `rsp` +array must be `MODBUS_RTU_MAX_ADU_LENGTH` bytes and in TCP it must be +`MODBUS_TCP_MAX_ADU_LENGTH` bytes. If you want to write code compatible with +both, you can use the constant `MODBUS_MAX_ADU_LENGTH` (maximum value of all +libmodbus backends). Take care to allocate enough memory to store responses to +avoid crashes of your server. + +## Return value + +The function shall store the confirmation request in `rsp` and return the +response length if successful. The returned request length can be zero if the +indication request is ignored (eg. a query for another slave in RTU +mode). Otherwise it shall return -1 and set errno. + +## Example + +```c +uint8_t rsp[MODBUS_MAX_ADU_LENGTH]; +rc = modbus_receive_confirmation(ctx, rsp); +``` + +## See also + +- [modbus_send_raw_request](modbus_send_raw_request.md) diff --git a/docs/modbus_reply.md b/docs/modbus_reply.md new file mode 100644 index 000000000..b542c946c --- /dev/null +++ b/docs/modbus_reply.md @@ -0,0 +1,39 @@ +# modbus_reply + +## Name + +modbus_reply - send a response to the received request + +## Synopsis + +```c +int modbus_reply(modbus_t *ctx, const uint8_t *req, int req_length, modbus_mapping_t *mb_mapping); +``` + +## Description + +The *modbus_reply()* function shall send a response to received request. The +request `req` given in argument is analyzed, a response is then built and sent +by using the information of the modbus context `ctx`. + +If the request indicates to read or write a value the operation will done in the +modbus mapping `mb_mapping` according to the type of the manipulated data. + +If an error occurs, an exception response will be sent. + +This function is designed for Modbus servers. + +## Return value + +The function shall return the length of the response sent if +successful. Otherwise it shall return -1 and set errno. + +## Errors + +- *EMBMDATA*, sending has failed + +See also the errors returned by the syscall used to send the response (eg. send or write). + +## See also + +- [modbus_reply_exception](modbus_reply_exception.md) diff --git a/docs/modbus_reply_exception.md b/docs/modbus_reply_exception.md new file mode 100644 index 000000000..640e43527 --- /dev/null +++ b/docs/modbus_reply_exception.md @@ -0,0 +1,45 @@ +# modbus_reply_exception + +## Name + +modbus_reply_exception - send an exception response + +## Synopsis + +```c +int modbus_reply_exception(modbus_t *ctx, const uint8_t *req, unsigned int exception_code); +``` + +## Description + +The *modbus_reply_exception()* function shall send an exception response based +on the 'exception_code' in argument. + +The libmodbus provides the following exception codes: + +- MODBUS_EXCEPTION_ILLEGAL_FUNCTION (1) +- MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS (2) +- MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE (3) +- MODBUS_EXCEPTION_SLAVE_OR_SERVER_FAILURE (4) +- MODBUS_EXCEPTION_ACKNOWLEDGE (5) +- MODBUS_EXCEPTION_SLAVE_OR_SERVER_BUSY (6) +- MODBUS_EXCEPTION_NEGATIVE_ACKNOWLEDGE (7) +- MODBUS_EXCEPTION_MEMORY_PARITY (8) +- MODBUS_EXCEPTION_NOT_DEFINED (9) +- MODBUS_EXCEPTION_GATEWAY_PATH (10) +- MODBUS_EXCEPTION_GATEWAY_TARGET (11) + +The initial request `req` is required to build a valid response. + +## Return value + +The function shall return the length of the response sent if +successful. Otherwise it shall return -1 and set errno. + +## Errors + +- *EINVAL*, the exception code is invalid + +## See also + +- [modbus_reply](modbus_reply.md) diff --git a/docs/modbus_report_slave_id.md b/docs/modbus_report_slave_id.md new file mode 100644 index 000000000..b3180ecac --- /dev/null +++ b/docs/modbus_report_slave_id.md @@ -0,0 +1,56 @@ +# modbus_report_slave_id + +## Name + +modbus_report_slave_id - returns a description of the controller + +## Synopsis + +```c +int modbus_report_slave_id(modbus_t *ctx, int max_dest, uint8_t *dest); +``` + +## Description + +The *modbus_report_slave_id()* function shall send a request to the controller +to obtain a description of the controller. + +The response stored in `dest` contains: + +- the slave ID, this unique ID is in reality not unique at all so it's not + possible to depend on it to know how the information are packed in the + response. +- the run indicator status (0x00 = OFF, 0xFF = ON) +- additional data specific to each controller. For example, libmodbus returns + the version of the library as a string. + +The function writes at most `max_dest` bytes from the response to `dest` so +you must ensure that `dest` is large enough. + +## Return value + +The function shall return the number of read data if successful. + +If the output was truncated due to the `max_dest` limit then the return value is +the number of bytes which would have been written to `dest` if enough space had +been available. Thus, a return value greater than `max_dest` means that the +response data was truncated. + +Otherwise it shall return -1 and set errno. + +## Errors + +- *EINVAL*, the context is NULL, `dest` is NULL, or `max_dest` is not positive. + +## Example + +```c +uint8_t tab_bytes[MODBUS_MAX_PDU_LENGTH]; + +... + +rc = modbus_report_slave_id(ctx, MODBUS_MAX_PDU_LENGTH, tab_bytes); +if (rc > 1) { + printf("Run Status Indicator: %s\n", tab_bytes[1] ? "ON" : "OFF"); +} +``` diff --git a/docs/modbus_rtu_get_rts.md b/docs/modbus_rtu_get_rts.md new file mode 100644 index 000000000..ed222cfeb --- /dev/null +++ b/docs/modbus_rtu_get_rts.md @@ -0,0 +1,35 @@ +# modbus_rtu_get_rts + +## Name + +modbus_rtu_get_rts - get the current RTS mode in RTU + +## Synopsis + +```c +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 to one of the values defined below. + +## Errors + +- *EINVAL*, the libmodbus backend is not RTU. + +## See also + +- [modbus_rtu_set_rts](modbus_rtu_set_rts.md) diff --git a/docs/modbus_rtu_get_rts_delay.md b/docs/modbus_rtu_get_rts_delay.md new file mode 100644 index 000000000..7f6b57b77 --- /dev/null +++ b/docs/modbus_rtu_get_rts_delay.md @@ -0,0 +1,31 @@ +# modbus_rtu_get_rts_delay + +## Name + +modbus_rtu_get_rts_delay - get the current RTS delay in RTU + +## Synopsis + +```c +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 function shall return the current RTS delay in microseconds 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. + +## See also + +- [modbus_rtu_set_rts_delay](modbus_rtu_set_rts_delay.md) diff --git a/docs/modbus_rtu_get_serial_mode.md b/docs/modbus_rtu_get_serial_mode.md new file mode 100644 index 000000000..ef568f21c --- /dev/null +++ b/docs/modbus_rtu_get_serial_mode.md @@ -0,0 +1,41 @@ +# modbus_rtu_get_serial_mode + +## Name + +modbus_rtu_get_serial_mode - get the current serial mode + +## Synopsis + +```c +int modbus_rtu_get_serial_mode(modbus_t *ctx); +``` + +## Description + +The *modbus_rtu_get_serial_mode()* function shall return the serial mode +currently used by the libmodbus context: + +- **MODBUS_RTU_RS232**, the serial line is set for RS-232 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 RS-485 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 automation because it can be used effectively over long distances + and in 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. + +## Return value + +The function shall return `MODBUS_RTU_RS232` or `MODBUS_RTU_RS485` if +successful. Otherwise it shall return -1 and set errno to one of the values +defined below. + +## Errors + +- *EINVAL*, the current libmodbus backend is not RTU. diff --git a/docs/modbus_rtu_set_custom_rts.md b/docs/modbus_rtu_set_custom_rts.md new file mode 100644 index 000000000..9b3230b54 --- /dev/null +++ b/docs/modbus_rtu_set_custom_rts.md @@ -0,0 +1,32 @@ +# modbus_rtu_set_custom_rts + +## Name + +modbus_rtu_set_custom_rts - set a function to be used for custom RTS implementation + +## Synopsis + +```c +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. diff --git a/doc/modbus_rtu_set_rts.txt b/docs/modbus_rtu_set_rts.md similarity index 66% rename from doc/modbus_rtu_set_rts.txt rename to docs/modbus_rtu_set_rts.md index 98a231eda..f76e48a83 100644 --- a/doc/modbus_rtu_set_rts.txt +++ b/docs/modbus_rtu_set_rts.md @@ -1,21 +1,19 @@ -modbus_rtu_set_rts(3) -===================== +# modbus_rtu_set_rts +## Name -NAME ----- modbus_rtu_set_rts - set the RTS mode in RTU +## Synopsis -SYNOPSIS --------- -*int modbus_rtu_set_rts(modbus_t *'ctx', int 'mode')* +```c +int modbus_rtu_set_rts(modbus_t *ctx, int mode) +``` +## Description -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 +communicate on a RS-485 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` @@ -28,38 +26,35 @@ RTS flag. This function can only be used with a context using a RTU backend. +## Return value -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 -ERRORS ------- -*EINVAL*:: -The libmodbus backend isn't RTU or the mode given in argument is invalid. +- *EINVAL*, the libmodbus backend isn't RTU or the mode given in argument is invalid. +## Example -EXAMPLE -------- -.Enable the RTS mode with positive polarity -[source,c] -------------------- +Enable the RTS mode with positive polarity: + +```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)); + fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno)); modbus_free(ctx); return -1; } +modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS485); +modbus_rtu_set_rts(ctx, MODBUS_RTU_RTS_UP); + rc = modbus_read_registers(ctx, 0, 7, tab_reg); if (rc == -1) { fprintf(stderr, "%s\n", modbus_strerror(errno)); @@ -68,14 +63,8 @@ if (rc == -1) { modbus_close(ctx); modbus_free(ctx); -------------------- - -SEE ALSO --------- -linkmb:modbus_rtu_get_rts[3] +``` +## See also -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - +- [modbus_rtu_get_rts](modbus_rtu_get_rts.md) diff --git a/docs/modbus_rtu_set_rts_delay.md b/docs/modbus_rtu_set_rts_delay.md new file mode 100644 index 000000000..1ab1258b5 --- /dev/null +++ b/docs/modbus_rtu_set_rts_delay.md @@ -0,0 +1,31 @@ +# modbus_rtu_set_rts_delay + +## Name + +modbus_rtu_set_rts_delay - set the RTS delay in RTU + +## Synopsis + +```c +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 + +- [modbus_rtu_get_rts_delay](modbus_rtu_get_rts_delay.md) diff --git a/docs/modbus_rtu_set_serial_mode.md b/docs/modbus_rtu_set_serial_mode.md new file mode 100644 index 000000000..f263088f3 --- /dev/null +++ b/docs/modbus_rtu_set_serial_mode.md @@ -0,0 +1,43 @@ +# modbus_rtu_set_serial_mode + +## Name + +modbus_rtu_set_serial_mode - set the serial mode + +## Synopsis + +```c +int modbus_rtu_set_serial_mode(modbus_t *ctx, int mode); +``` + +## Description + +The *modbus_rtu_set_serial_mode()* function shall set the selected serial +mode: + +- **MODBUS_RTU_RS232**, the serial line is set for RS-232 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 RS-485 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 automation because it can be used effectively over long +distances and in electrically noisy environments. + +This function is only supported on Linux kernels 2.6.28 onwards. + +## 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 current libmodbus backend is not RTU. +- *ENOTSUP*, the function is not supported on your platform. + +If the call to `ioctl()` fails, the error code of ioctl will be returned. diff --git a/doc/modbus_send_raw_request.txt b/docs/modbus_send_raw_request.md similarity index 66% rename from doc/modbus_send_raw_request.txt rename to docs/modbus_send_raw_request.md index 39f836d28..f71e1463a 100644 --- a/doc/modbus_send_raw_request.txt +++ b/docs/modbus_send_raw_request.md @@ -1,23 +1,21 @@ -modbus_send_raw_request(3) -========================== +# modbus_send_raw_request +## Name -NAME ----- modbus_send_raw_request - send a raw request +## Synopsis -SYNOPSIS --------- -*int modbus_send_raw_request(modbus_t *'ctx', const uint8_t *'raw_req', int 'raw_req_length');* +```c +int modbus_send_raw_request(modbus_t *ctx, const uint8_t *raw_req, int raw_req_length); +``` +## Description -DESCRIPTION ------------ The *modbus_send_raw_request()* function shall send a request via the socket of -the context _ctx_. This function must be used for debugging purposes because you +the context `ctx`. This function must be used for debugging purposes because you have to take care to make a valid request by hand. The function only adds to the -message, the header or CRC of the selected backend, so _raw_req_ must start and +message, the header or CRC of the selected backend, so `raw_req` must start and contain at least a slave/unit identifier and a function code. This function can be used to send request not handled by the library. @@ -25,25 +23,22 @@ The public header of libmodbus provides a list of supported Modbus functions codes, prefixed by `MODBUS_FC_` (eg. `MODBUS_FC_READ_HOLDING_REGISTERS`), to help build of raw requests. +## Return value -RETURN VALUE ------------- The function shall return the full message length, counting the extra data relating to the backend, if successful. Otherwise it shall return -1 and set errno. +## Example -EXAMPLE -------- -[source,c] -------------------- +```c modbus_t *ctx; /* Read 5 holding registers from address 1 */ uint8_t raw_req[] = { 0xFF, MODBUS_FC_READ_HOLDING_REGISTERS, 0x00, 0x01, 0x0, 0x05 }; int req_length; uint8_t rsp[MODBUS_TCP_MAX_ADU_LENGTH]; -ctx = modbus_new_tcp("127.0.0.1", 1502); +ctx = modbus_new_tcp("127.0.0.1", 502); if (modbus_connect(ctx) == -1) { fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno)); modbus_free(ctx); @@ -55,14 +50,8 @@ modbus_receive_confirmation(ctx, rsp); modbus_close(ctx); modbus_free(ctx); -------------------- +``` -SEE ALSO --------- -linkmb:modbus_receive_confirmation[3] +## See also - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - +- [modbus_receive_confirmation](modbus_receive_confirmation.md) diff --git a/docs/modbus_send_raw_request_tid.md b/docs/modbus_send_raw_request_tid.md new file mode 100644 index 000000000..6d4470fb0 --- /dev/null +++ b/docs/modbus_send_raw_request_tid.md @@ -0,0 +1,59 @@ +# modbus_send_raw_request_tid + +## Name + +modbus_send_raw_request_tid - send a raw request with a specific transaction id + +## Synopsis + +```c +int modbus_send_raw_request_tid(modbus_t *ctx, const uint8_t *raw_req, int raw_req_length, int tid); +``` + +## Description + +The *modbus_send_raw_request_tid()* function shall send a request via the socket of +the context `ctx`. This function must be used for debugging purposes because you +have to take care to make a valid request by hand. The function only adds to the +message, the header or CRC of the selected backend, so `raw_req` must start and +contain at least a slave/unit identifier and a function code. This function can +be used to send request not handled by the library. + +The tid paramter enables one to specify a transaction identifier. + +The public header of libmodbus provides a list of supported Modbus functions +codes, prefixed by `MODBUS_FC_` (eg. `MODBUS_FC_READ_HOLDING_REGISTERS`), to help +build of raw requests. + +## Return value + +The function shall return the full message length, counting the extra data +relating to the backend, if successful. Otherwise it shall return -1 and set +errno. + +## Example + +```c +modbus_t *ctx; +/* Read 5 holding registers from address 1 */ +uint8_t raw_req[] = { 0xFF, MODBUS_FC_READ_HOLDING_REGISTERS, 0x00, 0x01, 0x0, 0x05 }; +int req_length; +uint8_t rsp[MODBUS_TCP_MAX_ADU_LENGTH]; + +ctx = modbus_new_tcp("127.0.0.1", 502); +if (modbus_connect(ctx) == -1) { + fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno)); + modbus_free(ctx); + return -1; +} + +req_length = modbus_send_raw_request(ctx, raw_req, 6 * sizeof(uint8_t), 0); +modbus_receive_confirmation(ctx, rsp); + +modbus_close(ctx); +modbus_free(ctx); +``` + +## See also + +- [modbus_receive_confirmation](modbus_receive_confirmation.md) diff --git a/docs/modbus_set_bits_from_byte.md b/docs/modbus_set_bits_from_byte.md new file mode 100644 index 000000000..77066fca1 --- /dev/null +++ b/docs/modbus_set_bits_from_byte.md @@ -0,0 +1,27 @@ +# modbus_set_bits_from_byte + +## Name + +modbus_set_bits_from_byte - set many bits from a single byte value + + +## Synopsis + +```c +void modbus_set_bits_from_byte(uint8_t *dest, int index, const uint8_t value); +``` + +## Description + +The *modbus_set_bits_from_byte()* function shall set many bits from a single +byte. All 8 bits from the byte `value` will be written to `dest` array starting +at `index` position. + +## Return value + +There is no return value. + +## See also + +- [modbus_get_byte_from_bits](modbus_get_byte_from_bits.md) +- [modbus_set_bits_from_bytes](modbus_set_bits_from_bytes.md) diff --git a/docs/modbus_set_bits_from_bytes.md b/docs/modbus_set_bits_from_bytes.md new file mode 100644 index 000000000..e66d11e33 --- /dev/null +++ b/docs/modbus_set_bits_from_bytes.md @@ -0,0 +1,26 @@ +# modbus_set_bits_from_bytes + +## Name + +modbus_set_bits_from_bytes - set many bits from an array of bytes + +## Synopsis + +```c +void modbus_set_bits_from_bytes(uint8_t *dest, int index, unsigned int nb_bits, const uint8_t *tab_byte); +``` + +## Description + +The *modbus_set_bits_from_bytes* function shall set bits by reading an array of +bytes. All the bits of the bytes read from the first position of the array +`tab_byte` are written as bits in the `dest` array starting at position `index`. + +## Return value + +There is no return value. + +## See also + +- [modbus_set_bits_from_byte](modbus_set_bits_from_byte.md) +- [modbus_get_byte_from_bits](modbus_get_byte_from_bits.md) diff --git a/doc/modbus_set_byte_timeout.txt b/docs/modbus_set_byte_timeout.md similarity index 54% rename from doc/modbus_set_byte_timeout.txt rename to docs/modbus_set_byte_timeout.md index 84e73ae72..ff4162901 100644 --- a/doc/modbus_set_byte_timeout.txt +++ b/docs/modbus_set_byte_timeout.md @@ -1,54 +1,42 @@ -modbus_set_byte_timeout(3) -========================== +# modbus_set_byte_timeout +## Name -NAME ----- modbus_set_byte_timeout - set timeout between bytes +## Synopsis -SYNOPSIS --------- -*void modbus_set_byte_timeout(modbus_t *'ctx', uint32_t 'to_sec', uint32_t 'to_usec');* +```c +int modbus_set_byte_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec); +``` +## Description -DESCRIPTION ------------ The *modbus_set_byte_timeout()* function shall set the timeout interval between two consecutive bytes of the same message. The timeout is an upper bound on the amount of time elapsed before *select()* returns, if the time elapsed is longer than the defined timeout, an `ETIMEDOUT` error will be raised by the function waiting for a response. -The value of _to_usec_ argument must be in the range 0 to 999999. +The value of `to_usec` argument must be in the range 0 to 999999. -If both _to_sec_ and _to_usec_ are zero, this timeout will not be used at all. +If both `to_sec` and `to_usec` are zero, this timeout will not be used at all. In this case, *modbus_set_response_timeout()* governs the entire handling of the response, the full confirmation response must be received before expiration of the response timeout. When a byte timeout is set, the response timeout is only used to wait for until the first byte of the response. +## Return value -RETURN VALUE ------------- The function shall return 0 if successful. Otherwise it shall return -1 and set errno. +## Errors -ERRORS ------- -*EINVAL*:: -The argument _ctx_ is NULL or _to_usec_ is larger than 1000000. +- *EINVAL*, The argument `ctx` is NULL or `to_usec` is larger than 999999. +## See also -SEE ALSO --------- -linkmb:modbus_get_byte_timeout[3] -linkmb:modbus_get_response_timeout[3] -linkmb:modbus_set_response_timeout[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - +- [modbus_get_byte_timeout](modbus_get_byte_timeout.md) +- [modbus_get_response_timeout](modbus_get_response_timeout.md) +- [modbus_set_response_timeout](modbus_set_response_timeout.md) diff --git a/doc/modbus_set_debug.txt b/docs/modbus_set_debug.md similarity index 51% rename from doc/modbus_set_debug.txt rename to docs/modbus_set_debug.md index 3154c2199..b68a17377 100644 --- a/doc/modbus_set_debug.txt +++ b/docs/modbus_set_debug.md @@ -1,38 +1,29 @@ -modbus_set_debug(3) -=================== +# modbus_set_debug + +## Name -NAME ----- modbus_set_debug - set debug flag of the context +## Synopsis -SYNOPSIS --------- -*int modbus_set_debug(modbus_t *'ctx', int 'flag');* +```c +int modbus_set_debug(modbus_t *ctx, int flag); +``` +## Description -DESCRIPTION ------------ The *modbus_set_debug()* function shall set the debug flag of the *modbus_t* -context by using the argument _flag_. By default, the boolean flag is set to -`FALSE`. When the _flag_ value is set to `TRUE`, many verbose messages are +context by using the argument `flag`. By default, the boolean flag is set to +`FALSE`. When the `flag` value is set to `TRUE`, many verbose messages are displayed on stdout and stderr. For example, this flag is useful to display the bytes of the Modbus messages. -[verse] -___________________ +```text [00][14][00][00][00][06][12][03][00][6B][00][03] Waiting for a confirmation... <00><14><00><00><00><09><12><03><06><02><2B><00><00><00><00> -___________________ +``` +## Return value -RETURN VALUE ------------- The function shall return 0 if successful. Otherwise it shall return -1 and set errno. - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - diff --git a/doc/modbus_set_error_recovery.txt b/docs/modbus_set_error_recovery.md similarity index 67% rename from doc/modbus_set_error_recovery.txt rename to docs/modbus_set_error_recovery.md index b5fc521c2..deb042be1 100644 --- a/doc/modbus_set_error_recovery.txt +++ b/docs/modbus_set_error_recovery.md @@ -1,22 +1,20 @@ -modbus_set_error_recovery(3) -============================ +# modbus_set_error_recovery +## Name -NAME ----- modbus_set_error_recovery - set the error recovery mode +## Synopsis -SYNOPSIS --------- -*int modbus_set_error_recovery(modbus_t *'ctx', modbus_error_recovery_mode 'error_recovery');* +```c +int modbus_set_error_recovery(modbus_t *ctx, modbus_error_recovery_mode error_recovery); +``` +## Description -DESCRIPTION ------------ The *modbus_set_error_recovery()* function shall set the error recovery mode to apply when the connection fails or the byte received is not expected. The -argument _error_recovery_ may be bitwise-or'ed with zero or more of the +argument `error_recovery` may be bitwise-or'ed with zero or more of the following constants. By default there is no error recovery (`MODBUS_ERROR_RECOVERY_NONE`) so the @@ -40,31 +38,22 @@ expected one. The response timeout delay will be used to sleep. The modes are mask values and so they are complementary. -It's not recommended to enable error recovery for slave/server. +It's not recommended to enable error recovery for a Modbus slave/server. +## Return value -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 -ERRORS ------- -*EINVAL*:: -The value of the argument _error_recovery_ is not positive. +- *EINVAL*, the value of the argument `error_recovery` is not positive. +## Example -EXAMPLE -------- -[source,c] -------------------- -modbus_set_error_recovery(ctx, - MODBUS_ERROR_RECOVERY_LINK | - MODBUS_ERROR_RECOVERY_PROTOCOL); -------------------- - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - +```c +modbus_set_error_recovery( + ctx, + MODBUS_ERROR_RECOVERY_LINK | MODBUS_ERROR_RECOVERY_PROTOCOL +); +``` diff --git a/docs/modbus_set_float.md b/docs/modbus_set_float.md new file mode 100644 index 000000000..34fc0a1a8 --- /dev/null +++ b/docs/modbus_set_float.md @@ -0,0 +1,29 @@ +# modbus_set_float + +## Name + +modbus_set_float - set a float value from 2 registers + +## Synopsis + +```c +void modbus_set_float(float f, uint16_t *dest); +``` + +Warning, this function is *deprecated* since libmodbus v3.2.0 and has been +replaced by *modbus_set_float_dcba()*. + +## Description + +The *modbus_set_float()* function shall set a float to 4 bytes in Modbus format +(ABCD). The `dest` array must be pointer on two 16 bits values to be able to +store the full result of the conversion. + +## Return value + +There is no return values. + +## See also + +- [modbus_get_float](modbus_get_float.md) +- [modbus_set_float_dcba](modbus_set_float_dcba.md) diff --git a/docs/modbus_set_float_abcd.md b/docs/modbus_set_float_abcd.md new file mode 100644 index 000000000..070893eb1 --- /dev/null +++ b/docs/modbus_set_float_abcd.md @@ -0,0 +1,28 @@ +# modbus_set_float_abcd + +## Name + +modbus_set_float_abcd - set a float value in 2 registers using ABCD byte order + +## Synopsis + +```c +void modbus_set_float_abcd(float f, uint16_t *dest); +``` + +## Description + +The *modbus_set_float_abcd()* function shall set a float to 4 bytes in usual +Modbus format. The `dest` array must be pointer on two 16 bits values to be able +to store the full result of the conversion. + +## Return value + +There is no return values. + +## See also + +- [modbus_get_float_abcd](modbus_get_float_abcd.md) +- [modbus_set_float_badc](modbus_set_float_badc.md) +- [modbus_set_float_cdab](modbus_set_float_cdab.md) +- [modbus_set_float_dcba](modbus_set_float_dcba.md) diff --git a/docs/modbus_set_float_badc.md b/docs/modbus_set_float_badc.md new file mode 100644 index 000000000..c0559efb0 --- /dev/null +++ b/docs/modbus_set_float_badc.md @@ -0,0 +1,28 @@ +# modbus_set_float_badc + +## Name + +modbus_set_float_badc - set a float value in 2 registers using BADC byte order + +## Synopsis + +```c +void modbus_set_float_badc(float f, uint16_t *dest); +``` + +## Description + +The *modbus_set_float_badc()* function shall set a float to 4 bytes in swapped +bytes Modbus format (BADC instead of ABCD). The `dest` array must be pointer on +two 16 bits values to be able to store the full result of the conversion. + +## Return value + +There is no return values. + +## See also + +- [modbus_get_float_badc](modbus_get_float_badc.md) +- [modbus_set_float_abcd](modbus_set_float_abcd.md) +- [modbus_set_float_cdab](modbus_set_float_cdab.md) +- [modbus_set_float_dcba](modbus_set_float_dcba.md) diff --git a/docs/modbus_set_float_cdab.md b/docs/modbus_set_float_cdab.md new file mode 100644 index 000000000..d91470bf1 --- /dev/null +++ b/docs/modbus_set_float_cdab.md @@ -0,0 +1,29 @@ +# modbus_set_float_cdab + +## Name + +modbus_set_float_cdab - set a float value in 2 registers using CDAB byte order + +## Synopsis + +```c +void modbus_set_float_cdab(float f, uint16_t *dest); +``` + +## Description + +The *modbus_set_float_cdab()* function shall set a float to 4 bytes in swapped +words Modbus format (CDAB order instead of ABCD). The `dest` array must be +pointer on two 16 bits values to be able to store the full result of the +conversion. + +## Return value + +There is no return values. + +## See also + +- [modbus_get_float_cdab](modbus_get_float_cdab.md) +- [modbus_set_float_abcd](modbus_set_float_abcd.md) +- [modbus_set_float_badc](modbus_set_float_badc.md) +- [modbus_set_float_dcba](modbus_set_float_dcba.md) diff --git a/docs/modbus_set_float_dcba.md b/docs/modbus_set_float_dcba.md new file mode 100644 index 000000000..8f6e48272 --- /dev/null +++ b/docs/modbus_set_float_dcba.md @@ -0,0 +1,27 @@ +# modbus_set_float_dcba + +## Name + +modbus_set_float_dcba - set a float value in 2 registers using DCBA byte order + +## Synopsis + +```c +void modbus_set_float_dcba(float f, uint16_t *dest); +``` + +## Description + +The *modbus_set_float_dcba()* function shall set a float to 4 bytes in inverted +Modbus format (DCBA order). The `dest` array must be pointer on two 16 bits +values to be able to store the full result of the conversion. + +## Return value + +There is no return values. + +## See also + +- [modbus_get_float_dcba](modbus_get_float_dcba.md) +- [modbus_set_float](modbus_set_float.md) +- [modbus_get_float](modbus_get_float.md) diff --git a/docs/modbus_set_indication_timeout.md b/docs/modbus_set_indication_timeout.md new file mode 100644 index 000000000..cb61b51b4 --- /dev/null +++ b/docs/modbus_set_indication_timeout.md @@ -0,0 +1,36 @@ +# modbus_set_indication_timeout + +## Name + +modbus_set_indication_timeout - set timeout between indications + +## Synopsis + +```c +void modbus_set_indication_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec); +``` + +## Description + +The *modbus_set_indication_timeout()* function shall set the timeout interval used by +a server to wait for a request from a client. + +The value of `to_usec` argument must be in the range 0 to 999999. + +If both `to_sec` and `to_usec` are zero, this timeout will not be used at all. +In this case, the server will wait forever. + +## Return value + +The function shall return 0 if successful. Otherwise it shall return -1 and set +errno. + +## Errors + +- *EINVAL*, the argument `ctx` is NULL or `to_usec` is larger than 1000000. + +## See also + +- [modbus_get_indication_timeout](modbus_get_indication_timeout.md) +- [modbus_get_response_timeout](modbus_get_response_timeout.md) +- [modbus_set_response_timeout](modbus_set_response_timeout.md) diff --git a/doc/modbus_set_response_timeout.txt b/docs/modbus_set_response_timeout.md similarity index 52% rename from doc/modbus_set_response_timeout.txt rename to docs/modbus_set_response_timeout.md index 47caf61b0..99f15bac6 100644 --- a/doc/modbus_set_response_timeout.txt +++ b/docs/modbus_set_response_timeout.md @@ -1,19 +1,17 @@ -modbus_set_response_timeout(3) -============================== +# modbus_set_response_timeout +## Name -NAME ----- modbus_set_response_timeout - set timeout for response +## Synopsis -SYNOPSIS --------- -*int modbus_set_response_timeout(modbus_t *'ctx', uint32_t 'to_sec', uint32_t 'to_usec');* +```c +int modbus_set_response_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec); +``` +## Description -DESCRIPTION ------------ The *modbus_set_response_timeout()* function shall set the timeout interval used to wait for a response. When a byte timeout is set, if elapsed time for the first byte of response is longer than the given timeout, an `ETIMEDOUT` error @@ -21,26 +19,21 @@ will be raised by the function waiting for a response. When byte timeout is disabled, the full confirmation response must be received before expiration of the response timeout. -The value of _to_usec_ argument must be in the range 0 to 999999. +The value of `to_usec` argument must be in the range 0 to 999999. +## Return value -RETURN VALUE ------------- The function shall return 0 if successful. Otherwise it shall return -1 and set errno. +## Errors -ERRORS ------- -*EINVAL*:: -The argument _ctx_ is NULL, or both _to_sec_ and _to_usec_ are zero, or _to_usec_ -is larger than 1000000. +- *EINVAL*, the argument `ctx` is NULL, or both `to_sec` and `to_usec` are zero, + or `to_usec` is larger than 999999. +## Example -EXAMPLE -------- -[source,c] -------------------- +```c uint32_t old_response_to_sec; uint32_t old_response_to_usec; @@ -49,17 +42,10 @@ modbus_get_response_timeout(ctx, &old_response_to_sec, &old_response_to_usec); /* Define a new timeout of 200ms */ modbus_set_response_timeout(ctx, 0, 200000); -------------------- +``` +## See also -SEE ALSO --------- -linkmb:modbus_get_response_timeout[3] -linkmb:modbus_get_byte_timeout[3] -linkmb:modbus_set_byte_timeout[3] - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - +- [modbus_get_response_timeout](modbus_get_response_timeout.md) +- [modbus_get_byte_timeout](modbus_get_byte_timeout.md) +- [modbus_set_byte_timeout](modbus_set_byte_timeout.md) diff --git a/docs/modbus_set_slave.md b/docs/modbus_set_slave.md new file mode 100644 index 000000000..efdeea386 --- /dev/null +++ b/docs/modbus_set_slave.md @@ -0,0 +1,77 @@ +# modbus_set_slave + +## Name + +modbus_set_slave - set slave number in the context + +## Synopsis + +```c +int modbus_set_slave(modbus_t *ctx, int slave); +``` + +## Description + +The *modbus_set_slave()* function shall set the slave number in the libmodbus +context. + +It is usually only required to set the slave ID in **RTU**. The meaning of this +ID will be different if your program acts as client (master) or server (slave). + +As **RTU client**, *modbus_set_slave()* sets the ID of the remote device you +want to communicate. Be sure to set the slave ID before issuing any Modbus +requests on the serial bus. If you communicate with several servers (slaves), +you can set the slave ID of the remote device before each request. + +As **RTU server**, the slave ID allows the various clients to reach your +service. You should use a free ID, once set, this ID should be known by the +clients of the network. According to the protocol, a Modbus device must only +accept message holding its slave number or the special broadcast number. + +In **TCP**, the slave number is only required if the message must reach a device +on a serial network. Some not compliant devices or software (such as modpoll) +uses the slave ID as unit identifier, that's incorrect (cf page 23 of Modbus +Messaging Implementation Guide v1.0b) but without the slave value, the faulty +remote device or software drops the requests! The special value +`MODBUS_TCP_SLAVE` (0xFF) can be used in TCP mode to restore the default value. + +The broadcast address is `MODBUS_BROADCAST_ADDRESS`. This special value must be +use when you want all Modbus devices of the network receive the request. + +## 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 slave number is invalid. + +## Example + +```c +modbus_t *ctx; + +ctx = modbus_new_rtu("/dev/ttyUSB0", 115200, 'N', 8, 1); +if (ctx == NULL) { + fprintf(stderr, "Unable to create the libmodbus context\n"); + return -1; +} + +rc = modbus_set_slave(ctx, YOUR_DEVICE_ID); +if (rc == -1) { + fprintf(stderr, "Invalid slave ID\n"); + modbus_free(ctx); + return -1; +} + +if (modbus_connect(ctx) == -1) { + fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno)); + modbus_free(ctx); + return -1; +} +``` + +## See also + +- [modbus_get_slave](modbus_get_slave.md) diff --git a/doc/modbus_set_socket.txt b/docs/modbus_set_socket.md similarity index 59% rename from doc/modbus_set_socket.txt rename to docs/modbus_set_socket.md index 49e5d1ff0..5fb91a07b 100644 --- a/doc/modbus_set_socket.txt +++ b/docs/modbus_set_socket.md @@ -1,34 +1,29 @@ -modbus_set_socket(3) -==================== +# modbus_set_socket +## Name -NAME ----- modbus_set_socket - set socket of the context +## Synopsis -SYNOPSIS --------- -*int modbus_set_socket(modbus_t *'ctx', int 's');* +```c +int modbus_set_socket(modbus_t *ctx, int s); +``` +## Description -DESCRIPTION ------------ The *modbus_set_socket()* function shall set the socket or file descriptor in the libmodbus context. This function is useful for managing multiple client connections to the same server. +## Return value -RETURN VALUE ------------- The function shall return 0 if successful. Otherwise it shall return -1 and set errno. +## Example -EXAMPLE -------- -[source,c] -------------------- -ctx = modbus_new_tcp("127.0.0.1", 1502); +```c +ctx = modbus_new_tcp("127.0.0.1", 502); server_socket = modbus_tcp_listen(ctx, NB_CONNECTION); FD_ZERO(&rdset); @@ -43,14 +38,8 @@ if (FD_ISSET(master_socket, &rdset)) { modbus_reply(ctx, query, rc, mb_mapping); } } -------------------- +``` -SEE ALSO --------- -linkmb:modbus_get_socket[3] +## See also - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - +- [modbus_get_socket](modbus_get_socket.md) diff --git a/doc/modbus_strerror.txt b/docs/modbus_strerror.md similarity index 50% rename from doc/modbus_strerror.txt rename to docs/modbus_strerror.md index 9cc355f1a..9db8b799e 100644 --- a/doc/modbus_strerror.txt +++ b/docs/modbus_strerror.md @@ -1,54 +1,39 @@ -modbus_strerror(3) -================= +# modbus_strerror +## Name -NAME ----- modbus_strerror - return the error message +## Synopsis -SYNOPSIS --------- -*const char *modbus_strerror(int 'errnum');* +```c +const char *modbus_strerror(int errnum); +``` +## Description -DESCRIPTION ------------ The *modbus_strerror()* function shall return a pointer to an error message -string corresponding to the error number specified by the _errnum_ argument. As +string corresponding to the error number specified by the `errnum` argument. As libmodbus defines additional error numbers over and above those defined by the operating system, applications should use *modbus_strerror()* in preference to the standard *strerror()* function. +## Return value -RETURN VALUE ------------- The *modbus_strerror()* function shall return a pointer to an error message string. +## Errors -ERRORS ------- No errors are defined. +## Example -EXAMPLE -------- -.Display an error message when a Modbus connection cannot be established -[source,c] -------------------- +Display an error message when a Modbus connection cannot be established + +```c if (modbus_connect(ctx) == -1) { fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno)); abort(); } -------------------- - -SEE ALSO --------- -linkmb:libmodbus - - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - +``` diff --git a/doc/modbus_tcp_accept.txt b/docs/modbus_tcp_accept.md similarity index 50% rename from doc/modbus_tcp_accept.txt rename to docs/modbus_tcp_accept.md index 4c46d9079..db7ad6884 100644 --- a/doc/modbus_tcp_accept.txt +++ b/docs/modbus_tcp_accept.md @@ -1,37 +1,32 @@ -modbus_tcp_accept(3) -==================== +# modbus_tcp_accept +## Name -NAME ----- modbus_tcp_accept - accept a new connection on a TCP Modbus socket (IPv4) +## Synopsis -SYNOPSIS --------- -*int modbus_tcp_accept(modbus_t *'ctx', int *'s);* +```c +int modbus_tcp_accept(modbus_t *ctx, int *s); +``` +## Description -DESCRIPTION ------------ The *modbus_tcp_accept()* function shall extract the first connection on the queue of pending connections, create a new socket and store it in libmodbus -context given in argument. If available, _accept4()_ with `SOCK_CLOEXEC` will be -called instead of *accept()*. +context given in argument. If available, `accept4()` with `SOCK_CLOEXEC` will be +called instead of `accept()`. +## Return value -RETURN VALUE ------------- The function shall return a new socket if successful. Otherwise it shall return -1 and set errno. +## Example -EXAMPLE -------- For detailed example, see unit-test-server.c source file in tests directory. -[source,c] -------------------- +```c ... ctx = modbus_new_tcp("127.0.0.1", 502); @@ -40,17 +35,12 @@ modbus_tcp_accept(ctx, &s); ... -close(s) +close(s); modbus_free(ctx); -------------------- - -SEE ALSO --------- -linkmb:modbus_tcp_pi_accept[3] -linkmb:modbus_tcp_listen[3] -linkmb:modbus_tcp_pi_listen[3] - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - +``` + +## See also + +- [modbus_tcp_pi_accept](modbus_tcp_pi_accept.md) +- [modbus_tcp_listen](modbus_tcp_listen.md) +- [modbus_tcp_pi_listen](modbus_tcp_pi_listen.md) diff --git a/doc/modbus_tcp_listen.txt b/docs/modbus_tcp_listen.md similarity index 60% rename from doc/modbus_tcp_listen.txt rename to docs/modbus_tcp_listen.md index 640bbbbfe..20c6b3ceb 100644 --- a/doc/modbus_tcp_listen.txt +++ b/docs/modbus_tcp_listen.md @@ -1,42 +1,36 @@ -modbus_tcp_listen(3) -==================== +# modbus_tcp_listen +## Name -NAME ----- modbus_tcp_listen - create and listen a TCP Modbus socket (IPv4) +## Synopsis -SYNOPSIS --------- -*int modbus_tcp_listen(modbus_t *'ctx', int 'nb_connection');* +```c +int modbus_tcp_listen(modbus_t *ctx, int nb_connection); +``` +## Description -DESCRIPTION ------------ The *modbus_tcp_listen()* function shall create a socket and listen to maximum -_nb_connection_ incoming connections on the specified IP address. The context -_ctx _must be allocated and initialized with linkmb:modbus_new_tcp[3] before to +`nb_connection` incoming connections on the specified IP address. The context +`ctx` must be allocated and initialized with [modbus_new_tcp](modbus_new_tcp.md) before to set the IP address to listen, if IP address is set to NULL or '0.0.0.0', any addresses will be listen. +## Return value -RETURN VALUE ------------- The function shall return a new socket if successful. Otherwise it shall return -1 and set errno. +## Example -EXAMPLE -------- For detailed examples, see source files in tests directory: - unit-test-server.c, simple but handle only one connection - bandwidth-server-many-up.c, handles several connections at once - -[source,c] -------------------- +```c ... /* To listen any addresses on port 502 */ @@ -58,15 +52,10 @@ if (select(server_socket + 1, &refset, NULL, NULL, NULL) == -1) { close(server_socket); modbus_free(ctx); -------------------- - -SEE ALSO --------- -linkmb:modbus_new_tcp[3] -linkmb:modbus_tcp_accept[3] -linkmb:modbus_tcp_pi_listen[3] - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - +``` + +## See also + +- [modbus_new_tcp](modbus_new_tcp.md) +- [modbus_tcp_accept](modbus_tcp_accept.md) +- [modbus_tcp_pi_listen](modbus_tcp_pi_listen.md) diff --git a/docs/modbus_tcp_pi_accept.md b/docs/modbus_tcp_pi_accept.md new file mode 100644 index 000000000..730d702a4 --- /dev/null +++ b/docs/modbus_tcp_pi_accept.md @@ -0,0 +1,46 @@ +# modbus_tcp_pi_accept + +## Name + +modbus_tcp_pi_accept - accept a new connection on a TCP PI Modbus socket (IPv6) + +## Synopsis + +```c +int modbus_tcp_pi_accept(modbus_t *ctx, int *s); +``` + +## Description + +The *modbus_tcp_pi_accept()* function shall extract the first connection on the +queue of pending connections, create a new socket and store it in libmodbus +context given in argument. If available, `accept4()` with `SOCK_CLOEXEC` will be +called instead of `accept()`. + +## Return value + +The function shall return a new socket if successful. +Otherwise it shall return -1 and set errno. + +## Example + +For detailed example, see unit-test-server.c source file in tests directory. + +```c +... + +ctx = modbus_new_tcp_pi("::0", "502"); +s = modbus_tcp_pi_listen(ctx, 1); +modbus_tcp_pi_accept(ctx, &s); + +... + +close(s); +modbus_free(ctx); +``` + +## See also + +- [modbus_tcp_accept](modbus_tcp_accept.md) +- [modbus_tcp_listen](modbus_tcp_listen.md) +- [modbus_tcp_pi_listen](modbus_tcp_pi_listen.md) diff --git a/doc/modbus_tcp_pi_listen.txt b/docs/modbus_tcp_pi_listen.md similarity index 51% rename from doc/modbus_tcp_pi_listen.txt rename to docs/modbus_tcp_pi_listen.md index 2a501ee6c..a47fe68ce 100644 --- a/doc/modbus_tcp_pi_listen.txt +++ b/docs/modbus_tcp_pi_listen.md @@ -1,41 +1,35 @@ -modbus_tcp_pi_listen(3) -======================= +# modbus_tcp_pi_listen +## Name -NAME ----- modbus_tcp_pi_listen - create and listen a TCP PI Modbus socket (IPv6) +## Synopsis -SYNOPSIS --------- -*int modbus_tcp_pi_listen(modbus_t *'ctx', int 'nb_connection');* +```c +int modbus_tcp_pi_listen(modbus_t *ctx, int nb_connection); +``` +## Description -DESCRIPTION ------------ The *modbus_tcp_pi_listen()* function shall create a socket and listen to -maximum _nb_connection_ incoming connections on the specified nodes. The -context *ctx* must be allocated and initialized with linkmb:modbus_new_tcp_pi[3] +maximum `nb_connection` incoming connections on the specified nodes. The +context *ctx* must be allocated and initialized with [modbus_new_tcp_pi](modbus_new_tcp_pi.md) before to set the node to listen, if node is set to NULL or '0.0.0.0', any addresses will be listen. +## Return value -RETURN VALUE ------------- The function shall return a new socket if successful. Otherwise it shall return -1 and set errno. - -EXAMPLE -------- +## Example For detailed examples, see source files in tests directory: - unit-test-server.c, simple but handle only one connection -[source,c] -------------------- +```c ... ctx = modbus_new_tcp_pi("::0", "502"); @@ -44,24 +38,18 @@ modbus_tcp_pi_accept(ctx, &s); for (;;) { rc = modbus_receive(ctx, query); - modbus_replay(ctx, query, rc, mb_mapping); + modbus_reply(ctx, query, rc, mb_mapping); } ... -mclose(s); +close(s); modbus_free(ctx); -------------------- +``` - bandwidth-server-many-up.c, handles several connections at once +## See also -SEE ALSO --------- -linkmb:modbus_new_tcp_pi[3] -linkmb:modbus_tcp_pi_accept[3] -linkmb:modbus_tcp_listen[3] - -AUTHORS -------- -The libmodbus documentation was written by Stéphane Raimbault - +- [modbus_new_tcp_pi](modbus_new_tcp_pi.md) +- [modbus_tcp_pi_accept](modbus_tcp_pi_accept.md) +- [modbus_tcp_listen](modbus_tcp_listen.md) diff --git a/docs/modbus_write_and_read_registers.md b/docs/modbus_write_and_read_registers.md new file mode 100644 index 000000000..0283ab488 --- /dev/null +++ b/docs/modbus_write_and_read_registers.md @@ -0,0 +1,44 @@ +# modbus_write_and_read_registers + +## Name + +modbus_write_and_read_registers - write and read many registers in a single transaction + +## Synopsis + +```c +int modbus_write_and_read_registers( + modbus_t *ctx, + int write_addr, int write_nb, const uint16_t *src, + int read_addr, int read_nb, uint16_t *dest +); +``` + +## Description + +The *modbus_write_and_read_registers()* function shall write the content of the +`write_nb` holding registers from the array 'src' to the address `write_addr` of +the remote device then shall read the content of the `read_nb` holding registers +to the address `read_addr` of the remote device. The result of reading is stored +in `dest` array as word values (16 bits). + +You must take care to allocate enough memory to store the results in `dest` +(at least `nb * sizeof(uint16_t)`). + +The function uses the Modbus function code 0x17 (write/read registers). + +## Return value + +The function shall return the number of read registers if successful. Otherwise +it shall return -1 and set errno. + +## Errors + +- *EINVAL*, the context is NULL, `src` or `dest` is NULL, or `write_nb` / `read_nb` is less than 1. +- *EMBXILVAL*, too many registers requested, Too many registers to write + +## See also + +- [modbus_read_registers](modbus_read_registers.md) +- [modbus_write_register](modbus_write_register.md) +- [modbus_write_registers](modbus_write_registers.md) diff --git a/docs/modbus_write_bit.md b/docs/modbus_write_bit.md new file mode 100644 index 000000000..ee3d2b2a9 --- /dev/null +++ b/docs/modbus_write_bit.md @@ -0,0 +1,28 @@ +# modbus_write_bit + +## Name + +modbus_write_bit - write a single bit + +## Synopsis + +```c +int modbus_write_bit(modbus_t *ctx, int addr, int status); +``` + +## Description + +The *modbus_write_bit()* function shall write the status of `status` at the +address `addr` of the remote device. The value must be set to `TRUE` or `FALSE`. + +The function uses the Modbus function code 0x05 (force single coil). + +## Return value + +The function shall return 1 if successful. Otherwise it shall return -1 and set +errno. + +## See also + +- [modbus_read_bits](modbus_read_bits.md) +- [modbus_write_bits](modbus_write_bits.md) diff --git a/docs/modbus_write_bits.md b/docs/modbus_write_bits.md new file mode 100644 index 000000000..e0b772d22 --- /dev/null +++ b/docs/modbus_write_bits.md @@ -0,0 +1,38 @@ +# modbus_write_bits + +## Name + +modbus_write_bits - write many bits + +## Synopsis + +```c +int modbus_write_bits(modbus_t *ctx, int addr, int nb, const uint8_t *src); +``` + +## Description + +The *modbus_write_bits()* function shall write the status of the `nb` bits +(coils) from `src` at the address `addr` of the remote device. The +`src` array must contain bytes set to `TRUE` or `FALSE`. + +The `src` array must be allocated with at least `nb` elements. It is the +caller's responsibility to ensure the buffer is large enough to hold all the +bits to be written. + +The function uses the Modbus function code 0x0F (force multiple coils). + +## Return value + +The function shall return the number of written bits if successful. Otherwise it +shall return -1 and set errno. + +## Errors + +- *EINVAL*, the `ctx` or `src` argument is NULL, or `nb` is less than 1. +- *EMBXILVAL*, writing too many bits (nb > MODBUS_MAX_WRITE_BITS). + +## See also + +- [modbus_read_bits](modbus_read_bits.md) +- [modbus_write_bit](modbus_write_bit.md) diff --git a/docs/modbus_write_register.md b/docs/modbus_write_register.md new file mode 100644 index 000000000..244c978ef --- /dev/null +++ b/docs/modbus_write_register.md @@ -0,0 +1,28 @@ +# modbus_write_register + +## Name + +modbus_write_register - write a single register + +## Synopsis + +```c +int modbus_write_register(modbus_t *ctx, int addr, const uint16_t value); +``` + +## Description + +The *modbus_write_register()* function shall write the value of `value` +holding registers at the address `addr` of the remote device. + +The function uses the Modbus function code 0x06 (preset single register). + +## Return value + +The function shall return 1 if successful. Otherwise it shall return -1 and set +errno. + +## See also + +- [modbus_read_registers](modbus_read_registers.md) +- [modbus_write_registers](modbus_write_registers.md) diff --git a/docs/modbus_write_registers.md b/docs/modbus_write_registers.md new file mode 100644 index 000000000..5193dc826 --- /dev/null +++ b/docs/modbus_write_registers.md @@ -0,0 +1,37 @@ +# modbus_write_registers + +## Name + +modbus_write_registers - write many registers + +## Synopsis + +```c +int modbus_write_registers(modbus_t *ctx, int addr, int nb, const uint16_t *src); +``` + +## Description + +The *modbus_write_registers()* function shall write the content of the `nb` +holding registers from the array `src` at address `addr` of the remote device. + +The `src` array must be allocated with at least `nb` elements. It is the +caller's responsibility to ensure the buffer is large enough to hold all the +registers to be written. + +The function uses the Modbus function code 0x10 (preset multiple registers). + +## Return value + +The function shall return the number of written registers if +successful. Otherwise it shall return -1 and set errno. + +## Errors + +- *EINVAL*, the `ctx` or `src` argument is NULL, or `nb` is less than 1. +- *EMBXILVAL*, writing too many registers (nb > MODBUS_MAX_WRITE_REGISTERS). + +## See also + +- [modbus_write_register](modbus_write_register.md) +- [modbus_read_registers](modbus_read_registers.md) diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 000000000..61358fda1 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,17 @@ +site_name: libmodbus + +theme: + name: material + font: false + +markdown_extensions: + - md_in_html + - attr_list + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences + - pymdownx.emoji: + emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:materialx.emoji.to_svg diff --git a/run-coverage.sh b/run-coverage.sh new file mode 100755 index 000000000..750a2ca4b --- /dev/null +++ b/run-coverage.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -e + +./configure --quiet +make clean --quiet +make CFLAGS="--coverage -O0" LDFLAGS="--coverage" --quiet +make coverage || true +open coverage/index.html 2>/dev/null || xdg-open coverage/index.html 2>/dev/null || echo "Report: coverage/index.html" diff --git a/src/Makefile.am b/src/Makefile.am index 551fe4328..157e9200c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -7,7 +7,7 @@ AM_CPPFLAGS = \ -DLIBEXECDIR=\""$(libexecdir)"\" \ -I${top_srcdir}/src -AM_CFLAGS = ${my_CFLAGS} +AM_CFLAGS = $(LIBMODBUSCFLAGS) $(WARNING_CFLAGS) libmodbus_la_SOURCES = \ modbus.c \ diff --git a/src/modbus-data.c b/src/modbus-data.c index 5b0a6ed60..c71590841 100644 --- a/src/modbus-data.c +++ b/src/modbus-data.c @@ -1,11 +1,12 @@ /* - * Copyright © 2010-2014 Stéphane Raimbault + * Copyright © Stéphane Raimbault * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include +// clang-format off #ifndef _MSC_VER # include #else @@ -13,7 +14,6 @@ #endif #include -#include #if defined(_WIN32) # include @@ -25,50 +25,7 @@ #include "modbus.h" -#if defined(HAVE_BYTESWAP_H) -# include -#endif - -#if defined(__APPLE__) -# include -# define bswap_16 OSSwapInt16 -# define bswap_32 OSSwapInt32 -# define bswap_64 OSSwapInt64 -#endif - -#if defined(__GNUC__) -# define GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__ * 10) -# if GCC_VERSION >= 430 -// Since GCC >= 4.30, GCC provides __builtin_bswapXX() alternatives so we switch to them -# undef bswap_32 -# define bswap_32 __builtin_bswap32 -# endif -# if GCC_VERSION >= 480 -# undef bswap_16 -# define bswap_16 __builtin_bswap16 -# endif -#endif - -#if defined(_MSC_VER) && (_MSC_VER >= 1400) -# define bswap_32 _byteswap_ulong -# define bswap_16 _byteswap_ushort -#endif - -#if !defined(bswap_16) -# warning "Fallback on C functions for bswap_16" -static inline uint16_t bswap_16(uint16_t x) -{ - return (x >> 8) | (x << 8); -} -#endif - -#if !defined(bswap_32) -# warning "Fallback on C functions for bswap_32" -static inline uint32_t bswap_32(uint32_t x) -{ - return (bswap_16(x & 0xffff) << 16) | (bswap_16(x >> 16)); -} -#endif +// clang-format on /* Sets many bits from a single byte value (all 8 bits of the byte value are set) */ @@ -76,14 +33,16 @@ void modbus_set_bits_from_byte(uint8_t *dest, int idx, const uint8_t value) { int i; - for (i=0; i < 8; i++) { - dest[idx+i] = (value & (1 << i)) ? 1 : 0; + for (i = 0; i < 8; i++) { + dest[idx + i] = (value & (1 << i)) ? 1 : 0; } } /* Sets many bits from a table of bytes (only the bits between idx and idx + nb_bits are set) */ -void modbus_set_bits_from_bytes(uint8_t *dest, int idx, unsigned int nb_bits, +void modbus_set_bits_from_bytes(uint8_t *dest, + int idx, + unsigned int nb_bits, const uint8_t *tab_byte) { unsigned int i; @@ -99,20 +58,18 @@ void modbus_set_bits_from_bytes(uint8_t *dest, int idx, unsigned int nb_bits, /* Gets the byte value from many bits. To obtain a full byte, set nb_bits to 8. */ -uint8_t modbus_get_byte_from_bits(const uint8_t *src, int idx, - unsigned int nb_bits) +uint8_t modbus_get_byte_from_bits(const uint8_t *src, int idx, unsigned int nb_bits) { unsigned int i; uint8_t value = 0; + /* A byte contains 8 bits at most */ if (nb_bits > 8) { - /* Assert is ignored if NDEBUG is set */ - assert(nb_bits < 8); nb_bits = 8; } - for (i=0; i < nb_bits; i++) { - value |= (src[idx+i] << i); + for (i = 0; i < nb_bits; i++) { + value |= (src[idx + i] << i); } return value; @@ -123,9 +80,22 @@ float modbus_get_float_abcd(const uint16_t *src) { float f; uint32_t i; - - i = ntohl(((uint32_t)src[0] << 16) + src[1]); - memcpy(&f, &i, sizeof(float)); + uint8_t a, b, c, d; + + // Mind: src contains 16-bit numbers in processor-endianness, hence + // we use shift operations and do not access memory directly + a = (src[0] >> 8) & 0xFF; // high byte of first word + b = (src[0] >> 0) & 0xFF; // low byte of first word + c = (src[1] >> 8) & 0xFF; // high byte of second word + d = (src[1] >> 0) & 0xFF; // low byte of second word + + // we assemble 32bit integer always in abcd order via shift operations + // Cast to uint32_t before shifting: a..d promote to signed int, and + // "a << 24" with a >= 0x80 would overflow a 32-bit signed int (undefined + // behavior). Assembling in unsigned width keeps it well-defined. The same + // applies to the identical assembly in the dcba/badc/cdab variants below. + i = ((uint32_t) a << 24) | ((uint32_t) b << 16) | ((uint32_t) c << 8) | d; + memcpy(&f, &i, 4); return f; } @@ -135,9 +105,17 @@ float modbus_get_float_dcba(const uint16_t *src) { float f; uint32_t i; + uint8_t a, b, c, d; + + // byte order is defined when reading from src: dcba + d = (src[0] >> 8) & 0xFF; + c = (src[0] >> 0) & 0xFF; + b = (src[1] >> 8) & 0xFF; + a = (src[1] >> 0) & 0xFF; - i = ntohl(bswap_32((((uint32_t)src[0]) << 16) + src[1])); - memcpy(&f, &i, sizeof(float)); + // we assemble 32bit integer always in abcd order via shift operations + i = ((uint32_t) a << 24) | ((uint32_t) b << 16) | ((uint32_t) c << 8) | d; + memcpy(&f, &i, 4); return f; } @@ -147,9 +125,17 @@ float modbus_get_float_badc(const uint16_t *src) { float f; uint32_t i; + uint8_t a, b, c, d; - i = ntohl((uint32_t)(bswap_16(src[0]) << 16) + bswap_16(src[1])); - memcpy(&f, &i, sizeof(float)); + // byte order is defined when reading from src: badc + b = (src[0] >> 8) & 0xFF; + a = (src[0] >> 0) & 0xFF; + d = (src[1] >> 8) & 0xFF; + c = (src[1] >> 0) & 0xFF; + + // we assemble 32bit integer always in abcd order via shift operations + i = ((uint32_t) a << 24) | ((uint32_t) b << 16) | ((uint32_t) c << 8) | d; + memcpy(&f, &i, 4); return f; } @@ -159,9 +145,17 @@ float modbus_get_float_cdab(const uint16_t *src) { float f; uint32_t i; + uint8_t a, b, c, d; + + // byte order is defined when reading from src: cdab + c = (src[0] >> 8) & 0xFF; + d = (src[0] >> 0) & 0xFF; + a = (src[1] >> 8) & 0xFF; + b = (src[1] >> 0) & 0xFF; - i = ntohl((((uint32_t)src[1]) << 16) + src[0]); - memcpy(&f, &i, sizeof(float)); + // we assemble 32bit integer always in abcd order via shift operations + i = ((uint32_t) a << 24) | ((uint32_t) b << 16) | ((uint32_t) c << 8) | d; + memcpy(&f, &i, 4); return f; } @@ -169,65 +163,83 @@ float modbus_get_float_cdab(const uint16_t *src) /* DEPRECATED - Get a float from 4 bytes in sort of Modbus format */ float modbus_get_float(const uint16_t *src) { - float f; - uint32_t i; - - i = (((uint32_t)src[1]) << 16) + src[0]; - memcpy(&f, &i, sizeof(float)); - - return f; + return modbus_get_float_cdab(src); } /* Set a float to 4 bytes for Modbus w/o any conversion (ABCD) */ void modbus_set_float_abcd(float f, uint16_t *dest) { + // Use memcpy for the type conversion: accessing a float through a uint32_t + // lvalue (the old *(uint32_t*)&f trick) violates strict aliasing and is + // undefined behavior. This mirrors the modbus_get_float_* functions and + // compiles to the same load. Same applies to the dcba/badc/cdab variants. uint32_t i; + uint8_t a, b, c, d; + + memcpy(&i, &f, 4); + + a = (i >> 24) & 0xFF; + b = (i >> 16) & 0xFF; + c = (i >> 8) & 0xFF; + d = (i >> 0) & 0xFF; - memcpy(&i, &f, sizeof(uint32_t)); - i = htonl(i); - dest[0] = (uint16_t)(i >> 16); - dest[1] = (uint16_t)i; + dest[0] = (a << 8) | b; + dest[1] = (c << 8) | d; } /* Set a float to 4 bytes for Modbus with byte and word swap conversion (DCBA) */ void modbus_set_float_dcba(float f, uint16_t *dest) { uint32_t i; + uint8_t a, b, c, d; - memcpy(&i, &f, sizeof(uint32_t)); - i = bswap_32(htonl(i)); - dest[0] = (uint16_t)(i >> 16); - dest[1] = (uint16_t)i; + memcpy(&i, &f, 4); + + a = (i >> 24) & 0xFF; + b = (i >> 16) & 0xFF; + c = (i >> 8) & 0xFF; + d = (i >> 0) & 0xFF; + + dest[0] = (d << 8) | c; + dest[1] = (b << 8) | a; } /* Set a float to 4 bytes for Modbus with byte swap conversion (BADC) */ void modbus_set_float_badc(float f, uint16_t *dest) { uint32_t i; + uint8_t a, b, c, d; + + memcpy(&i, &f, 4); + + a = (i >> 24) & 0xFF; + b = (i >> 16) & 0xFF; + c = (i >> 8) & 0xFF; + d = (i >> 0) & 0xFF; - memcpy(&i, &f, sizeof(uint32_t)); - i = htonl(i); - dest[0] = (uint16_t)bswap_16(i >> 16); - dest[1] = (uint16_t)bswap_16(i & 0xFFFF); + dest[0] = (b << 8) | a; + dest[1] = (d << 8) | c; } /* Set a float to 4 bytes for Modbus with word swap conversion (CDAB) */ void modbus_set_float_cdab(float f, uint16_t *dest) { uint32_t i; + uint8_t a, b, c, d; - memcpy(&i, &f, sizeof(uint32_t)); - i = htonl(i); - dest[0] = (uint16_t)i; - dest[1] = (uint16_t)(i >> 16); + memcpy(&i, &f, 4); + + a = (i >> 24) & 0xFF; + b = (i >> 16) & 0xFF; + c = (i >> 8) & 0xFF; + d = (i >> 0) & 0xFF; + + dest[0] = (c << 8) | d; + dest[1] = (a << 8) | b; } /* DEPRECATED - Set a float to 4 bytes in a sort of Modbus format! */ void modbus_set_float(float f, uint16_t *dest) { - uint32_t i; - - memcpy(&i, &f, sizeof(uint32_t)); - dest[0] = (uint16_t)i; - dest[1] = (uint16_t)(i >> 16); + modbus_set_float_cdab(f, dest); } diff --git a/src/modbus-private.h b/src/modbus-private.h index 198baeffd..ea83187f7 100644 --- a/src/modbus-private.h +++ b/src/modbus-private.h @@ -1,5 +1,5 @@ /* - * Copyright © 2010-2012 Stéphane Raimbault + * Copyright © Stéphane Raimbault * * SPDX-License-Identifier: LGPL-2.1-or-later */ @@ -7,6 +7,7 @@ #ifndef MODBUS_PRIVATE_H #define MODBUS_PRIVATE_H +// clang-format off #ifndef _MSC_VER # include # include @@ -15,8 +16,9 @@ # include typedef int ssize_t; #endif -#include +// clang-format on #include +#include #include "modbus.h" @@ -36,11 +38,11 @@ MODBUS_BEGIN_DECLS #define _MODBUS_EXCEPTION_RSP_LENGTH 5 /* Timeouts in microsecond (0.5 s) */ -#define _RESPONSE_TIMEOUT 500000 -#define _BYTE_TIMEOUT 500000 +#define _RESPONSE_TIMEOUT 500000 +#define _BYTE_TIMEOUT 500000 typedef enum { - _MODBUS_BACKEND_TYPE_RTU=0, + _MODBUS_BACKEND_TYPE_RTU = 0, _MODBUS_BACKEND_TYPE_TCP } modbus_backend_type_t; @@ -69,24 +71,26 @@ typedef struct _modbus_backend { unsigned int header_length; unsigned int checksum_length; unsigned int max_adu_length; - int (*set_slave) (modbus_t *ctx, int slave); - int (*build_request_basis) (modbus_t *ctx, int function, int addr, - int nb, uint8_t *req); - int (*build_response_basis) (sft_t *sft, uint8_t *rsp); - int (*prepare_response_tid) (const uint8_t *req, int *req_length); - int (*send_msg_pre) (uint8_t *req, int req_length); - ssize_t (*send) (modbus_t *ctx, const uint8_t *req, int req_length); - int (*receive) (modbus_t *ctx, uint8_t *req); - ssize_t (*recv) (modbus_t *ctx, uint8_t *rsp, int rsp_length); - int (*check_integrity) (modbus_t *ctx, uint8_t *msg, - const int msg_length); - int (*pre_check_confirmation) (modbus_t *ctx, const uint8_t *req, - const uint8_t *rsp, int rsp_length); - int (*connect) (modbus_t *ctx); - void (*close) (modbus_t *ctx); - int (*flush) (modbus_t *ctx); - int (*select) (modbus_t *ctx, fd_set *rset, struct timeval *tv, int msg_length); - void (*free) (modbus_t *ctx); + int (*set_slave)(modbus_t *ctx, int slave); + int (*build_request_basis)( + modbus_t *ctx, int function, int addr, int nb, uint8_t *req); + int (*build_response_basis)(sft_t *sft, uint8_t *rsp); + int (*get_response_tid)(const uint8_t *req); + int (*send_msg_pre)(uint8_t *req, int req_length); + ssize_t (*send)(modbus_t *ctx, const uint8_t *req, int req_length); + int (*receive)(modbus_t *ctx, uint8_t *req); + ssize_t (*recv)(modbus_t *ctx, uint8_t *rsp, int rsp_length); + int (*check_integrity)(modbus_t *ctx, uint8_t *msg, const int msg_length); + int (*pre_check_confirmation)(modbus_t *ctx, + const uint8_t *req, + const uint8_t *rsp, + int rsp_length); + int (*connect)(modbus_t *ctx); + unsigned int (*is_connected)(modbus_t *ctx); + void (*close)(modbus_t *ctx); + int (*flush)(modbus_t *ctx); + int (*select)(modbus_t *ctx, fd_set *rset, struct timeval *tv, int msg_length); + void (*free)(modbus_t *ctx); } modbus_backend_t; struct _modbus { @@ -96,6 +100,7 @@ struct _modbus { int s; int debug; int error_recovery; + int quirks; struct timeval response_timeout; struct timeval byte_timeout; struct timeval indication_timeout; @@ -113,4 +118,4 @@ size_t strlcpy(char *dest, const char *src, size_t dest_size); MODBUS_END_DECLS -#endif /* MODBUS_PRIVATE_H */ +#endif /* MODBUS_PRIVATE_H */ diff --git a/src/modbus-rtu-private.h b/src/modbus-rtu-private.h index 1c7d4781a..bf6781662 100644 --- a/src/modbus-rtu-private.h +++ b/src/modbus-rtu-private.h @@ -1,5 +1,5 @@ /* - * Copyright © 2001-2011 Stéphane Raimbault + * Copyright © Stéphane Raimbault * * SPDX-License-Identifier: LGPL-2.1-or-later */ @@ -16,14 +16,21 @@ #if defined(_WIN32) #include #else +#if defined(HAVE_STRUCT_TERMIOS2) +/* Prevent duplicate definitions of "struct termios" + * when including and . */ +#define termios +#include +#undef termios +#endif #include #endif -#define _MODBUS_RTU_HEADER_LENGTH 1 -#define _MODBUS_RTU_PRESET_REQ_LENGTH 6 -#define _MODBUS_RTU_PRESET_RSP_LENGTH 2 +#define _MODBUS_RTU_HEADER_LENGTH 1 +#define _MODBUS_RTU_PRESET_REQ_LENGTH 6 +#define _MODBUS_RTU_PRESET_RSP_LENGTH 2 -#define _MODBUS_RTU_CHECKSUM_LENGTH 2 +#define _MODBUS_RTU_CHECKSUM_LENGTH 2 #if defined(_WIN32) #if !defined(ENOTSUP) @@ -32,6 +39,7 @@ /* WIN32: struct containing serial handle and a receive buffer */ #define PY_BUF_SIZE 512 + struct win32_ser { /* File handle */ HANDLE fd; @@ -56,6 +64,9 @@ typedef struct _modbus_rtu { #if defined(_WIN32) struct win32_ser w_ser; DCB old_dcb; +#elif defined(HAVE_STRUCT_TERMIOS2) + /* Save old termios settings */ + struct termios2 old_tios; #else /* Save old termios settings */ struct termios old_tios; @@ -67,7 +78,7 @@ typedef struct _modbus_rtu { int rts; int rts_delay; int onebyte_time; - void (*set_rts) (modbus_t *ctx, int on); + void (*set_rts)(modbus_t *ctx, int on); #endif /* To handle many slaves on the same link */ int confirmation_to_ignore; diff --git a/src/modbus-rtu.c b/src/modbus-rtu.c index 56c812ca3..87e08874e 100644 --- a/src/modbus-rtu.c +++ b/src/modbus-rtu.c @@ -1,23 +1,25 @@ /* - * Copyright © 2001-2011 Stéphane Raimbault + * Copyright © Stéphane Raimbault * * SPDX-License-Identifier: LGPL-2.1-or-later */ -#include -#include #include #include +#include +#include #include #ifndef _MSC_VER #include #endif -#include - +#if defined(HAVE_STRUCT_TERMIOS2) +#include +#endif #include "modbus-private.h" +#include -#include "modbus-rtu.h" #include "modbus-rtu-private.h" +#include "modbus-rtu.h" #if HAVE_DECL_TIOCSRS485 || HAVE_DECL_TIOCM_RTS #include @@ -29,70 +31,56 @@ /* Table of CRC values for high-order byte */ static const uint8_t table_crc_hi[] = { - 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, - 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, - 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, - 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, - 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, - 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, - 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, - 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, - 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, - 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, - 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, - 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, - 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, - 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, - 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, - 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, - 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, - 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, - 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, - 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, - 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, - 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, - 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, - 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, - 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, - 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40 -}; + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, + 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, + 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, + 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, + 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, + 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, + 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, + 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40}; /* Table of CRC values for low-order byte */ static const uint8_t table_crc_lo[] = { - 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, - 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, - 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, - 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, - 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, - 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, - 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, - 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, - 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, - 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, - 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, - 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, - 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, - 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, - 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, - 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, - 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, - 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, - 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, - 0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, - 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, - 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, - 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, - 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, - 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, - 0x43, 0x83, 0x41, 0x81, 0x80, 0x40 -}; + 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, + 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, + 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, + 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, + 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, + 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, + 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, + 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, + 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, + 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, + 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, + 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, + 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7, + 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, + 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, + 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, + 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, + 0x4C, 0x8C, 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 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) { + int max_slave = (ctx->quirks & MODBUS_QUIRK_MAX_SLAVE) ? 255 : 247; + /* Broadcast address is 0 (MODBUS_BROADCAST_ADDRESS) */ - if (slave >= 0 && slave <= 247) { + if (slave >= 0 && slave <= max_slave) { ctx->slave = slave; } else { errno = EINVAL; @@ -103,9 +91,8 @@ static int _modbus_set_slave(modbus_t *ctx, int slave) } /* Builds a RTU request header */ -static int _modbus_rtu_build_request_basis(modbus_t *ctx, int function, - int addr, int nb, - uint8_t *req) +static int _modbus_rtu_build_request_basis( + modbus_t *ctx, int function, int addr, int nb, uint8_t *req) { assert(ctx->slave != -1); req[0] = ctx->slave; @@ -133,21 +120,20 @@ static uint16_t crc16(uint8_t *buffer, uint16_t buffer_length) { uint8_t crc_hi = 0xFF; /* high CRC byte initialized */ uint8_t crc_lo = 0xFF; /* low CRC byte initialized */ - unsigned int i; /* will index into CRC lookup */ + unsigned int i; /* will index into CRC lookup */ /* pass through message buffer */ while (buffer_length--) { - i = crc_hi ^ *buffer++; /* calculate the CRC */ - crc_hi = crc_lo ^ table_crc_hi[i]; - crc_lo = table_crc_lo[i]; + i = crc_lo ^ *buffer++; /* calculate the CRC */ + crc_lo = crc_hi ^ table_crc_hi[i]; + crc_hi = table_crc_lo[i]; } return (crc_hi << 8 | crc_lo); } -static int _modbus_rtu_prepare_response_tid(const uint8_t *req, int *req_length) +static int _modbus_rtu_get_response_tid(const uint8_t *req) { - (*req_length) -= _MODBUS_RTU_CHECKSUM_LENGTH; /* No TID */ return 0; } @@ -155,8 +141,11 @@ static int _modbus_rtu_prepare_response_tid(const uint8_t *req, int *req_length) static int _modbus_rtu_send_msg_pre(uint8_t *req, int req_length) { uint16_t crc = crc16(req, req_length); - req[req_length++] = crc >> 8; + + /* According to the MODBUS specs (p. 14), the low order byte of the CRC comes + * first in the RTU message */ req[req_length++] = crc & 0x00FF; + req[req_length++] = crc >> 8; return req_length; } @@ -181,8 +170,7 @@ static void win32_ser_init(struct win32_ser *ws) } /* 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) +static int win32_ser_select(struct win32_ser *ws, int max_len, const struct timeval *tv) { COMMTIMEOUTS comm_to; unsigned int msec = 0; @@ -232,8 +220,7 @@ static int win32_ser_select(struct win32_ser *ws, int max_len, } } -static int win32_ser_read(struct win32_ser *ws, uint8_t *p_msg, - unsigned int max_len) +static int win32_ser_read(struct win32_ser *ws, uint8_t *p_msg, unsigned int max_len) { unsigned int n = ws->n_bytes; @@ -257,13 +244,22 @@ static void _modbus_rtu_ioctl_rts(modbus_t *ctx, int on) int fd = ctx->s; int flags; - ioctl(fd, TIOCMGET, &flags); + if (ioctl(fd, TIOCMGET, &flags) == -1) { + if (ctx->debug) { + fprintf(stderr, "ERROR Can't get RTS line state (%s)\n", strerror(errno)); + } + return; + } if (on) { flags |= TIOCM_RTS; } else { flags &= ~TIOCM_RTS; } - ioctl(fd, TIOCMSET, &flags); + if (ioctl(fd, TIOCMSET, &flags) == -1) { + if (ctx->debug) { + fprintf(stderr, "ERROR Can't set RTS line state (%s)\n", strerror(errno)); + } + } } #endif @@ -272,7 +268,9 @@ static ssize_t _modbus_rtu_send(modbus_t *ctx, const uint8_t *req, int req_lengt #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; + 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; @@ -283,12 +281,29 @@ static ssize_t _modbus_rtu_send(modbus_t *ctx, const uint8_t *req, int req_lengt fprintf(stderr, "Sending request using RTS signal\n"); } + uint64_t total_delay; + 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); + /* Compute the post-send delay in a wide unsigned type to avoid the + signed overflow that occurs with a very low baud (large + onebyte_time) and a large request, then clamp to a sane maximum. */ + total_delay = (uint64_t) ctx_rtu->onebyte_time * (uint64_t) req_length + + (uint64_t) ctx_rtu->rts_delay; + if (total_delay > 1000000000ULL) { + total_delay = 1000000000ULL; + } + /* POSIX allows usleep() to fail with EINVAL for values >= 1 second + so sleep in chunks below that limit */ + while (total_delay > 0) { + useconds_t delay = + (total_delay > 999999ULL) ? 999999 : (useconds_t) total_delay; + usleep(delay); + total_delay -= delay; + } ctx_rtu->set_rts(ctx, ctx_rtu->rts != MODBUS_RTU_RTS_UP); return size; @@ -307,7 +322,7 @@ static int _modbus_rtu_receive(modbus_t *ctx, uint8_t *req) modbus_rtu_t *ctx_rtu = ctx->backend_data; if (ctx_rtu->confirmation_to_ignore) { - _modbus_receive_msg(ctx, req, MSG_CONFIRMATION); + (void) _modbus_receive_msg(ctx, req, MSG_CONFIRMATION); /* Ignore errors and reset the flag */ ctx_rtu->confirmation_to_ignore = FALSE; rc = 0; @@ -327,7 +342,7 @@ static int _modbus_rtu_receive(modbus_t *ctx, uint8_t *req) 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); + return win32_ser_read(&((modbus_rtu_t *) ctx->backend_data)->w_ser, rsp, rsp_length); #else return read(ctx->s, rsp, rsp_length); #endif @@ -335,8 +350,10 @@ static ssize_t _modbus_rtu_recv(modbus_t *ctx, uint8_t *rsp, int rsp_length) 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) +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) */ @@ -344,7 +361,8 @@ static int _modbus_rtu_pre_check_confirmation(modbus_t *ctx, const uint8_t *req, if (ctx->debug) { fprintf(stderr, "The responding slave %d isn't the requested slave %d\n", - rsp[0], req[0]); + rsp[0], + req[0]); } errno = EMBBADSLAVE; return -1; @@ -353,36 +371,25 @@ static int _modbus_rtu_pre_check_confirmation(modbus_t *ctx, const uint8_t *req, } } -/* The check_crc16 function shall return 0 is the message is ignored and the +/* The check_crc16 function shall return 0 if the message is ignored and the message length if the CRC is valid. Otherwise it shall return -1 and set errno to EMBBADCRC. */ -static int _modbus_rtu_check_integrity(modbus_t *ctx, uint8_t *msg, - const int msg_length) +static int _modbus_rtu_check_integrity(modbus_t *ctx, uint8_t *msg, const int msg_length) { uint16_t crc_calculated; uint16_t crc_received; int slave = msg[0]; - /* Filter on the Modbus unit identifier (slave) in RTU mode to avoid useless - * CRC computing. */ - if (slave != ctx->slave && slave != MODBUS_BROADCAST_ADDRESS) { - if (ctx->debug) { - printf("Request for slave %d ignored (not %d)\n", slave, ctx->slave); - } - /* Following call to check_confirmation handles this error */ - return 0; - } - crc_calculated = crc16(msg, msg_length - 2); - crc_received = (msg[msg_length - 2] << 8) | msg[msg_length - 1]; + crc_received = (msg[msg_length - 1] << 8) | msg[msg_length - 2]; /* Check CRC of msg */ - if (crc_calculated == crc_received) { - return msg_length; - } else { + if (crc_calculated != crc_received) { if (ctx->debug) { - fprintf(stderr, "ERROR CRC received 0x%0X != CRC calculated 0x%0X\n", - crc_received, crc_calculated); + fprintf(stderr, + "ERROR CRC received 0x%0X != CRC calculated 0x%0X\n", + crc_received, + crc_calculated); } if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) { @@ -391,27 +398,35 @@ static int _modbus_rtu_check_integrity(modbus_t *ctx, uint8_t *msg, errno = EMBBADCRC; return -1; } + + /* Filter on the Modbus unit identifier (slave) in RTU mode */ + if (slave != ctx->slave && slave != MODBUS_BROADCAST_ADDRESS) { + if (ctx->debug) { + printf("Request for slave %d ignored (not %d)\n", slave, ctx->slave); + } + /* Following call to check_confirmation handles this error */ + return 0; + } + + return msg_length; } /* Sets up a serial port for RTU communications */ +#if defined(_WIN32) 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); + 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 */ @@ -419,19 +434,16 @@ static int _modbus_rtu_connect(modbus_t *ctx) /* 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); + 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()); + fprintf(stderr, + "ERROR Can't open the device %s (LastError %d)\n", + ctx_rtu->device, + (int) GetLastError()); } return -1; } @@ -440,8 +452,9 @@ static int _modbus_rtu_connect(modbus_t *ctx) 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()); + 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; @@ -452,69 +465,7 @@ static int _modbus_rtu_connect(modbus_t *ctx) 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); - } - } + dcb.BaudRate = ctx_rtu->baud; /* Data bits */ switch (ctx_rtu->data_bit) { @@ -568,44 +519,25 @@ static int _modbus_rtu_connect(modbus_t *ctx) /* 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()); + 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); + return 0; +} +#else - memset(&tios, 0, sizeof(struct termios)); +#ifndef HAVE_STRUCT_TERMIOS2 +static speed_t _get_termios_speed(int baud, int debug) +{ + speed_t speed; - /* C_ISPEED Input baud (new interface) - C_OSPEED Output baud (new interface) - */ - switch (ctx_rtu->baud) { + switch (baud) { case 110: speed = B110; break; @@ -674,7 +606,7 @@ static int _modbus_rtu_connect(modbus_t *ctx) break; #endif #ifdef B1152000 - case 1152000: + case 1152000: speed = B1152000; break; #endif @@ -705,20 +637,107 @@ static int _modbus_rtu_connect(modbus_t *ctx) #endif default: speed = B9600; + if (debug) { + fprintf(stderr, "WARNING Unknown baud rate %d (B9600 used)\n", baud); + } + } + + return speed; +} +#endif + +/* POSIX */ +static int _modbus_rtu_connect(modbus_t *ctx) +{ +#ifdef HAVE_STRUCT_TERMIOS2 + struct termios2 tios; +#else + speed_t speed; + struct termios tios; +#endif + int flags; + 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); + } + + /* 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 + NONBLOCK option is set on the file via open or fcntl */ + flags = O_RDWR | O_NOCTTY | O_NONBLOCK; +#ifdef O_CLOEXEC + flags |= O_CLOEXEC; +#endif + + ctx->s = open(ctx_rtu->device, flags); + if (ctx->s < 0) { + if (ctx->debug) { + fprintf(stderr, + "ERROR Can't open the device %s (%s)\n", + ctx_rtu->device, + strerror(errno)); + } + return -1; + } + + /* Save */ +#ifdef HAVE_STRUCT_TERMIOS2 + if (ioctl(ctx->s, TCGETS2, &ctx_rtu->old_tios) < 0) { +#else + if (tcgetattr(ctx->s, &ctx_rtu->old_tios) < 0) { +#endif if (ctx->debug) { fprintf(stderr, - "WARNING Unknown baud rate %d for %s (B9600 used)\n", - ctx_rtu->baud, ctx_rtu->device); + "ERROR Can't save the termios settings of %s (%s)\n", + ctx_rtu->device, + strerror(errno)); } + close(ctx->s); + ctx->s = -1; + return -1; } + memset(&tios, 0, sizeof(tios)); + + /* C_ISPEED Input baud (new interface) + C_OSPEED Output baud (new interface) + */ + /* Set the baud rate */ - if ((cfsetispeed(&tios, speed) < 0) || - (cfsetospeed(&tios, speed) < 0)) { + +#ifdef HAVE_STRUCT_TERMIOS2 + tios.c_cflag |= BOTHER; /* Allow custom baud rate. */ + tios.c_ispeed = ctx_rtu->baud; /* Set input baud rate. */ + tios.c_ospeed = ctx_rtu->baud; /* Set output baud rate. */ +#else + /* + On MacOS, constants of baud rates are equal to the integer in argument but + that's not the case under Linux so we have to find the corresponding + constant. Without termios2, the list of possible values is limited + (no 14400 for example). + */ + if (9600 == B9600) { + speed = ctx_rtu->baud; + } else { + speed = _get_termios_speed(ctx_rtu->baud, ctx->debug); + } + + if ((cfsetispeed(&tios, speed) < 0) || (cfsetospeed(&tios, speed) < 0)) { close(ctx->s); ctx->s = -1; return -1; } +#endif /* C_CFLAG Control options CLOCAL Local line - do not change "owner" of port @@ -749,7 +768,7 @@ static int _modbus_rtu_connect(modbus_t *ctx) /* Stop bit (1 or 2) */ if (ctx_rtu->stop_bit == 1) - tios.c_cflag &=~ CSTOPB; + tios.c_cflag &= ~CSTOPB; else /* 2 */ tios.c_cflag |= CSTOPB; @@ -757,11 +776,11 @@ static int _modbus_rtu_connect(modbus_t *ctx) PARODD Use odd parity instead of even */ if (ctx_rtu->parity == 'N') { /* None */ - tios.c_cflag &=~ PARENB; + tios.c_cflag &= ~PARENB; } else if (ctx_rtu->parity == 'E') { /* Even */ tios.c_cflag |= PARENB; - tios.c_cflag &=~ PARODD; + tios.c_cflag &= ~PARODD; } else { /* Odd */ tios.c_cflag |= PARENB; @@ -842,8 +861,8 @@ static int _modbus_rtu_connect(modbus_t *ctx) ONCLR ant others needs OPOST to be enabled */ - /* Raw ouput */ - tios.c_oflag &=~ OPOST; + /* Raw output */ + tios.c_oflag &= ~OPOST; /* C_CC Control characters VMIN Minimum number of characters to read @@ -852,7 +871,7 @@ static int _modbus_rtu_connect(modbus_t *ctx) 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 + are ignored in canonical input mode or when the NONBLOCK option is set on the file via open or fcntl. VMIN specifies the minimum number of characters to read. If @@ -882,12 +901,28 @@ static int _modbus_rtu_connect(modbus_t *ctx) 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. + NONBLOCK option is set on the port with open or fcntl. */ - /* Unused because we use open with the NDELAY option */ + /* Unused because we use open with the NONBLOCK option */ tios.c_cc[VMIN] = 0; tios.c_cc[VTIME] = 0; +#ifdef HAVE_STRUCT_TERMIOS2 + if (ioctl(ctx->s, TCSETS2, &tios) < 0) { + close(ctx->s); + ctx->s = -1; + return -1; + } + if (ctx->debug) { + ioctl(ctx->s, TCGETS2, &tios); + if (tios.c_ispeed != (unsigned int)ctx_rtu->baud) { + fprintf(stderr, + "WARNING Failed to set baud rate %d (%d used)\n", + ctx_rtu->baud, + tios.c_ispeed); + } + } +#else if (tcsetattr(ctx->s, TCSANOW, &tios) < 0) { close(ctx->s); ctx->s = -1; @@ -897,6 +932,20 @@ static int _modbus_rtu_connect(modbus_t *ctx) return 0; } +#endif + +// FIXME Temporary solution before rewriting Windows RTU backend +static unsigned int _modbus_rtu_is_connected(modbus_t *ctx) +{ +#if defined(_WIN32) + modbus_rtu_t *ctx_rtu = ctx->backend_data; + + /* Check if file handle is valid */ + return ctx_rtu->w_ser.fd != INVALID_HANDLE_VALUE; +#else + return ctx->s >= 0; +#endif +} int modbus_rtu_set_serial_mode(modbus_t *ctx, int mode) { @@ -1036,9 +1085,9 @@ int modbus_rtu_set_rts(modbus_t *ctx, int mode) return -1; } -int modbus_rtu_set_custom_rts(modbus_t *ctx, void (*set_rts) (modbus_t *ctx, int on)) +int modbus_rtu_set_custom_rts(modbus_t *ctx, void (*set_rts)(modbus_t *ctx, int on)) { - if (ctx == NULL) { + if (ctx == NULL || set_rts == NULL) { errno = EINVAL; return -1; } @@ -1071,7 +1120,7 @@ int modbus_rtu_get_rts_delay(modbus_t *ctx) 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 = (modbus_rtu_t *) ctx->backend_data; return ctx_rtu->rts_delay; #else if (ctx->debug) { @@ -1096,7 +1145,7 @@ int modbus_rtu_set_rts_delay(modbus_t *ctx, int us) 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 = (modbus_rtu_t *) ctx->backend_data; ctx_rtu->rts_delay = us; return 0; #else @@ -1118,19 +1167,38 @@ static void _modbus_rtu_close(modbus_t *ctx) 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 (ctx_rtu->w_ser.fd != INVALID_HANDLE_VALUE) { + /* 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()); + if (!CloseHandle(ctx_rtu->w_ser.fd) && ctx->debug) { + fprintf(stderr, + "ERROR Error while closing handle (LastError %d)\n", + (int) GetLastError()); + } + ctx_rtu->w_ser.fd = INVALID_HANDLE_VALUE; + } +#elif defined(HAVE_STRUCT_TERMIOS2) + if (ctx->s >= 0) { + if (ioctl(ctx->s, TCSETS2, &ctx_rtu->old_tios) < 0 && ctx->debug) { + fprintf(stderr, + "ERROR Can't restore the termios settings (%s)\n", + strerror(errno)); + } + close(ctx->s); + ctx->s = -1; } #else - if (ctx->s != -1) { - tcsetattr(ctx->s, TCSANOW, &ctx_rtu->old_tios); + if (ctx->s >= 0) { + if (tcsetattr(ctx->s, TCSANOW, &ctx_rtu->old_tios) < 0 && ctx->debug) { + fprintf(stderr, + "ERROR Can't restore the termios settings (%s)\n", + strerror(errno)); + } close(ctx->s); ctx->s = -1; } @@ -1148,13 +1216,13 @@ static int _modbus_rtu_flush(modbus_t *ctx) #endif } -static int _modbus_rtu_select(modbus_t *ctx, fd_set *rset, - struct timeval *tv, int length_to_read) +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); + 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; @@ -1164,13 +1232,17 @@ static int _modbus_rtu_select(modbus_t *ctx, fd_set *rset, return -1; } #else - while ((s_rc = select(ctx->s+1, rset, NULL, NULL, tv)) == -1) { + 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); + if (ctx->s < 0 || ctx->s >= FD_SETSIZE) { + errno = EINVAL; + return -1; + } FD_SET(ctx->s, rset); } else { return -1; @@ -1187,15 +1259,17 @@ static int _modbus_rtu_select(modbus_t *ctx, fd_set *rset, return s_rc; } -static void _modbus_rtu_free(modbus_t *ctx) { +static void _modbus_rtu_free(modbus_t *ctx) +{ if (ctx->backend_data) { - free(((modbus_rtu_t *)ctx->backend_data)->device); + free(((modbus_rtu_t *) ctx->backend_data)->device); free(ctx->backend_data); } free(ctx); } +// clang-format off const modbus_backend_t _modbus_rtu_backend = { _MODBUS_BACKEND_TYPE_RTU, _MODBUS_RTU_HEADER_LENGTH, @@ -1204,7 +1278,7 @@ const modbus_backend_t _modbus_rtu_backend = { _modbus_set_slave, _modbus_rtu_build_request_basis, _modbus_rtu_build_response_basis, - _modbus_rtu_prepare_response_tid, + _modbus_rtu_get_response_tid, _modbus_rtu_send_msg_pre, _modbus_rtu_send, _modbus_rtu_receive, @@ -1212,15 +1286,17 @@ const modbus_backend_t _modbus_rtu_backend = { _modbus_rtu_check_integrity, _modbus_rtu_pre_check_confirmation, _modbus_rtu_connect, + _modbus_rtu_is_connected, _modbus_rtu_close, _modbus_rtu_flush, _modbus_rtu_select, _modbus_rtu_free }; -modbus_t* modbus_new_rtu(const char *device, - int baud, char parity, int data_bit, - int stop_bit) +// clang-format on + +modbus_t * +modbus_new_rtu(const char *device, int baud, char parity, int data_bit, int stop_bit) { modbus_t *ctx; modbus_rtu_t *ctx_rtu; @@ -1233,35 +1309,58 @@ modbus_t* modbus_new_rtu(const char *device, } /* Check baud argument */ - if (baud == 0) { - fprintf(stderr, "The baud rate value must not be zero\n"); + if (baud <= 0) { + fprintf(stderr, "The baud rate value must be strictly positive\n"); + errno = EINVAL; + return NULL; + } + + /* Check data_bit argument */ + if (data_bit < 5 || data_bit > 8) { + fprintf(stderr, "The number of data bits must be between 5 and 8\n"); + errno = EINVAL; + return NULL; + } + + /* Check stop_bit argument */ + if (stop_bit != 1 && stop_bit != 2) { + fprintf(stderr, "The number of stop bits must be 1 or 2\n"); errno = EINVAL; return NULL; } - ctx = (modbus_t *)malloc(sizeof(modbus_t)); + 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)); + 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; + ctx_rtu = (modbus_rtu_t *) ctx->backend_data; + +#if defined(_WIN32) + ctx_rtu->w_ser.fd = INVALID_HANDLE_VALUE; +#endif /* Device name and \0 */ - ctx_rtu->device = (char *)malloc((strlen(device) + 1) * sizeof(char)); + ctx_rtu->device = (char *) malloc((strlen(device) + 1) * sizeof(char)); if (ctx_rtu->device == NULL) { modbus_free(ctx); errno = ENOMEM; return NULL; } + +#if defined(_WIN32) + strcpy_s(ctx_rtu->device, strlen(device) + 1, device); +#else strcpy(ctx_rtu->device, device); +#endif ctx_rtu->baud = baud; if (parity == 'N' || parity == 'E' || parity == 'O') { @@ -1284,7 +1383,8 @@ modbus_t* modbus_new_rtu(const char *device, 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; + 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; diff --git a/src/modbus-rtu.h b/src/modbus-rtu.h index fa3765521..8e89e7304 100644 --- a/src/modbus-rtu.h +++ b/src/modbus-rtu.h @@ -1,5 +1,5 @@ /* - * Copyright © 2001-2011 Stéphane Raimbault + * Copyright © Stéphane Raimbault * * SPDX-License-Identifier: LGPL-2.1-or-later */ @@ -14,10 +14,10 @@ MODBUS_BEGIN_DECLS /* Modbus_Application_Protocol_V1_1b.pdf Chapter 4 Section 1 Page 5 * RS232 / RS485 ADU = 253 bytes + slave (1 byte) + CRC (2 bytes) = 256 bytes */ -#define MODBUS_RTU_MAX_ADU_LENGTH 256 +#define MODBUS_RTU_MAX_ADU_LENGTH 256 -MODBUS_API modbus_t* modbus_new_rtu(const char *device, int baud, char parity, - int data_bit, int stop_bit); +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 @@ -25,14 +25,15 @@ MODBUS_API modbus_t* modbus_new_rtu(const char *device, int baud, char parity, 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 +#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_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); diff --git a/src/modbus-tcp-private.h b/src/modbus-tcp-private.h index 780587c70..faffc21a5 100644 --- a/src/modbus-tcp-private.h +++ b/src/modbus-tcp-private.h @@ -1,5 +1,5 @@ /* - * Copyright © 2001-2011 Stéphane Raimbault + * Copyright © Stéphane Raimbault * * SPDX-License-Identifier: LGPL-2.1-or-later */ @@ -7,14 +7,14 @@ #ifndef MODBUS_TCP_PRIVATE_H #define MODBUS_TCP_PRIVATE_H -#define _MODBUS_TCP_HEADER_LENGTH 7 +#define _MODBUS_TCP_HEADER_LENGTH 7 #define _MODBUS_TCP_PRESET_REQ_LENGTH 12 -#define _MODBUS_TCP_PRESET_RSP_LENGTH 8 +#define _MODBUS_TCP_PRESET_RSP_LENGTH 8 -#define _MODBUS_TCP_CHECKSUM_LENGTH 0 +#define _MODBUS_TCP_CHECKSUM_LENGTH 0 /* In both structures, the transaction ID must be placed on first position - to have a quick access not dependant of the TCP backend */ + to have a quick access not dependent of the TCP backend */ typedef struct _modbus_tcp { /* Extract from MODBUS Messaging on TCP/IP Implementation Guide V1.0b (page 23/46): @@ -27,18 +27,15 @@ typedef struct _modbus_tcp { char ip[16]; } modbus_tcp_t; -#define _MODBUS_TCP_PI_NODE_LENGTH 1025 -#define _MODBUS_TCP_PI_SERVICE_LENGTH 32 - typedef struct _modbus_tcp_pi { /* Transaction ID */ uint16_t t_id; /* TCP port */ int port; /* Node */ - char node[_MODBUS_TCP_PI_NODE_LENGTH]; + char *node; /* Service */ - char service[_MODBUS_TCP_PI_SERVICE_LENGTH]; + char *service; } modbus_tcp_pi_t; #endif /* MODBUS_TCP_PRIVATE_H */ diff --git a/src/modbus-tcp.c b/src/modbus-tcp.c index fc2ff24b8..e84dc6028 100644 --- a/src/modbus-tcp.c +++ b/src/modbus-tcp.c @@ -1,9 +1,10 @@ /* - * Copyright © 2001-2013 Stéphane Raimbault + * Copyright © Stéphane Raimbault * * SPDX-License-Identifier: LGPL-2.1-or-later */ +// clang-format off #if defined(_WIN32) # define OS_WIN32 /* ws2_32.dll has getaddrinfo and freeaddrinfo on Windows XP and later. @@ -15,6 +16,8 @@ #include #include +#include +#include #include #include #ifndef _MSC_VER @@ -29,6 +32,7 @@ # include # define SHUT_RDWR 2 # define close closesocket +# define strdup _strdup #else # include # include @@ -38,8 +42,12 @@ # include #endif -# include -# include +#ifdef HAVE_NETINET_IN_H +#include +#endif /* HAVE_NETINET_IN_H */ +#ifdef HAVE_NETINET_IP_H +#include +#endif /* HAVE_NETINET_IP_H */ # include # include # include @@ -52,11 +60,12 @@ #if defined(_AIX) && !defined(MSG_DONTWAIT) #define MSG_DONTWAIT MSG_NONBLOCK #endif +// clang-format on #include "modbus-private.h" -#include "modbus-tcp.h" #include "modbus-tcp-private.h" +#include "modbus-tcp.h" #ifdef OS_WIN32 static int _modbus_tcp_init_win32(void) @@ -65,8 +74,9 @@ static int _modbus_tcp_init_win32(void) WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { - fprintf(stderr, "WSAStartup() returned error code %d\n", - (unsigned int)GetLastError()); + fprintf(stderr, + "WSAStartup() returned error code %d\n", + (unsigned int) GetLastError()); errno = EIO; return -1; } @@ -76,8 +86,10 @@ static int _modbus_tcp_init_win32(void) static int _modbus_set_slave(modbus_t *ctx, int slave) { + int max_slave = (ctx->quirks & MODBUS_QUIRK_MAX_SLAVE) ? 255 : 247; + /* Broadcast address is 0 (MODBUS_BROADCAST_ADDRESS) */ - if (slave >= 0 && slave <= 247) { + if (slave >= 0 && slave <= max_slave) { ctx->slave = slave; } else if (slave == MODBUS_TCP_SLAVE) { /* The special value MODBUS_TCP_SLAVE (0xFF) can be used in TCP mode to @@ -92,9 +104,8 @@ static int _modbus_set_slave(modbus_t *ctx, int slave) } /* Builds a TCP request header */ -static int _modbus_tcp_build_request_basis(modbus_t *ctx, int function, - int addr, int nb, - uint8_t *req) +static int _modbus_tcp_build_request_basis( + modbus_t *ctx, int function, int addr, int nb, uint8_t *req) { modbus_tcp_t *ctx_tcp = ctx->backend_data; @@ -146,15 +157,14 @@ static int _modbus_tcp_build_response_basis(sft_t *sft, uint8_t *rsp) return _MODBUS_TCP_PRESET_RSP_LENGTH; } - -static int _modbus_tcp_prepare_response_tid(const uint8_t *req, int *req_length) +static int _modbus_tcp_get_response_tid(const uint8_t *req) { return (req[0] << 8) + req[1]; } static int _modbus_tcp_send_msg_pre(uint8_t *req, int req_length) { - /* Substract the header length to the message length */ + /* Subtract the header length to the message length */ int mbap_length = req_length - 6; req[4] = mbap_length >> 8; @@ -169,15 +179,17 @@ static ssize_t _modbus_tcp_send(modbus_t *ctx, const uint8_t *req, int req_lengt Requests not to send SIGPIPE on errors on stream oriented sockets when the other end breaks the connection. The EPIPE error is still returned. */ - return send(ctx->s, (const char *)req, req_length, MSG_NOSIGNAL); + return send(ctx->s, (const char *) req, req_length, MSG_NOSIGNAL); } -static int _modbus_tcp_receive(modbus_t *ctx, uint8_t *req) { +static int _modbus_tcp_receive(modbus_t *ctx, uint8_t *req) +{ return _modbus_receive_msg(ctx, req, MSG_INDICATION); } -static ssize_t _modbus_tcp_recv(modbus_t *ctx, uint8_t *rsp, int rsp_length) { - return recv(ctx->s, (char *)rsp, rsp_length, 0); +static ssize_t _modbus_tcp_recv(modbus_t *ctx, uint8_t *rsp, int rsp_length) +{ + return recv(ctx->s, (char *) rsp, rsp_length, 0); } static int _modbus_tcp_check_integrity(modbus_t *ctx, uint8_t *msg, const int msg_length) @@ -185,24 +197,29 @@ static int _modbus_tcp_check_integrity(modbus_t *ctx, uint8_t *msg, const int ms return msg_length; } -static int _modbus_tcp_pre_check_confirmation(modbus_t *ctx, const uint8_t *req, - const uint8_t *rsp, int rsp_length) +static int _modbus_tcp_pre_check_confirmation(modbus_t *ctx, + const uint8_t *req, + const uint8_t *rsp, + int rsp_length) { + unsigned int protocol_id; /* Check transaction ID */ if (req[0] != rsp[0] || req[1] != rsp[1]) { if (ctx->debug) { - fprintf(stderr, "Invalid transaction ID received 0x%X (not 0x%X)\n", - (rsp[0] << 8) + rsp[1], (req[0] << 8) + req[1]); + fprintf(stderr, + "Invalid transaction ID received 0x%X (not 0x%X)\n", + (rsp[0] << 8) + rsp[1], + (req[0] << 8) + req[1]); } errno = EMBBADDATA; return -1; } /* Check protocol ID */ - if (rsp[2] != 0x0 && rsp[3] != 0x0) { + protocol_id = (rsp[2] << 8) + rsp[3]; + if (protocol_id != 0x0) { if (ctx->debug) { - fprintf(stderr, "Invalid protocol ID received 0x%X (not 0x0)\n", - (rsp[2] << 8) + rsp[3]); + fprintf(stderr, "Invalid protocol ID received 0x%X (not 0x0)\n", protocol_id); } errno = EMBBADDATA; return -1; @@ -219,8 +236,11 @@ static int _modbus_tcp_set_ipv4_options(int s) /* Set the TCP no delay flag */ /* SOL_TCP = IPPROTO_TCP */ option = 1; - rc = setsockopt(s, IPPROTO_TCP, TCP_NODELAY, - (const void *)&option, sizeof(int)); +#ifdef _WIN32 + rc = setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (const char *)&option, sizeof(int)); +#else + rc = setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (const void *)&option, sizeof(int)); +#endif if (rc == -1) { return -1; } @@ -248,8 +268,11 @@ static int _modbus_tcp_set_ipv4_options(int s) **/ /* Set the IP low delay option */ option = IPTOS_LOWDELAY; - rc = setsockopt(s, IPPROTO_IP, IP_TOS, - (const void *)&option, sizeof(int)); +#ifdef _WIN32 + rc = setsockopt(s, IPPROTO_IP, IP_TOS, (const char *)&option, sizeof(int)); +#else + rc = setsockopt(s, IPPROTO_IP, IP_TOS, (const void *)&option, sizeof(int)); +#endif if (rc == -1) { return -1; } @@ -258,7 +281,9 @@ static int _modbus_tcp_set_ipv4_options(int s) return 0; } -static int _connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen, +static int _connect(int sockfd, + const struct sockaddr *addr, + socklen_t addrlen, const struct timeval *ro_tv) { int rc = connect(sockfd, addr, addrlen); @@ -280,15 +305,25 @@ static int _connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen, /* Wait to be available in writing */ FD_ZERO(&wset); + if (sockfd >= FD_SETSIZE) { + errno = EINVAL; + return -1; + } FD_SET(sockfd, &wset); rc = select(sockfd + 1, NULL, &wset, NULL, &tv); - if (rc <= 0) { - /* Timeout or fail */ + if (rc < 0) { + /* Fail */ + return -1; + } + + if (rc == 0) { + /* Timeout */ + errno = ETIMEDOUT; return -1; } /* The connection is established if SO_ERROR and optval are set to 0 */ - rc = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&optval, &optlen); + rc = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *) &optval, &optlen); if (rc == 0 && optval == 0) { return 0; } else { @@ -303,6 +338,7 @@ static int _connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen, static int _modbus_tcp_connect(modbus_t *ctx) { int rc; + int s; /* Specialized version of sockaddr for Internet socket address (same size) */ struct sockaddr_in addr; modbus_tcp_t *ctx_tcp = ctx->backend_data; @@ -322,15 +358,27 @@ static int _modbus_tcp_connect(modbus_t *ctx) flags |= SOCK_NONBLOCK; #endif - ctx->s = socket(PF_INET, flags, 0); - if (ctx->s == -1) { + s = socket(PF_INET, flags, 0); + if (s < 0) { return -1; } - rc = _modbus_tcp_set_ipv4_options(ctx->s); + if (s >= FD_SETSIZE) { + if (ctx->debug) { + fprintf( + stderr, + "ERROR Socket descriptor %d exceeds FD_SETSIZE (%d)\n", + s, + FD_SETSIZE); + } + close(s); + errno = EINVAL; + return -1; + } + + rc = _modbus_tcp_set_ipv4_options(s); if (rc == -1) { - close(ctx->s); - ctx->s = -1; + close(s); return -1; } @@ -340,14 +388,27 @@ static int _modbus_tcp_connect(modbus_t *ctx) addr.sin_family = AF_INET; addr.sin_port = htons(ctx_tcp->port); - addr.sin_addr.s_addr = inet_addr(ctx_tcp->ip); - rc = _connect(ctx->s, (struct sockaddr *)&addr, sizeof(addr), &ctx->response_timeout); + rc = inet_pton(addr.sin_family, ctx_tcp->ip, &(addr.sin_addr)); + if (rc <= 0) { + if (ctx->debug) { + fprintf(stderr, "Invalid IP address: %s\n", ctx_tcp->ip); + } + close(s); + return -1; + } + + rc = _connect(s, (struct sockaddr *) &addr, sizeof(addr), &ctx->response_timeout); if (rc == -1) { - close(ctx->s); - ctx->s = -1; + close(s); return -1; } + /* Replace any previously open socket */ + if (ctx->s >= 0) { + close(ctx->s); + } + ctx->s = s; + return 0; } @@ -355,6 +416,7 @@ static int _modbus_tcp_connect(modbus_t *ctx) static int _modbus_tcp_pi_connect(modbus_t *ctx) { int rc; + int new_s = -1; struct addrinfo *ai_list; struct addrinfo *ai_ptr; struct addrinfo ai_hints; @@ -377,11 +439,17 @@ static int _modbus_tcp_pi_connect(modbus_t *ctx) ai_hints.ai_next = NULL; ai_list = NULL; - rc = getaddrinfo(ctx_tcp_pi->node, ctx_tcp_pi->service, - &ai_hints, &ai_list); + rc = getaddrinfo(ctx_tcp_pi->node, ctx_tcp_pi->service, &ai_hints, &ai_list); if (rc != 0) { if (ctx->debug) { +#ifdef HAVE_GAI_STRERROR fprintf(stderr, "Error returned by getaddrinfo: %s\n", gai_strerror(rc)); +#else + fprintf(stderr, "Error returned by getaddrinfo: %d\n", rc); +#endif + } + if (ai_list != NULL) { + freeaddrinfo(ai_list); } errno = ECONNREFUSED; return -1; @@ -403,6 +471,18 @@ static int _modbus_tcp_pi_connect(modbus_t *ctx) if (s < 0) continue; + if (s >= FD_SETSIZE) { + if (ctx->debug) { + fprintf( + stderr, + "ERROR Socket descriptor %d exceeds FD_SETSIZE (%d)\n", + s, + FD_SETSIZE); + } + close(s); + continue; + } + if (ai_ptr->ai_family == AF_INET) _modbus_tcp_set_ipv4_options(s); @@ -416,23 +496,34 @@ static int _modbus_tcp_pi_connect(modbus_t *ctx) continue; } - ctx->s = s; + new_s = s; break; } freeaddrinfo(ai_list); - if (ctx->s < 0) { + if (new_s < 0) { return -1; } + /* Replace any previously open socket */ + if (ctx->s >= 0) { + close(ctx->s); + } + ctx->s = new_s; + return 0; } +static unsigned int _modbus_tcp_is_connected(modbus_t *ctx) +{ + return ctx->s >= 0; +} + /* Closes the network connection and socket in TCP mode */ static void _modbus_tcp_close(modbus_t *ctx) { - if (ctx->s != -1) { + if (ctx->s >= 0) { shutdown(ctx->s, SHUT_RDWR); close(ctx->s); ctx->s = -1; @@ -442,7 +533,9 @@ static void _modbus_tcp_close(modbus_t *ctx) static int _modbus_tcp_flush(modbus_t *ctx) { int rc; - int rc_sum = 0; + // Use an unsigned 16-bit integer to reduce overflow risk. The flush function + // is not expected to handle huge amounts of data (> 2GB). + uint16_t rc_sum = 0; do { /* Extract the garbage from the socket */ @@ -457,8 +550,12 @@ static int _modbus_tcp_flush(modbus_t *ctx) tv.tv_sec = 0; tv.tv_usec = 0; FD_ZERO(&rset); + if (ctx->s < 0 || ctx->s >= FD_SETSIZE) { + errno = EINVAL; + return -1; + } FD_SET(ctx->s, &rset); - rc = select(ctx->s+1, &rset, NULL, NULL, &tv); + rc = select(ctx->s + 1, &rset, NULL, NULL, &tv); if (rc == -1) { return -1; } @@ -469,11 +566,19 @@ static int _modbus_tcp_flush(modbus_t *ctx) } #endif if (rc > 0) { - rc_sum += rc; + // Check for overflow before adding + if (rc_sum <= UINT16_MAX - rc) { + rc_sum += rc; + } else { + // Handle overflow + errno = EOVERFLOW; + return -1; + } } } while (rc == MODBUS_TCP_MAX_ADU_LENGTH); - return rc_sum; + /* Cast is safe: uint16_t always fits in int, and overflow is checked above */ + return (int) rc_sum; } /* Listens for any request from one or many modbus masters in TCP */ @@ -484,6 +589,7 @@ int modbus_tcp_listen(modbus_t *ctx, int nb_connection) int flags; struct sockaddr_in addr; modbus_tcp_t *ctx_tcp; + int rc; if (ctx == NULL) { errno = EINVAL; @@ -510,8 +616,11 @@ int modbus_tcp_listen(modbus_t *ctx, int nb_connection) } enable = 1; - if (setsockopt(new_s, SOL_SOCKET, SO_REUSEADDR, - (char *)&enable, sizeof(enable)) == -1) { +#ifdef _WIN32 + if (setsockopt(new_s, SOL_SOCKET, SO_REUSEADDR, (const char *)&enable, sizeof(enable)) == -1) { +#else + if (setsockopt(new_s, SOL_SOCKET, SO_REUSEADDR, (const void *)&enable, sizeof(enable)) == -1) { +#endif close(new_s); return -1; } @@ -525,9 +634,17 @@ int modbus_tcp_listen(modbus_t *ctx, int nb_connection) addr.sin_addr.s_addr = htonl(INADDR_ANY); } else { /* Listen only specified IP address */ - addr.sin_addr.s_addr = inet_addr(ctx_tcp->ip); + rc = inet_pton(addr.sin_family, ctx_tcp->ip, &(addr.sin_addr)); + if (rc <= 0) { + if (ctx->debug) { + fprintf(stderr, "Invalid IP address: %s\n", ctx_tcp->ip); + } + close(new_s); + return -1; + } } - if (bind(new_s, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + + if (bind(new_s, (struct sockaddr *) &addr, sizeof(addr)) == -1) { close(new_s); return -1; } @@ -576,7 +693,7 @@ int modbus_tcp_pi_listen(modbus_t *ctx, int nb_connection) service = ctx_tcp_pi->service; } - memset(&ai_hints, 0, sizeof (ai_hints)); + memset(&ai_hints, 0, sizeof(ai_hints)); /* If node is not NULL, than the AI_PASSIVE flag is ignored. */ ai_hints.ai_flags |= AI_PASSIVE; #ifdef AI_ADDRCONFIG @@ -592,7 +709,14 @@ int modbus_tcp_pi_listen(modbus_t *ctx, int nb_connection) rc = getaddrinfo(node, service, &ai_hints, &ai_list); if (rc != 0) { if (ctx->debug) { +#ifdef HAVE_GAI_STRERROR fprintf(stderr, "Error returned by getaddrinfo: %s\n", gai_strerror(rc)); +#else + fprintf(stderr, "Error returned by getaddrinfo: %d\n", rc); +#endif + } + if (ai_list != NULL) { + freeaddrinfo(ai_list); } errno = ECONNREFUSED; return -1; @@ -615,8 +739,11 @@ int modbus_tcp_pi_listen(modbus_t *ctx, int nb_connection) continue; } else { int enable = 1; - rc = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, - (void *)&enable, sizeof (enable)); +#ifdef _WIN32 + rc = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char *)&enable, sizeof(enable)); +#else + rc = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const void *)&enable, sizeof(enable)); +#endif if (rc != 0) { close(s); if (ctx->debug) { @@ -669,18 +796,36 @@ int modbus_tcp_accept(modbus_t *ctx, int *s) addrlen = sizeof(addr); #ifdef HAVE_ACCEPT4 /* Inherit socket flags and use accept4 call */ - ctx->s = accept4(*s, (struct sockaddr *)&addr, &addrlen, SOCK_CLOEXEC); + ctx->s = accept4(*s, (struct sockaddr *) &addr, &addrlen, SOCK_CLOEXEC); #else - ctx->s = accept(*s, (struct sockaddr *)&addr, &addrlen); + ctx->s = accept(*s, (struct sockaddr *) &addr, &addrlen); #endif - if (ctx->s == -1) { + if (ctx->s < 0) { + return -1; + } + + if (ctx->s >= FD_SETSIZE) { + if (ctx->debug) { + fprintf( + stderr, + "ERROR Socket descriptor %d exceeds FD_SETSIZE (%d)\n", + ctx->s, + FD_SETSIZE); + } + close(ctx->s); + ctx->s = -1; + errno = EINVAL; return -1; } if (ctx->debug) { - printf("The client connection from %s is accepted\n", - inet_ntoa(addr.sin_addr)); + char buf[INET_ADDRSTRLEN]; + if (inet_ntop(AF_INET, &(addr.sin_addr), buf, INET_ADDRSTRLEN) == NULL) { + fprintf(stderr, "Client connection accepted from unparsable IP.\n"); + } else { + printf("Client connection accepted from %s.\n", buf); + } } return ctx->s; @@ -688,7 +833,7 @@ int modbus_tcp_accept(modbus_t *ctx, int *s) int modbus_tcp_pi_accept(modbus_t *ctx, int *s) { - struct sockaddr_storage addr; + struct sockaddr_in6 addr; socklen_t addrlen; if (ctx == NULL) { @@ -699,32 +844,56 @@ int modbus_tcp_pi_accept(modbus_t *ctx, int *s) addrlen = sizeof(addr); #ifdef HAVE_ACCEPT4 /* Inherit socket flags and use accept4 call */ - ctx->s = accept4(*s, (struct sockaddr *)&addr, &addrlen, SOCK_CLOEXEC); + ctx->s = accept4(*s, (struct sockaddr *) &addr, &addrlen, SOCK_CLOEXEC); #else - ctx->s = accept(*s, (struct sockaddr *)&addr, &addrlen); + ctx->s = accept(*s, (struct sockaddr *) &addr, &addrlen); #endif - if (ctx->s == -1) { + if (ctx->s < 0) { + return -1; + } + + if (ctx->s >= FD_SETSIZE) { + if (ctx->debug) { + fprintf( + stderr, + "ERROR Socket descriptor %d exceeds FD_SETSIZE (%d)\n", + ctx->s, + FD_SETSIZE); + } + close(ctx->s); + ctx->s = -1; + errno = EINVAL; return -1; } if (ctx->debug) { - printf("The client connection is accepted.\n"); + char buf[INET6_ADDRSTRLEN]; + if (inet_ntop(AF_INET6, &(addr.sin6_addr), buf, INET6_ADDRSTRLEN) == NULL) { + fprintf(stderr, "Client connection accepted from unparsable IP.\n"); + } else { + printf("Client connection accepted from %s.\n", buf); + } } return ctx->s; } -static int _modbus_tcp_select(modbus_t *ctx, fd_set *rset, struct timeval *tv, int length_to_read) +static int +_modbus_tcp_select(modbus_t *ctx, fd_set *rset, struct timeval *tv, int length_to_read) { int s_rc; - while ((s_rc = select(ctx->s+1, rset, NULL, NULL, tv)) == -1) { + 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); + if (ctx->s < 0 || ctx->s >= FD_SETSIZE) { + errno = EINVAL; + return -1; + } FD_SET(ctx->s, rset); } else { return -1; @@ -739,11 +908,27 @@ static int _modbus_tcp_select(modbus_t *ctx, fd_set *rset, struct timeval *tv, i return s_rc; } -static void _modbus_tcp_free(modbus_t *ctx) { - free(ctx->backend_data); +static void _modbus_tcp_free(modbus_t *ctx) +{ + if (ctx->backend_data) { + free(ctx->backend_data); + } + free(ctx); +} + +static void _modbus_tcp_pi_free(modbus_t *ctx) +{ + if (ctx->backend_data) { + modbus_tcp_pi_t *ctx_tcp_pi = ctx->backend_data; + free(ctx_tcp_pi->node); + free(ctx_tcp_pi->service); + free(ctx->backend_data); + } + free(ctx); } +// clang-format off const modbus_backend_t _modbus_tcp_backend = { _MODBUS_BACKEND_TYPE_TCP, _MODBUS_TCP_HEADER_LENGTH, @@ -752,7 +937,7 @@ const modbus_backend_t _modbus_tcp_backend = { _modbus_set_slave, _modbus_tcp_build_request_basis, _modbus_tcp_build_response_basis, - _modbus_tcp_prepare_response_tid, + _modbus_tcp_get_response_tid, _modbus_tcp_send_msg_pre, _modbus_tcp_send, _modbus_tcp_receive, @@ -760,13 +945,13 @@ const modbus_backend_t _modbus_tcp_backend = { _modbus_tcp_check_integrity, _modbus_tcp_pre_check_confirmation, _modbus_tcp_connect, + _modbus_tcp_is_connected, _modbus_tcp_close, _modbus_tcp_flush, _modbus_tcp_select, _modbus_tcp_free }; - const modbus_backend_t _modbus_tcp_pi_backend = { _MODBUS_BACKEND_TYPE_TCP, _MODBUS_TCP_HEADER_LENGTH, @@ -775,7 +960,7 @@ const modbus_backend_t _modbus_tcp_pi_backend = { _modbus_set_slave, _modbus_tcp_build_request_basis, _modbus_tcp_build_response_basis, - _modbus_tcp_prepare_response_tid, + _modbus_tcp_get_response_tid, _modbus_tcp_send_msg_pre, _modbus_tcp_send, _modbus_tcp_receive, @@ -783,13 +968,16 @@ const modbus_backend_t _modbus_tcp_pi_backend = { _modbus_tcp_check_integrity, _modbus_tcp_pre_check_confirmation, _modbus_tcp_pi_connect, + _modbus_tcp_is_connected, _modbus_tcp_close, _modbus_tcp_flush, _modbus_tcp_select, - _modbus_tcp_free + _modbus_tcp_pi_free }; -modbus_t* modbus_new_tcp(const char *ip, int port) +// clang-format on + +modbus_t *modbus_new_tcp(const char *ip, int port) { modbus_t *ctx; modbus_tcp_t *ctx_tcp; @@ -801,7 +989,9 @@ modbus_t* modbus_new_tcp(const char *ip, int port) handler for SIGPIPE. */ struct sigaction sa; + memset(&sa, 0, sizeof(sa)); sa.sa_handler = SIG_IGN; + sigemptyset(&sa.sa_mask); if (sigaction(SIGPIPE, &sa, NULL) < 0) { /* The debug flag can't be set here... */ fprintf(stderr, "Could not install SIGPIPE handler.\n"); @@ -809,7 +999,7 @@ modbus_t* modbus_new_tcp(const char *ip, int port) } #endif - ctx = (modbus_t *)malloc(sizeof(modbus_t)); + ctx = (modbus_t *) malloc(sizeof(modbus_t)); if (ctx == NULL) { return NULL; } @@ -820,13 +1010,13 @@ modbus_t* modbus_new_tcp(const char *ip, int port) ctx->backend = &_modbus_tcp_backend; - ctx->backend_data = (modbus_tcp_t *)malloc(sizeof(modbus_tcp_t)); + ctx->backend_data = (modbus_tcp_t *) malloc(sizeof(modbus_tcp_t)); if (ctx->backend_data == NULL) { modbus_free(ctx); errno = ENOMEM; return NULL; } - ctx_tcp = (modbus_tcp_t *)ctx->backend_data; + ctx_tcp = (modbus_tcp_t *) ctx->backend_data; if (ip != NULL) { dest_size = sizeof(char) * 16; @@ -846,6 +1036,7 @@ modbus_t* modbus_new_tcp(const char *ip, int port) } } else { ctx_tcp->ip[0] = '0'; + ctx_tcp->ip[1] = '\0'; } ctx_tcp->port = port; ctx_tcp->t_id = 0; @@ -853,15 +1044,12 @@ modbus_t* modbus_new_tcp(const char *ip, int port) return ctx; } - -modbus_t* modbus_new_tcp_pi(const char *node, const char *service) +modbus_t *modbus_new_tcp_pi(const char *node, const char *service) { modbus_t *ctx; modbus_tcp_pi_t *ctx_tcp_pi; - size_t dest_size; - size_t ret_size; - ctx = (modbus_t *)malloc(sizeof(modbus_t)); + ctx = (modbus_t *) malloc(sizeof(modbus_t)); if (ctx == NULL) { return NULL; } @@ -872,54 +1060,39 @@ modbus_t* modbus_new_tcp_pi(const char *node, const char *service) ctx->backend = &_modbus_tcp_pi_backend; - ctx->backend_data = (modbus_tcp_pi_t *)malloc(sizeof(modbus_tcp_pi_t)); + ctx->backend_data = (modbus_tcp_pi_t *) malloc(sizeof(modbus_tcp_pi_t)); if (ctx->backend_data == NULL) { modbus_free(ctx); errno = ENOMEM; return NULL; } - ctx_tcp_pi = (modbus_tcp_pi_t *)ctx->backend_data; + ctx_tcp_pi = (modbus_tcp_pi_t *) ctx->backend_data; + ctx_tcp_pi->node = NULL; + ctx_tcp_pi->service = NULL; - if (node == NULL) { - /* The node argument can be empty to indicate any hosts */ - ctx_tcp_pi->node[0] = 0; + if (node != NULL) { + ctx_tcp_pi->node = strdup(node); } else { - dest_size = sizeof(char) * _MODBUS_TCP_PI_NODE_LENGTH; - ret_size = strlcpy(ctx_tcp_pi->node, node, dest_size); - if (ret_size == 0) { - fprintf(stderr, "The node string is empty\n"); - modbus_free(ctx); - errno = EINVAL; - return NULL; - } - - if (ret_size >= dest_size) { - fprintf(stderr, "The node string has been truncated\n"); - modbus_free(ctx); - errno = EINVAL; - return NULL; - } - } - - if (service != NULL) { - dest_size = sizeof(char) * _MODBUS_TCP_PI_SERVICE_LENGTH; - ret_size = strlcpy(ctx_tcp_pi->service, service, dest_size); - } else { - /* Empty service is not allowed, error catched below. */ - ret_size = 0; + /* The node argument can be empty to indicate any hosts */ + ctx_tcp_pi->node = strdup(""); } - if (ret_size == 0) { - fprintf(stderr, "The service string is empty\n"); + if (ctx_tcp_pi->node == NULL) { modbus_free(ctx); - errno = EINVAL; + errno = ENOMEM; return NULL; } - if (ret_size >= dest_size) { - fprintf(stderr, "The service string has been truncated\n"); + if (service != NULL && service[0] != '\0') { + ctx_tcp_pi->service = strdup(service); + } else { + /* Default Modbus port number */ + ctx_tcp_pi->service = strdup("502"); + } + + if (ctx_tcp_pi->service == NULL) { modbus_free(ctx); - errno = EINVAL; + errno = ENOMEM; return NULL; } diff --git a/src/modbus-tcp.h b/src/modbus-tcp.h index d67c2393e..768d38c9d 100644 --- a/src/modbus-tcp.h +++ b/src/modbus-tcp.h @@ -1,5 +1,5 @@ /* - * Copyright © 2001-2010 Stéphane Raimbault + * Copyright © Stéphane Raimbault * * SPDX-License-Identifier: LGPL-2.1-or-later */ @@ -15,35 +15,35 @@ MODBUS_BEGIN_DECLS /* Win32 with MinGW, supplement to */ #include #if !defined(ECONNRESET) -#define ECONNRESET WSAECONNRESET +#define ECONNRESET WSAECONNRESET #endif #if !defined(ECONNREFUSED) #define ECONNREFUSED WSAECONNREFUSED #endif #if !defined(ETIMEDOUT) -#define ETIMEDOUT WSAETIMEDOUT +#define ETIMEDOUT WSAETIMEDOUT #endif #if !defined(ENOPROTOOPT) -#define ENOPROTOOPT WSAENOPROTOOPT +#define ENOPROTOOPT WSAENOPROTOOPT #endif #if !defined(EINPROGRESS) -#define EINPROGRESS WSAEINPROGRESS +#define EINPROGRESS WSAEINPROGRESS #endif #endif -#define MODBUS_TCP_DEFAULT_PORT 502 -#define MODBUS_TCP_SLAVE 0xFF +#define MODBUS_TCP_DEFAULT_PORT 502 +#define MODBUS_TCP_SLAVE 0xFF /* Modbus_Application_Protocol_V1_1b.pdf Chapter 4 Section 1 Page 5 * TCP MODBUS ADU = 253 bytes + MBAP (7 bytes) = 260 bytes */ -#define MODBUS_TCP_MAX_ADU_LENGTH 260 +#define MODBUS_TCP_MAX_ADU_LENGTH 260 -MODBUS_API modbus_t* modbus_new_tcp(const char *ip_address, int port); +MODBUS_API modbus_t *modbus_new_tcp(const char *ip_address, int port); MODBUS_API int modbus_tcp_listen(modbus_t *ctx, int nb_connection); MODBUS_API int modbus_tcp_accept(modbus_t *ctx, int *s); -MODBUS_API modbus_t* modbus_new_tcp_pi(const char *node, const char *service); +MODBUS_API modbus_t *modbus_new_tcp_pi(const char *node, const char *service); MODBUS_API int modbus_tcp_pi_listen(modbus_t *ctx, int nb_connection); MODBUS_API int modbus_tcp_pi_accept(modbus_t *ctx, int *s); diff --git a/src/modbus-version.h.in b/src/modbus-version.h.in index 8473d6597..6b79b51ba 100644 --- a/src/modbus-version.h.in +++ b/src/modbus-version.h.in @@ -1,5 +1,5 @@ /* - * Copyright © 2010-2014 Stéphane Raimbault + * Copyright © Stéphane Raimbault * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -29,25 +29,23 @@ #define LIBMODBUS_VERSION_MICRO (@LIBMODBUS_VERSION_MICRO@) /* The full version, like 1.2.3 */ -#define LIBMODBUS_VERSION @LIBMODBUS_VERSION@ +#define LIBMODBUS_VERSION @LIBMODBUS_VERSION@ /* The full version, in string form (suited for string concatenation) */ #define LIBMODBUS_VERSION_STRING "@LIBMODBUS_VERSION@" /* Numerically encoded version, eg. v1.2.3 is 0x010203 */ -#define LIBMODBUS_VERSION_HEX ((LIBMODBUS_VERSION_MAJOR << 16) | \ - (LIBMODBUS_VERSION_MINOR << 8) | \ - (LIBMODBUS_VERSION_MICRO << 0)) +#define LIBMODBUS_VERSION_HEX \ + ((LIBMODBUS_VERSION_MAJOR << 16) | (LIBMODBUS_VERSION_MINOR << 8) | \ + (LIBMODBUS_VERSION_MICRO << 0)) /* Evaluates to True if the version is greater than @major, @minor and @micro */ -#define LIBMODBUS_VERSION_CHECK(major,minor,micro) \ - (LIBMODBUS_VERSION_MAJOR > (major) || \ - (LIBMODBUS_VERSION_MAJOR == (major) && \ - LIBMODBUS_VERSION_MINOR > (minor)) || \ - (LIBMODBUS_VERSION_MAJOR == (major) && \ - LIBMODBUS_VERSION_MINOR == (minor) && \ +#define LIBMODBUS_VERSION_CHECK(major, minor, micro) \ + (LIBMODBUS_VERSION_MAJOR > (major) || \ + (LIBMODBUS_VERSION_MAJOR == (major) && LIBMODBUS_VERSION_MINOR > (minor)) || \ + (LIBMODBUS_VERSION_MAJOR == (major) && LIBMODBUS_VERSION_MINOR == (minor) && \ LIBMODBUS_VERSION_MICRO >= (micro))) #endif /* MODBUS_VERSION_H */ diff --git a/src/modbus.c b/src/modbus.c index 8607180bd..aa5c3317e 100644 --- a/src/modbus.c +++ b/src/modbus.c @@ -1,5 +1,5 @@ /* - * Copyright © 2001-2011 Stéphane Raimbault + * Copyright © Stéphane Raimbault * * SPDX-License-Identifier: LGPL-2.1-or-later * @@ -7,12 +7,12 @@ * http://libmodbus.org/ */ -#include -#include -#include -#include #include #include +#include +#include +#include +#include #include #ifndef _MSC_VER #include @@ -20,12 +20,16 @@ #include -#include "modbus.h" #include "modbus-private.h" +#include "modbus.h" /* Internal use */ #define MSG_LENGTH_UNDEFINED -1 +/* The Modbus address space is 16-bit, so a mapping table cannot hold more + than 65536 entries. */ +#define MODBUS_MAX_TABLE_SIZE 65536 + /* Exported version */ const unsigned int libmodbus_version_major = LIBMODBUS_VERSION_MAJOR; const unsigned int libmodbus_version_minor = LIBMODBUS_VERSION_MINOR; @@ -41,7 +45,8 @@ typedef enum { _STEP_DATA } _step_t; -const char *modbus_strerror(int errnum) { +const char *modbus_strerror(int errnum) +{ switch (errnum) { case EMBXILFUN: return "Illegal function"; @@ -95,13 +100,12 @@ static void _sleep_response_timeout(modbus_t *ctx) /* Response timeout is always positive */ #ifdef _WIN32 /* usleep doesn't exist on Windows */ - Sleep((ctx->response_timeout.tv_sec * 1000) + - (ctx->response_timeout.tv_usec / 1000)); + Sleep((ctx->response_timeout.tv_sec * 1000) + (ctx->response_timeout.tv_usec / 1000)); #else /* usleep source code */ struct timespec request, remaining; request.tv_sec = ctx->response_timeout.tv_sec; - request.tv_nsec = ((long int)ctx->response_timeout.tv_usec) * 1000; + request.tv_nsec = ((long int) ctx->response_timeout.tv_usec) * 1000; while (nanosleep(&request, &remaining) == -1 && errno == EINTR) { request = remaining; } @@ -125,7 +129,7 @@ int modbus_flush(modbus_t *ctx) return rc; } -/* Computes the length of the expected response */ +/* Computes the length of the expected response including checksum */ static unsigned int compute_response_length_from_request(modbus_t *ctx, uint8_t *req) { int length; @@ -137,8 +141,7 @@ static unsigned int compute_response_length_from_request(modbus_t *ctx, uint8_t /* Header + nb values (code from write_bits) */ int nb = (req[offset + 3] << 8) | req[offset + 4]; length = 2 + (nb / 8) + ((nb % 8) ? 1 : 0); - } - break; + } break; case MODBUS_FC_WRITE_AND_READ_REGISTERS: case MODBUS_FC_READ_HOLDING_REGISTERS: case MODBUS_FC_READ_INPUT_REGISTERS: @@ -186,6 +189,23 @@ static int send_msg(modbus_t *ctx, uint8_t *msg, int msg_length) if (rc == -1) { _error_print(ctx, NULL); if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) { +#ifdef _WIN32 + int saved_errno = errno; + const int wsa_err = WSAGetLastError(); + + if (wsa_err == WSAENETRESET || wsa_err == WSAENOTCONN || + wsa_err == WSAENOTSOCK || wsa_err == WSAESHUTDOWN || + wsa_err == WSAEHOSTUNREACH || wsa_err == WSAECONNABORTED || + wsa_err == WSAECONNRESET || wsa_err == WSAETIMEDOUT) { + modbus_close(ctx); + _sleep_response_timeout(ctx); + modbus_connect(ctx); + } else { + _sleep_response_timeout(ctx); + modbus_flush(ctx); + } + errno = saved_errno; +#else int saved_errno = errno; if ((errno == EBADF || errno == ECONNRESET || errno == EPIPE)) { @@ -197,10 +217,10 @@ static int send_msg(modbus_t *ctx, uint8_t *msg, int msg_length) modbus_flush(ctx); } errno = saved_errno; +#endif } } - } while ((ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) && - rc == -1); + } while ((ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) && rc == -1); if (rc > 0 && rc != msg_length) { errno = EMBBADDATA; @@ -210,13 +230,16 @@ static int send_msg(modbus_t *ctx, uint8_t *msg, int msg_length) return rc; } -int modbus_send_raw_request(modbus_t *ctx, const uint8_t *raw_req, int raw_req_length) +int modbus_send_raw_request_tid(modbus_t *ctx, + const uint8_t *raw_req, + int raw_req_length, + int tid) { sft_t sft; uint8_t req[MAX_MESSAGE_LENGTH]; int req_length; - if (ctx == NULL) { + if (ctx == NULL || raw_req == NULL) { errno = EINVAL; return -1; } @@ -232,7 +255,7 @@ int modbus_send_raw_request(modbus_t *ctx, const uint8_t *raw_req, int raw_req_l sft.slave = raw_req[0]; sft.function = raw_req[1]; /* The t_id is left to zero */ - sft.t_id = 0; + sft.t_id = tid; /* This response function only set the header so it's convenient here */ req_length = ctx->backend->build_response_basis(&sft, req); @@ -245,6 +268,11 @@ int modbus_send_raw_request(modbus_t *ctx, const uint8_t *raw_req, int raw_req_l return send_msg(ctx, req, req_length); } +int modbus_send_raw_request(modbus_t *ctx, const uint8_t *raw_req, int raw_req_length) +{ + return modbus_send_raw_request_tid(ctx, raw_req, raw_req_length, 0); +} + /* * ---------- Request Indication ---------- * | Client | ---------------------->| Server | @@ -252,8 +280,7 @@ int modbus_send_raw_request(modbus_t *ctx, const uint8_t *raw_req, int raw_req_l */ /* Computes the length to read after the function received */ -static uint8_t compute_meta_length_after_function(int function, - msg_type_t msg_type) +static uint8_t compute_meta_length_after_function(int function, msg_type_t msg_type) { int length; @@ -295,7 +322,8 @@ static uint8_t compute_meta_length_after_function(int function, } /* Computes the length to read after the meta information (address, count, etc) */ -static int compute_data_length_after_meta(modbus_t *ctx, uint8_t *msg, +static int +compute_data_length_after_meta(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type, unsigned * objects_to_read) { int function = msg[ctx->backend->header_length]; @@ -336,7 +364,6 @@ static int compute_data_length_after_meta(modbus_t *ctx, uint8_t *msg, return length; } - /* Waits a response from a modbus server or a request from a modbus client. This function blocks if there is no replies (3 timeouts). @@ -345,7 +372,6 @@ static int compute_data_length_after_meta(modbus_t *ctx, uint8_t *msg, and errno is set to one of the values defined below: - ECONNRESET - EMBBADDATA - - EMBUNKEXC - ETIMEDOUT - read() or recv() error codes */ @@ -356,10 +382,13 @@ int _modbus_receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type) fd_set rset; struct timeval tv; struct timeval *p_tv; - int length_to_read; + unsigned int length_to_read; int msg_length = 0; unsigned objects_to_read = 0; _step_t step; +#ifdef _WIN32 + int wsa_err; +#endif if (ctx->debug) { if (msg_type == MSG_INDICATION) { @@ -369,8 +398,22 @@ int _modbus_receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type) } } + if (!ctx->backend->is_connected(ctx)) { + if (ctx->debug) { + fprintf(stderr, "ERROR The connection is not established.\n"); + } + return -1; + } + /* Add a file descriptor to the set */ FD_ZERO(&rset); + if (ctx->s < 0 || ctx->s >= FD_SETSIZE) { + if (ctx->debug) { + fprintf(stderr, "ERROR Invalid socket descriptor %d\n", ctx->s); + } + errno = EINVAL; + return -1; + } FD_SET(ctx->s, &rset); /* We need to analyse the message step by step. At the first step, we want @@ -380,13 +423,13 @@ int _modbus_receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type) length_to_read = ctx->backend->header_length + 1; if (msg_type == MSG_INDICATION) { - /* Wait for a message, we don't know when the message will be - * received */ + /* Wait for a message, we don't know when the message will be received */ if (ctx->indication_timeout.tv_sec == 0 && ctx->indication_timeout.tv_usec == 0) { /* By default, the indication timeout isn't set */ p_tv = NULL; } else { - /* Wait for an indication (name of a received request by a server, see schema) */ + /* Wait for an indication (name of a received request by a server, see schema) + */ tv.tv_sec = ctx->indication_timeout.tv_sec; tv.tv_usec = ctx->indication_timeout.tv_usec; p_tv = &tv; @@ -402,6 +445,18 @@ int _modbus_receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type) if (rc == -1) { _error_print(ctx, "select"); if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) { +#ifdef _WIN32 + int saved_errno = errno; + + wsa_err = WSAGetLastError(); + + // no equivalent to ETIMEDOUT when select fails on Windows + if (wsa_err == WSAENETDOWN || wsa_err == WSAENOTSOCK) { + modbus_close(ctx); + modbus_connect(ctx); + } + errno = saved_errno; +#else int saved_errno = errno; if (errno == ETIMEDOUT) { @@ -413,6 +468,7 @@ int _modbus_receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type) modbus_connect(ctx); } errno = saved_errno; +#endif } return -1; } @@ -425,22 +481,36 @@ int _modbus_receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type) if (rc == -1) { _error_print(ctx, "read"); +#ifdef _WIN32 + wsa_err = WSAGetLastError(); if ((ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) && - (errno == ECONNRESET || errno == ECONNREFUSED || - errno == EBADF)) { + (wsa_err == WSAENOTCONN || wsa_err == WSAENETRESET || + wsa_err == WSAENOTSOCK || wsa_err == WSAESHUTDOWN || + wsa_err == WSAECONNABORTED || wsa_err == WSAETIMEDOUT || + wsa_err == WSAECONNRESET)) { int saved_errno = errno; modbus_close(ctx); modbus_connect(ctx); /* Could be removed by previous calls */ errno = saved_errno; } +#else + if ((ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) && + (errno == ECONNRESET || errno == ECONNREFUSED || errno == EBADF)) { + int saved_errno = errno; + modbus_close(ctx); + modbus_connect(ctx); + /* Could be removed by previous calls */ + errno = saved_errno; + } +#endif return -1; } /* Display the hex code of each character received */ if (ctx->debug) { int i; - for (i=0; i < rc; i++) + for (i = 0; i < rc; i++) printf("<%.2X>", msg[msg_length + i]); } @@ -454,8 +524,7 @@ int _modbus_receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type) case _STEP_FUNCTION: /* Function code position */ length_to_read = compute_meta_length_after_function( - msg[ctx->backend->header_length], - msg_type); + msg[ctx->backend->header_length], msg_type); if (length_to_read != 0) { step = _STEP_META; break; @@ -463,7 +532,7 @@ int _modbus_receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type) case _STEP_META: length_to_read = compute_data_length_after_meta( ctx, msg, msg_type, &objects_to_read); - if ((msg_length + length_to_read) > (int)ctx->backend->max_adu_length) { + if ((msg_length + length_to_read) > ctx->backend->max_adu_length) { errno = EMBBADDATA; _error_print(ctx, "too many data"); return -1; @@ -478,7 +547,7 @@ int _modbus_receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type) } else { length_to_read += ctx->backend->checksum_length; } - if ((msg_length + length_to_read) > (int)ctx->backend->max_adu_length) { + if ((msg_length + length_to_read) > ctx->backend->max_adu_length) { errno = EMBBADDATA; _error_print(ctx, "too many data"); return -1; @@ -538,12 +607,11 @@ int modbus_receive_confirmation(modbus_t *ctx, uint8_t *rsp) return _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION); } -static int check_confirmation(modbus_t *ctx, uint8_t *req, - uint8_t *rsp, int rsp_length) +static int check_confirmation(modbus_t *ctx, uint8_t *req, uint8_t *rsp, int rsp_length) { int rc; int rsp_length_computed; - const int offset = ctx->backend->header_length; + const unsigned int offset = ctx->backend->header_length; const int function = rsp[offset]; if (ctx->backend->pre_check_confirmation) { @@ -561,7 +629,7 @@ static int check_confirmation(modbus_t *ctx, uint8_t *req, /* Exception code */ if (function >= 0x80) { - if (rsp_length == (offset + 2 + (int)ctx->backend->checksum_length) && + if (rsp_length == (int) (offset + 2 + ctx->backend->checksum_length) && req[offset] == (rsp[offset] - 0x80)) { /* Valid exception code received */ @@ -586,13 +654,17 @@ static int check_confirmation(modbus_t *ctx, uint8_t *req, function < 0x80) { int req_nb_value; int rsp_nb_value; + int resp_addr_ok = TRUE; + int resp_data_ok = TRUE; /* Check function code */ if (function != req[offset]) { if (ctx->debug) { - fprintf(stderr, - "Received function not corresponding to the request (0x%X != 0x%X)\n", - function, req[offset]); + fprintf( + stderr, + "Received function not corresponding to the request (0x%X != 0x%X)\n", + function, + req[offset]); } if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) { _sleep_response_timeout(ctx); @@ -622,6 +694,11 @@ static int check_confirmation(modbus_t *ctx, uint8_t *req, break; case MODBUS_FC_WRITE_MULTIPLE_COILS: case MODBUS_FC_WRITE_MULTIPLE_REGISTERS: + /* address in request and response must be equal */ + if ((req[offset + 1] != rsp[offset + 1]) || + (req[offset + 2] != rsp[offset + 2])) { + resp_addr_ok = FALSE; + } /* N Write functions */ req_nb_value = (req[offset + 3] << 8) + req[offset + 4]; rsp_nb_value = (rsp[offset + 3] << 8) | rsp[offset + 4]; @@ -630,18 +707,36 @@ static int check_confirmation(modbus_t *ctx, uint8_t *req, /* Report slave ID (bytes received) */ req_nb_value = rsp_nb_value = rsp[offset + 1]; break; + case MODBUS_FC_WRITE_SINGLE_COIL: + case MODBUS_FC_WRITE_SINGLE_REGISTER: + /* address in request and response must be equal */ + if ((req[offset + 1] != rsp[offset + 1]) || + (req[offset + 2] != rsp[offset + 2])) { + resp_addr_ok = FALSE; + } + /* data in request and response must be equal */ + if ((req[offset + 3] != rsp[offset + 3]) || + (req[offset + 4] != rsp[offset + 4])) { + resp_data_ok = FALSE; + } + /* 1 Write functions & others */ + req_nb_value = rsp_nb_value = 1; + break; default: /* 1 Write functions & others */ req_nb_value = rsp_nb_value = 1; + break; } - if (req_nb_value == rsp_nb_value) { + if ((req_nb_value == rsp_nb_value) && (resp_addr_ok == TRUE) && + (resp_data_ok == TRUE)) { rc = rsp_nb_value; } else { if (ctx->debug) { fprintf(stderr, - "Quantity not corresponding to the request (%d != %d)\n", - rsp_nb_value, req_nb_value); + "Received data not corresponding to the request (%d != %d)\n", + rsp_nb_value, + req_nb_value); } if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) { @@ -654,9 +749,11 @@ static int check_confirmation(modbus_t *ctx, uint8_t *req, } } else { if (ctx->debug) { - fprintf(stderr, - "Message length not corresponding to the computed length (%d != %d)\n", - rsp_length, rsp_length_computed); + fprintf( + stderr, + "Message length not corresponding to the computed length (%d != %d)\n", + rsp_length, + rsp_length_computed); } if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) { _sleep_response_timeout(ctx); @@ -669,9 +766,8 @@ static int check_confirmation(modbus_t *ctx, uint8_t *req, return rc; } -static int response_io_status(uint8_t *tab_io_status, - int address, int nb, - uint8_t *rsp, int offset) +static int +response_io_status(uint8_t *tab_io_status, int address, int nb, uint8_t *rsp, int offset) { int shift = 0; /* Instead of byte (not allowed in Win32) */ @@ -696,10 +792,13 @@ static int response_io_status(uint8_t *tab_io_status, } /* Build the exception response */ -static int response_exception(modbus_t *ctx, sft_t *sft, - int exception_code, uint8_t *rsp, +static int response_exception(modbus_t *ctx, + sft_t *sft, + int exception_code, + uint8_t *rsp, unsigned int to_flush, - const char* template, ...) + const char *template, + ...) { int rsp_length; @@ -732,30 +831,83 @@ static int response_exception(modbus_t *ctx, sft_t *sft, If an error occurs, this function construct the response accordingly. */ -int modbus_reply(modbus_t *ctx, const uint8_t *req, - int req_length, modbus_mapping_t *mb_mapping) +int modbus_reply(modbus_t *ctx, + const uint8_t *req, + int req_length, + modbus_mapping_t *mb_mapping) { - int offset; + unsigned int offset; int slave; int function; uint16_t address; uint8_t rsp[MAX_MESSAGE_LENGTH]; int rsp_length = 0; sft_t sft; + uint8_t meta_length; + int data_length; + unsigned objects_to_read = 0; - if (ctx == NULL) { + if (ctx == NULL || req == NULL || mb_mapping == NULL) { errno = EINVAL; return -1; } offset = ctx->backend->header_length; + + /* The request must contain at least a slave address and a function code + before they can be read. */ + if (req_length < (int) (offset + 1)) { + errno = EMBBADDATA; + return -1; + } + slave = req[offset - 1]; function = req[offset]; - address = (req[offset + 1] << 8) + req[offset + 2]; + + /* Some function codes (eg. FC_READ_EXCEPTION_STATUS, FC_REPORT_SLAVE_ID) + carry no address in the request PDU, so only read when available. Reading + them is safe as it fits within MODBUS_MAX_ADU_LENGTH, it's just for + coherency. + */ + if (req_length >= (int) (offset + 3)) + address = (req[offset + 1] << 8) + req[offset + 2]; + else + address = 0; sft.slave = slave; sft.function = function; - sft.t_id = ctx->backend->prepare_response_tid(req, &req_length); + sft.t_id = ctx->backend->get_response_tid(req); + + /* Ensure the request is long enough to contain the meta fields (address, + quantity, byte count...) and the declared data read by the handlers + below. On the canonical modbus_receive() path the framing layer already + guarantees a complete PDU; this check defends direct callers that pass a + truncated or untrusted request to modbus_reply(). */ + meta_length = compute_meta_length_after_function(function, MSG_INDICATION); + data_length = 0; + if (req_length >= (int) (offset + 1 + meta_length)) { + /* The meta fields (including the byte count) are present, so the + declared data length can be read safely. */ + data_length = compute_data_length_after_meta(ctx, (uint8_t *) req, MSG_INDICATION, &objects_to_read); + } + if (req_length < (int) (offset + 1 + meta_length + data_length)) { + rsp_length = response_exception(ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, + rsp, + TRUE, + "Truncated request (length %d) for function " + "0x%0X in modbus_reply\n", + req_length, + function); + /* Suppress responses to broadcasts in RTU, see below. */ + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU && + slave == MODBUS_BROADCAST_ADDRESS && + !(ctx->quirks & MODBUS_QUIRK_REPLY_TO_BROADCAST)) { + return 0; + } + return send_msg(ctx, rsp, rsp_length); + } /* Data are flushed on illegal number of values errors. */ switch (function) { @@ -765,53 +917,72 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req, int start_bits = is_input ? mb_mapping->start_input_bits : mb_mapping->start_bits; int nb_bits = is_input ? mb_mapping->nb_input_bits : mb_mapping->nb_bits; uint8_t *tab_bits = is_input ? mb_mapping->tab_input_bits : mb_mapping->tab_bits; - const char * const name = is_input ? "read_input_bits" : "read_bits"; + const char *const name = is_input ? "read_input_bits" : "read_bits"; int nb = (req[offset + 3] << 8) + req[offset + 4]; /* The mapping can be shifted to reduce memory consumption and it doesn't always start at address zero. */ int mapping_address = address - start_bits; if (nb < 1 || MODBUS_MAX_READ_BITS < nb) { - rsp_length = response_exception( - ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, TRUE, - "Illegal nb of values %d in %s (max %d)\n", - nb, name, MODBUS_MAX_READ_BITS); + rsp_length = response_exception(ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, + rsp, + TRUE, + "Illegal nb of values %d in %s (max %d)\n", + nb, + name, + MODBUS_MAX_READ_BITS); } else if (mapping_address < 0 || (mapping_address + nb) > nb_bits) { - rsp_length = response_exception( - ctx, &sft, - MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE, - "Illegal data address 0x%0X in %s\n", - mapping_address < 0 ? address : address + nb, name); + rsp_length = response_exception(ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, + rsp, + FALSE, + "Illegal data address 0x%0X in %s\n", + mapping_address < 0 ? address : address + nb, + name); } else { rsp_length = ctx->backend->build_response_basis(&sft, rsp); rsp[rsp_length++] = (nb / 8) + ((nb % 8) ? 1 : 0); - rsp_length = response_io_status(tab_bits, mapping_address, nb, - rsp, rsp_length); + rsp_length = + response_io_status(tab_bits, mapping_address, nb, rsp, rsp_length); } - } - break; + } break; case MODBUS_FC_READ_HOLDING_REGISTERS: case MODBUS_FC_READ_INPUT_REGISTERS: { unsigned int is_input = (function == MODBUS_FC_READ_INPUT_REGISTERS); - int start_registers = is_input ? mb_mapping->start_input_registers : mb_mapping->start_registers; - int nb_registers = is_input ? mb_mapping->nb_input_registers : mb_mapping->nb_registers; - uint16_t *tab_registers = is_input ? mb_mapping->tab_input_registers : mb_mapping->tab_registers; - const char * const name = is_input ? "read_input_registers" : "read_registers"; + int start_registers = + is_input ? mb_mapping->start_input_registers : mb_mapping->start_registers; + int nb_registers = + is_input ? mb_mapping->nb_input_registers : mb_mapping->nb_registers; + uint16_t *tab_registers = + is_input ? mb_mapping->tab_input_registers : mb_mapping->tab_registers; + const char *const name = is_input ? "read_input_registers" : "read_registers"; int nb = (req[offset + 3] << 8) + req[offset + 4]; /* The mapping can be shifted to reduce memory consumption and it doesn't always start at address zero. */ int mapping_address = address - start_registers; if (nb < 1 || MODBUS_MAX_READ_REGISTERS < nb) { - rsp_length = response_exception( - ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, TRUE, - "Illegal nb of values %d in %s (max %d)\n", - nb, name, MODBUS_MAX_READ_REGISTERS); + rsp_length = response_exception(ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, + rsp, + TRUE, + "Illegal nb of values %d in %s (max %d)\n", + nb, + name, + MODBUS_MAX_READ_REGISTERS); } else if (mapping_address < 0 || (mapping_address + nb) > nb_registers) { - rsp_length = response_exception( - ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE, - "Illegal data address 0x%0X in %s\n", - mapping_address < 0 ? address : address + nb, name); + rsp_length = response_exception(ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, + rsp, + FALSE, + "Illegal data address 0x%0X in %s\n", + mapping_address < 0 ? address : address + nb, + name); } else { int i; @@ -822,51 +993,93 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req, rsp[rsp_length++] = tab_registers[i] & 0xFF; } } - } - break; + } break; case MODBUS_FC_WRITE_SINGLE_COIL: { int mapping_address = address - mb_mapping->start_bits; if (mapping_address < 0 || mapping_address >= mb_mapping->nb_bits) { + rsp_length = response_exception(ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, + rsp, + FALSE, + "Illegal data address 0x%0X in write bit\n", + address); + break; + } + + /* This check is only done here to ensure using memcpy is safe. */ + rsp_length = compute_response_length_from_request(ctx, (uint8_t *) req); + if (rsp_length != req_length) { + /* Bad use of modbus_reply */ rsp_length = response_exception( - ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE, - "Illegal data address 0x%0X in write_bit\n", - address); - } else { - int data = (req[offset + 3] << 8) + req[offset + 4]; + ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, + rsp, + FALSE, + "Invalid request length in modbus_reply to write bit (%d)\n", + req_length); + break; + } - if (data == 0xFF00 || data == 0x0) { - mb_mapping->tab_bits[mapping_address] = data ? ON : OFF; - memcpy(rsp, req, req_length); - rsp_length = req_length; - } else { - rsp_length = response_exception( - ctx, &sft, - MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, FALSE, - "Illegal data value 0x%0X in write_bit request at address %0X\n", - data, address); - } + /* Don't copy the CRC, if any, it will be computed later (even if identical to the + * request) */ + rsp_length -= ctx->backend->checksum_length; + + int data = (req[offset + 3] << 8) + req[offset + 4]; + if (data == 0xFF00 || data == 0x0) { + /* Apply the change to mapping */ + mb_mapping->tab_bits[mapping_address] = data ? ON : OFF; + /* Prepare response */ + memcpy(rsp, req, rsp_length); + } else { + rsp_length = response_exception( + ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, + rsp, + FALSE, + "Illegal data value 0x%0X in write_bit request at address %0X\n", + data, + address); } - } - break; + } break; case MODBUS_FC_WRITE_SINGLE_REGISTER: { int mapping_address = address - mb_mapping->start_registers; if (mapping_address < 0 || mapping_address >= mb_mapping->nb_registers) { - rsp_length = response_exception( - ctx, &sft, - MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE, - "Illegal data address 0x%0X in write_register\n", - address); - } else { - int data = (req[offset + 3] << 8) + req[offset + 4]; + rsp_length = + response_exception(ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, + rsp, + FALSE, + "Illegal data address 0x%0X in write_register\n", + address); + break; + } - mb_mapping->tab_registers[mapping_address] = data; - memcpy(rsp, req, req_length); - rsp_length = req_length; + rsp_length = compute_response_length_from_request(ctx, (uint8_t *) req); + if (rsp_length != req_length) { + /* Bad use of modbus_reply */ + rsp_length = response_exception( + ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, + rsp, + FALSE, + "Invalid request length in modbus_reply to write register (%d)\n", + req_length); + break; } - } - break; + int data = (req[offset + 3] << 8) + req[offset + 4]; + + mb_mapping->tab_registers[mapping_address] = data; + + rsp_length -= ctx->backend->checksum_length; + memcpy(rsp, req, rsp_length); + } break; case MODBUS_FC_WRITE_MULTIPLE_COILS: { int nb = (req[offset + 3] << 8) + req[offset + 4]; int nb_bits = req[offset + 5]; @@ -876,29 +1089,34 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req, /* May be the indication has been truncated on reading because of * invalid address (eg. nb is 0 but the request contains values to * write) so it's necessary to flush. */ - rsp_length = response_exception( - ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, TRUE, - "Illegal number of values %d in write_bits (max %d)\n", - nb, MODBUS_MAX_WRITE_BITS); - } else if (mapping_address < 0 || - (mapping_address + nb) > mb_mapping->nb_bits) { - rsp_length = response_exception( - ctx, &sft, - MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE, - "Illegal data address 0x%0X in write_bits\n", - mapping_address < 0 ? address : address + nb); + rsp_length = + response_exception(ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, + rsp, + TRUE, + "Illegal number of values %d in write_bits (max %d)\n", + nb, + MODBUS_MAX_WRITE_BITS); + } else if (mapping_address < 0 || (mapping_address + nb) > mb_mapping->nb_bits) { + rsp_length = response_exception(ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, + rsp, + FALSE, + "Illegal data address 0x%0X in write_bits\n", + mapping_address < 0 ? address : address + nb); } else { /* 6 = byte count */ - modbus_set_bits_from_bytes(mb_mapping->tab_bits, mapping_address, nb, - &req[offset + 6]); + modbus_set_bits_from_bytes( + mb_mapping->tab_bits, mapping_address, nb, &req[offset + 6]); rsp_length = ctx->backend->build_response_basis(&sft, rsp); /* 4 to copy the bit address (2) and the quantity of bits */ memcpy(rsp + rsp_length, req + rsp_length, 4); rsp_length += 4; } - } - break; + } break; case MODBUS_FC_WRITE_MULTIPLE_REGISTERS: { int nb = (req[offset + 3] << 8) + req[offset + 4]; int nb_bytes = req[offset + 5]; @@ -906,15 +1124,24 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req, if (nb < 1 || MODBUS_MAX_WRITE_REGISTERS < nb || nb_bytes != nb * 2) { rsp_length = response_exception( - ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, TRUE, + ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, + rsp, + TRUE, "Illegal number of values %d in write_registers (max %d)\n", - nb, MODBUS_MAX_WRITE_REGISTERS); + nb, + MODBUS_MAX_WRITE_REGISTERS); } else if (mapping_address < 0 || (mapping_address + nb) > mb_mapping->nb_registers) { - rsp_length = response_exception( - ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE, - "Illegal data address 0x%0X in write_registers\n", - mapping_address < 0 ? address : address + nb); + rsp_length = + response_exception(ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, + rsp, + FALSE, + "Illegal data address 0x%0X in write_registers\n", + mapping_address < 0 ? address : address + nb); } else { int i, j; for (i = mapping_address, j = 6; i < mapping_address + nb; i++, j += 2) { @@ -928,8 +1155,7 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req, memcpy(rsp + rsp_length, req + rsp_length, 4); rsp_length += 4; } - } - break; + } break; case MODBUS_FC_REPORT_SLAVE_ID: { int str_len; int byte_count_pos; @@ -945,35 +1171,54 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req, memcpy(rsp + rsp_length, "LMB" LIBMODBUS_VERSION_STRING, str_len); rsp_length += str_len; rsp[byte_count_pos] = rsp_length - byte_count_pos - 1; - } - break; + } break; case MODBUS_FC_READ_EXCEPTION_STATUS: - if (ctx->debug) { - fprintf(stderr, "FIXME Not implemented\n"); - } - errno = ENOPROTOOPT; - return -1; + rsp_length = response_exception(ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_FUNCTION, + rsp, + TRUE, + "Unsupported function: READ EXCEPTION STATUS (0x07)\n"); break; case MODBUS_FC_MASK_WRITE_REGISTER: { int mapping_address = address - mb_mapping->start_registers; if (mapping_address < 0 || mapping_address >= mb_mapping->nb_registers) { - rsp_length = response_exception( - ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE, - "Illegal data address 0x%0X in write_register\n", - address); + rsp_length = + response_exception(ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, + rsp, + FALSE, + "Illegal data address 0x%0X in write_register\n", + address); } else { - uint16_t data = mb_mapping->tab_registers[mapping_address]; + uint16_t data; uint16_t and = (req[offset + 3] << 8) + req[offset + 4]; uint16_t or = (req[offset + 5] << 8) + req[offset + 6]; + rsp_length = compute_response_length_from_request(ctx, (uint8_t *) req); + if (rsp_length != req_length) { + /* Bad use of modbus_reply */ + rsp_length = response_exception(ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, + rsp, + FALSE, + "Invalid request length in modbus_reply " + "to mask write register (%d)\n", + req_length); + break; + } + + data = mb_mapping->tab_registers[mapping_address]; data = (data & and) | (or & (~and)); mb_mapping->tab_registers[mapping_address] = data; - memcpy(rsp, req, req_length); - rsp_length = req_length; + + rsp_length -= ctx->backend->checksum_length; + memcpy(rsp, req, rsp_length); } - } - break; + } break; case MODBUS_FC_WRITE_AND_READ_REGISTERS: { int nb = (req[offset + 3] << 8) + req[offset + 4]; uint16_t address_write = (req[offset + 5] << 8) + req[offset + 6]; @@ -982,20 +1227,32 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req, int mapping_address = address - mb_mapping->start_registers; int mapping_address_write = address_write - mb_mapping->start_registers; - if (nb_write < 1 || MODBUS_MAX_WR_WRITE_REGISTERS < nb_write || - nb < 1 || MODBUS_MAX_WR_READ_REGISTERS < nb || - nb_write_bytes != nb_write * 2) { + if (nb_write < 1 || MODBUS_MAX_WR_WRITE_REGISTERS < nb_write || nb < 1 || + MODBUS_MAX_WR_READ_REGISTERS < nb || nb_write_bytes != nb_write * 2) { rsp_length = response_exception( - ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, TRUE, - "Illegal nb of values (W%d, R%d) in write_and_read_registers (max W%d, R%d)\n", - nb_write, nb, MODBUS_MAX_WR_WRITE_REGISTERS, MODBUS_MAX_WR_READ_REGISTERS); + ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, + rsp, + TRUE, + "Illegal nb of values (W%d, R%d) in write_and_read_registers (max W%d, " + "R%d)\n", + nb_write, + nb, + MODBUS_MAX_WR_WRITE_REGISTERS, + MODBUS_MAX_WR_READ_REGISTERS); } else if (mapping_address < 0 || (mapping_address + nb) > mb_mapping->nb_registers || - mapping_address < 0 || + mapping_address_write < 0 || (mapping_address_write + nb_write) > mb_mapping->nb_registers) { rsp_length = response_exception( - ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE, - "Illegal data read address 0x%0X or write address 0x%0X write_and_read_registers\n", + ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, + rsp, + FALSE, + "Illegal data read address 0x%0X or write address 0x%0X " + "write_and_read_registers\n", mapping_address < 0 ? address : address + nb, mapping_address_write < 0 ? address_write : address_write + nb_write); } else { @@ -1005,8 +1262,8 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req, /* Write first. 10 and 11 are the offset of the first values to write */ - for (i = mapping_address_write, j = 10; - i < mapping_address_write + nb_write; i++, j += 2) { + for (i = mapping_address_write, j = 10; i < mapping_address_write + nb_write; + i++, j += 2) { mb_mapping->tab_registers[i] = (req[offset + j] << 8) + req[offset + j + 1]; } @@ -1017,33 +1274,39 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req, rsp[rsp_length++] = mb_mapping->tab_registers[i] & 0xFF; } } - } - break; + } break; default: - rsp_length = response_exception( - ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_FUNCTION, rsp, TRUE, - "Unknown Modbus function code: 0x%0X\n", function); + rsp_length = response_exception(ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_FUNCTION, + rsp, + TRUE, + "Unknown Modbus function code: 0x%0X\n", + function); break; } - /* Suppress any responses when the request was a broadcast */ - return (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU && - slave == MODBUS_BROADCAST_ADDRESS) ? 0 : send_msg(ctx, rsp, rsp_length); + /* Suppress any responses in RTU when the request was a broadcast, excepted when + * quirk is enabled. */ + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU && + slave == MODBUS_BROADCAST_ADDRESS && + !(ctx->quirks & MODBUS_QUIRK_REPLY_TO_BROADCAST)) { + return 0; + } + return send_msg(ctx, rsp, rsp_length); } -int modbus_reply_exception(modbus_t *ctx, const uint8_t *req, - unsigned int exception_code) +int modbus_reply_exception(modbus_t *ctx, const uint8_t *req, unsigned int exception_code) { - int offset; + unsigned int offset; int slave; int function; uint8_t rsp[MAX_MESSAGE_LENGTH]; int rsp_length; - int dummy_length = 99; sft_t sft; - if (ctx == NULL) { + if (ctx == NULL || req == NULL) { errno = EINVAL; return -1; } @@ -1054,7 +1317,7 @@ int modbus_reply_exception(modbus_t *ctx, const uint8_t *req, sft.slave = slave; sft.function = function + 0x80; - sft.t_id = ctx->backend->prepare_response_tid(req, &dummy_length); + sft.t_id = ctx->backend->get_response_tid(req); rsp_length = ctx->backend->build_response_basis(&sft, rsp); /* Positive exception code */ @@ -1067,9 +1330,155 @@ int modbus_reply_exception(modbus_t *ctx, const uint8_t *req, } } +/* Forward a request received on one context to another and relay the response back. + This function is useful to implement a Modbus gateway/proxy that bridges + two different backends (eg. TCP to RTU). */ +int modbus_proxy(modbus_t *frontend_ctx, + modbus_t *backend_ctx, + const uint8_t *req, + int req_length) +{ + int rc; + int frontend_header_length; + int frontend_checksum_length; + int backend_header_length; + int backend_checksum_length; + int pdu_length; + uint8_t backend_req[MAX_MESSAGE_LENGTH]; + int backend_req_length; + uint8_t backend_rsp[MAX_MESSAGE_LENGTH]; + int backend_rsp_length; + uint8_t frontend_rsp[MAX_MESSAGE_LENGTH]; + int frontend_rsp_length; + sft_t sft; + + if (frontend_ctx == NULL || backend_ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (req == NULL || req_length < 1) { + errno = EINVAL; + return -1; + } + + frontend_header_length = frontend_ctx->backend->header_length; + frontend_checksum_length = frontend_ctx->backend->checksum_length; + backend_header_length = backend_ctx->backend->header_length; + backend_checksum_length = backend_ctx->backend->checksum_length; + + /* Extract the PDU (slave + function + data) from the frontend request. + The PDU sits between the header and the checksum. */ + pdu_length = req_length - frontend_header_length - frontend_checksum_length; + if (pdu_length < 1 || pdu_length > MODBUS_MAX_PDU_LENGTH + 1) { + errno = EMBBADDATA; + return -1; + } + + /* Set the slave address on the backend context */ + rc = modbus_set_slave(backend_ctx, req[frontend_header_length - 1]); + if (rc == -1) { + modbus_reply_exception(frontend_ctx, req, MODBUS_EXCEPTION_GATEWAY_PATH); + return -1; + } + + /* Build a raw request for the backend from the PDU. + The raw request for modbus_send_raw_request is: slave + function + data + (ie. the PDU from the frontend request, starting at header_length - 1). + pdu_length is bounded above by the check on MODBUS_MAX_PDU_LENGTH, so + pdu_length + 1 always fits in backend_req[MAX_MESSAGE_LENGTH]. */ + memcpy(backend_req, req + frontend_header_length - 1, pdu_length + 1); + backend_req_length = pdu_length + 1; + + /* Keep the transaction ID of the frontend request to restore it in the + response sent to the frontend */ + sft.slave = backend_req[0]; + sft.function = backend_req[1]; + sft.t_id = frontend_ctx->backend->get_response_tid(req); + + /* Send the request to the backend device using raw request. + modbus_send_raw_request_tid wraps the PDU into the backend framing. + The backend request uses transaction ID 0 and the pre-check of the + confirmation below relies on it (check_sft.t_id) */ + rc = modbus_send_raw_request_tid( + backend_ctx, backend_req, backend_req_length, 0); + if (rc == -1) { + modbus_reply_exception(frontend_ctx, req, MODBUS_EXCEPTION_GATEWAY_PATH); + return -1; + } + + /* Receive the response from the backend */ + backend_rsp_length = _modbus_receive_msg(backend_ctx, backend_rsp, MSG_CONFIRMATION); + if (backend_rsp_length == -1) { + if (errno == ETIMEDOUT) + modbus_reply_exception( + frontend_ctx, req, MODBUS_EXCEPTION_GATEWAY_TARGET); + else + modbus_reply_exception( + frontend_ctx, req, MODBUS_EXCEPTION_GATEWAY_PATH); + return -1; + } + + /* Check the backend response integrity */ + if (backend_ctx->backend->pre_check_confirmation) { + /* Build a minimal req for the pre_check (only header matters for TID/slave check). + We reuse the backend_req buffer which still holds our sent data. */ + uint8_t check_req[MAX_MESSAGE_LENGTH]; + int check_req_length; + sft_t check_sft; + + check_sft.slave = sft.slave; + check_sft.function = sft.function; + check_sft.t_id = 0; + check_req_length = backend_ctx->backend->build_response_basis(&check_sft, check_req); + /* Append enough data so the length is valid */ + if (backend_req_length > 2) { + memcpy(check_req + check_req_length, backend_req + 2, backend_req_length - 2); + check_req_length += backend_req_length - 2; + } + + rc = backend_ctx->backend->pre_check_confirmation( + backend_ctx, check_req, backend_rsp, backend_rsp_length); + if (rc == -1) { + modbus_reply_exception( + frontend_ctx, req, MODBUS_EXCEPTION_GATEWAY_TARGET); + return -1; + } + } + + /* Extract the PDU from the backend response and wrap it for the frontend. + Backend response layout: [header][function][data...][checksum] */ + pdu_length = + backend_rsp_length - backend_header_length - backend_checksum_length; + if (pdu_length < 1) { + modbus_reply_exception( + frontend_ctx, req, MODBUS_EXCEPTION_GATEWAY_TARGET); + return -1; + } + + /* Build the frontend response: use the original slave and function from the + backend response, and the transaction ID from the frontend request. */ + sft.slave = backend_rsp[backend_header_length - 1]; + sft.function = backend_rsp[backend_header_length]; + /* sft.t_id was already set from the frontend request above */ + + frontend_rsp_length = + frontend_ctx->backend->build_response_basis(&sft, frontend_rsp); + + /* Copy the remaining PDU data (everything after function code) */ + if (pdu_length > 1) { + int data_length = pdu_length - 1; + memcpy(frontend_rsp + frontend_rsp_length, + backend_rsp + backend_header_length + 1, + data_length); + frontend_rsp_length += data_length; + } + + return send_msg(frontend_ctx, frontend_rsp, frontend_rsp_length); +} + /* Reads IO status */ -static int read_io_status(modbus_t *ctx, int function, - int addr, int nb, uint8_t *dest) +static int read_io_status(modbus_t *ctx, int function, int addr, int nb, uint8_t *dest) { int rc; int req_length; @@ -1081,10 +1490,11 @@ static int read_io_status(modbus_t *ctx, int function, rc = send_msg(ctx, req, req_length); if (rc > 0) { - int i, temp, bit; + int temp, bit; int pos = 0; - int offset; - int offset_end; + unsigned int offset; + unsigned int offset_end; + unsigned int i; do { @@ -1107,7 +1517,6 @@ static int read_io_status(modbus_t *ctx, int function, dest[pos++] = (temp & bit) ? TRUE : FALSE; bit = bit << 1; } - } } @@ -1120,18 +1529,19 @@ int modbus_read_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest) { int rc; - if (ctx == NULL) { + if (ctx == NULL || dest == NULL) { errno = EINVAL; return -1; } - if (nb > MODBUS_MAX_READ_BITS) { + if (nb < 1 || nb > MODBUS_MAX_READ_BITS) { if (ctx->debug) { fprintf(stderr, "ERROR Too many bits requested (%d > %d)\n", - nb, MODBUS_MAX_READ_BITS); + nb, + MODBUS_MAX_READ_BITS); } - errno = EMBMDATA; + errno = EMBXILVAL; return -1; } @@ -1143,24 +1553,24 @@ int modbus_read_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest) return nb; } - /* Same as modbus_read_bits but reads the remote device input table */ int modbus_read_input_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest) { int rc; - if (ctx == NULL) { + if (ctx == NULL || dest == NULL) { errno = EINVAL; return -1; } - if (nb > MODBUS_MAX_READ_BITS) { + if (nb < 1 || nb > MODBUS_MAX_READ_BITS) { if (ctx->debug) { fprintf(stderr, "ERROR Too many discrete inputs requested (%d > %d)\n", - nb, MODBUS_MAX_READ_BITS); + nb, + MODBUS_MAX_READ_BITS); } - errno = EMBMDATA; + errno = EMBXILVAL; return -1; } @@ -1172,9 +1582,8 @@ int modbus_read_input_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest) return nb; } -/* Reads the data from a remove device and put that data into an array */ -static int read_registers(modbus_t *ctx, int function, int addr, int nb, - uint16_t *dest) +/* Reads the data from a remote device and put that data into an array */ +static int read_registers(modbus_t *ctx, int function, int addr, int nb, uint16_t *dest) { int rc; int req_length; @@ -1185,9 +1594,10 @@ static int read_registers(modbus_t *ctx, int function, int addr, int nb, if (ctx->debug) { fprintf(stderr, "ERROR Too many registers requested (%d > %d)\n", - nb, MODBUS_MAX_READ_REGISTERS); + nb, + MODBUS_MAX_READ_REGISTERS); } - errno = EMBMDATA; + errno = EMBXILVAL; return -1; } @@ -1195,7 +1605,7 @@ static int read_registers(modbus_t *ctx, int function, int addr, int nb, rc = send_msg(ctx, req, req_length); if (rc > 0) { - int offset; + unsigned int offset; int i; do @@ -1214,8 +1624,7 @@ static int read_registers(modbus_t *ctx, int function, int addr, int nb, for (i = 0; i < rc; i++) { /* shift reg hi_byte to temp OR with lo_byte */ - dest[i] = (rsp[offset + 2 + (i << 1)] << 8) | - rsp[offset + 3 + (i << 1)]; + dest[i] = (rsp[offset + 2 + (i << 1)] << 8) | rsp[offset + 3 + (i << 1)]; } } @@ -1228,47 +1637,48 @@ int modbus_read_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest) { int status; - if (ctx == NULL) { + if (ctx == NULL || dest == NULL) { errno = EINVAL; return -1; } - if (nb > MODBUS_MAX_READ_REGISTERS) { + if (nb < 1 || nb > MODBUS_MAX_READ_REGISTERS) { if (ctx->debug) { fprintf(stderr, "ERROR Too many registers requested (%d > %d)\n", - nb, MODBUS_MAX_READ_REGISTERS); + nb, + MODBUS_MAX_READ_REGISTERS); } - errno = EMBMDATA; + errno = EMBXILVAL; return -1; } - status = read_registers(ctx, MODBUS_FC_READ_HOLDING_REGISTERS, - addr, nb, dest); + status = read_registers(ctx, MODBUS_FC_READ_HOLDING_REGISTERS, addr, nb, dest); return status; } /* Reads the input registers of remote device and put the data into an array */ -int modbus_read_input_registers(modbus_t *ctx, int addr, int nb, - uint16_t *dest) +int modbus_read_input_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest) { int status; - if (ctx == NULL) { + if (ctx == NULL || dest == NULL) { errno = EINVAL; return -1; } - if (nb > MODBUS_MAX_READ_REGISTERS) { - fprintf(stderr, - "ERROR Too many input registers requested (%d > %d)\n", - nb, MODBUS_MAX_READ_REGISTERS); - errno = EMBMDATA; + if (nb < 1 || nb > MODBUS_MAX_READ_REGISTERS) { + if (ctx->debug) { + fprintf(stderr, + "ERROR Too many input registers requested (%d > %d)\n", + nb, + MODBUS_MAX_READ_REGISTERS); + } + errno = EMBXILVAL; return -1; } - status = read_registers(ctx, MODBUS_FC_READ_INPUT_REGISTERS, - addr, nb, dest); + status = read_registers(ctx, MODBUS_FC_READ_INPUT_REGISTERS, addr, nb, dest); return status; } @@ -1314,8 +1724,7 @@ int modbus_write_bit(modbus_t *ctx, int addr, int status) return -1; } - return write_single(ctx, MODBUS_FC_WRITE_SINGLE_COIL, addr, - status ? 0xFF00 : 0); + return write_single(ctx, MODBUS_FC_WRITE_SINGLE_COIL, addr, status ? 0xFF00 : 0); } /* Writes a value in one register of the remote device */ @@ -1340,23 +1749,24 @@ int modbus_write_bits(modbus_t *ctx, int addr, int nb, const uint8_t *src) int pos = 0; uint8_t req[MAX_MESSAGE_LENGTH]; - if (ctx == NULL) { + if (ctx == NULL || src == NULL) { errno = EINVAL; return -1; } - if (nb > MODBUS_MAX_WRITE_BITS) { + if (nb < 1 || nb > MODBUS_MAX_WRITE_BITS) { if (ctx->debug) { - fprintf(stderr, "ERROR Writing too many bits (%d > %d)\n", - nb, MODBUS_MAX_WRITE_BITS); + fprintf(stderr, + "ERROR Writing too many bits (%d > %d)\n", + nb, + MODBUS_MAX_WRITE_BITS); } - errno = EMBMDATA; + errno = EMBXILVAL; return -1; } - req_length = ctx->backend->build_request_basis(ctx, - MODBUS_FC_WRITE_MULTIPLE_COILS, - addr, nb, req); + req_length = ctx->backend->build_request_basis( + ctx, MODBUS_FC_WRITE_MULTIPLE_COILS, addr, nb, req); byte_count = (nb / 8) + ((nb % 8) ? 1 : 0); req[req_length++] = byte_count; @@ -1370,7 +1780,7 @@ int modbus_write_bits(modbus_t *ctx, int addr, int nb, const uint8_t *src) if (src[pos++]) req[req_length] |= bit; else - req[req_length] &=~ bit; + req[req_length] &= ~bit; bit = bit << 1; } @@ -1403,24 +1813,24 @@ int modbus_write_registers(modbus_t *ctx, int addr, int nb, const uint16_t *src) int byte_count; uint8_t req[MAX_MESSAGE_LENGTH]; - if (ctx == NULL) { + if (ctx == NULL || src == NULL) { errno = EINVAL; return -1; } - if (nb > MODBUS_MAX_WRITE_REGISTERS) { + if (nb < 1 || nb > MODBUS_MAX_WRITE_REGISTERS) { if (ctx->debug) { fprintf(stderr, "ERROR Trying to write to too many registers (%d > %d)\n", - nb, MODBUS_MAX_WRITE_REGISTERS); + nb, + MODBUS_MAX_WRITE_REGISTERS); } - errno = EMBMDATA; + errno = EMBXILVAL; return -1; } - req_length = ctx->backend->build_request_basis(ctx, - MODBUS_FC_WRITE_MULTIPLE_REGISTERS, - addr, nb, req); + req_length = ctx->backend->build_request_basis( + ctx, MODBUS_FC_WRITE_MULTIPLE_REGISTERS, addr, nb, req); byte_count = nb * 2; req[req_length++] = byte_count; @@ -1446,7 +1856,10 @@ int modbus_write_registers(modbus_t *ctx, int addr, int nb, const uint16_t *src) return rc; } -int modbus_mask_write_register(modbus_t *ctx, int addr, uint16_t and_mask, uint16_t or_mask) +int modbus_mask_write_register(modbus_t *ctx, + int addr, + uint16_t and_mask, + uint16_t or_mask) { int rc; int req_length; @@ -1455,9 +1868,13 @@ int modbus_mask_write_register(modbus_t *ctx, int addr, uint16_t and_mask, uint1 * (2 bytes) which is not used. */ uint8_t req[_MIN_REQ_LENGTH + 2]; - req_length = ctx->backend->build_request_basis(ctx, - MODBUS_FC_MASK_WRITE_REGISTER, - addr, 0, req); + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + req_length = ctx->backend->build_request_basis( + ctx, MODBUS_FC_MASK_WRITE_REGISTER, addr, 0, req); /* HACKISH, count is not used */ req_length -= 2; @@ -1488,9 +1905,11 @@ int modbus_mask_write_register(modbus_t *ctx, int addr, uint16_t and_mask, uint1 /* Write multiple registers from src array to remote device and read multiple registers from remote device to dest array. */ int modbus_write_and_read_registers(modbus_t *ctx, - int write_addr, int write_nb, + int write_addr, + int write_nb, const uint16_t *src, - int read_addr, int read_nb, + int read_addr, + int read_nb, uint16_t *dest) { @@ -1501,7 +1920,7 @@ int modbus_write_and_read_registers(modbus_t *ctx, uint8_t req[MAX_MESSAGE_LENGTH]; uint8_t rsp[MAX_MESSAGE_LENGTH]; - if (ctx == NULL) { + if (ctx == NULL || src == NULL || dest == NULL || write_nb < 1 || read_nb < 1) { errno = EINVAL; return -1; } @@ -1510,9 +1929,10 @@ int modbus_write_and_read_registers(modbus_t *ctx, if (ctx->debug) { fprintf(stderr, "ERROR Too many registers to write (%d > %d)\n", - write_nb, MODBUS_MAX_WR_WRITE_REGISTERS); + write_nb, + MODBUS_MAX_WR_WRITE_REGISTERS); } - errno = EMBMDATA; + errno = EMBXILVAL; return -1; } @@ -1520,14 +1940,14 @@ int modbus_write_and_read_registers(modbus_t *ctx, if (ctx->debug) { fprintf(stderr, "ERROR Too many registers requested (%d > %d)\n", - read_nb, MODBUS_MAX_WR_READ_REGISTERS); + read_nb, + MODBUS_MAX_WR_READ_REGISTERS); } - errno = EMBMDATA; + errno = EMBXILVAL; return -1; } - req_length = ctx->backend->build_request_basis(ctx, - MODBUS_FC_WRITE_AND_READ_REGISTERS, - read_addr, read_nb, req); + req_length = ctx->backend->build_request_basis( + ctx, MODBUS_FC_WRITE_AND_READ_REGISTERS, read_addr, read_nb, req); req[req_length++] = write_addr >> 8; req[req_length++] = write_addr & 0x00ff; @@ -1543,7 +1963,7 @@ int modbus_write_and_read_registers(modbus_t *ctx, rc = send_msg(ctx, req, req_length); if (rc > 0) { - int offset; + unsigned int offset; rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION); if (rc == -1) @@ -1556,8 +1976,7 @@ int modbus_write_and_read_registers(modbus_t *ctx, offset = ctx->backend->header_length; for (i = 0; i < rc; i++) { /* shift reg hi_byte to temp OR with lo_byte */ - dest[i] = (rsp[offset + 2 + (i << 1)] << 8) | - rsp[offset + 3 + (i << 1)]; + dest[i] = (rsp[offset + 2 + (i << 1)] << 8) | rsp[offset + 3 + (i << 1)]; } } @@ -1572,13 +1991,13 @@ int modbus_report_slave_id(modbus_t *ctx, int max_dest, uint8_t *dest) int req_length; uint8_t req[_MIN_REQ_LENGTH]; - if (ctx == NULL || max_dest <= 0) { + if (ctx == NULL || dest == NULL || max_dest <= 0) { errno = EINVAL; return -1; } - req_length = ctx->backend->build_request_basis(ctx, MODBUS_FC_REPORT_SLAVE_ID, - 0, 0, req); + req_length = + ctx->backend->build_request_basis(ctx, MODBUS_FC_REPORT_SLAVE_ID, 0, 0, req); /* HACKISH, addr and count are not used */ req_length -= 4; @@ -1586,7 +2005,7 @@ int modbus_report_slave_id(modbus_t *ctx, int max_dest, uint8_t *dest) rc = send_msg(ctx, req, req_length); if (rc > 0) { int i; - int offset; + unsigned int offset; uint8_t rsp[MAX_MESSAGE_LENGTH]; do @@ -1605,7 +2024,7 @@ int modbus_report_slave_id(modbus_t *ctx, int max_dest, uint8_t *dest) /* Byte count, slave id, run indicator status and additional data. Truncate copy to max_dest. */ - for (i=0; i < rc && i < max_dest; i++) { + for (i = 0; i < rc && i < max_dest; i++) { dest[i] = rsp[offset + i]; } } @@ -1621,6 +2040,7 @@ void _modbus_init_common(modbus_t *ctx) ctx->debug = FALSE; ctx->error_recovery = MODBUS_ERROR_RECOVERY_NONE; + ctx->quirks = MODBUS_QUIRK_NONE; ctx->response_timeout.tv_sec = 0; ctx->response_timeout.tv_usec = _RESPONSE_TIMEOUT; @@ -1653,8 +2073,7 @@ int modbus_get_slave(modbus_t *ctx) return ctx->slave; } -int modbus_set_error_recovery(modbus_t *ctx, - modbus_error_recovery_mode error_recovery) +int modbus_set_error_recovery(modbus_t *ctx, modbus_error_recovery_mode error_recovery) { if (ctx == NULL) { errno = EINVAL; @@ -1666,6 +2085,7 @@ int modbus_set_error_recovery(modbus_t *ctx, return 0; } +// FIXME Doesn't work under Windows RTU int modbus_set_socket(modbus_t *ctx, int s) { if (ctx == NULL) { @@ -1690,7 +2110,7 @@ int modbus_get_socket(modbus_t *ctx) /* Get the timeout interval used to wait for a response */ int modbus_get_response_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec) { - if (ctx == NULL) { + if (ctx == NULL || to_sec == NULL || to_usec == NULL) { errno = EINVAL; return -1; } @@ -1702,8 +2122,7 @@ int modbus_get_response_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_us int modbus_set_response_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec) { - if (ctx == NULL || - (to_sec == 0 && to_usec == 0) || to_usec > 999999) { + if (ctx == NULL || (to_sec == 0 && to_usec == 0) || to_usec > 999999) { errno = EINVAL; return -1; } @@ -1716,7 +2135,7 @@ int modbus_set_response_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec /* Get the timeout interval between two consecutive bytes of a message */ int modbus_get_byte_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec) { - if (ctx == NULL) { + if (ctx == NULL || to_sec == NULL || to_usec == NULL) { errno = EINVAL; return -1; } @@ -1739,10 +2158,11 @@ int modbus_set_byte_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec) return 0; } -/* Get the timeout interval used by the server to wait for an indication from a client */ +/* Get the timeout interval used by the server to wait for an indication from a client + */ int modbus_get_indication_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec) { - if (ctx == NULL) { + if (ctx == NULL || to_sec == NULL || to_usec == NULL) { errno = EINVAL; return -1; } @@ -1775,6 +2195,30 @@ int modbus_get_header_length(modbus_t *ctx) return ctx->backend->header_length; } +int modbus_enable_quirks(modbus_t *ctx, unsigned int quirks_mask) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + /* Enable quirks that have a true value at their index in the mask */ + ctx->quirks |= quirks_mask; + return 0; +} + +int modbus_disable_quirks(modbus_t *ctx, unsigned int quirks_mask) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + /* Disable quirks that have a true value at ther index in the mask */ + ctx->quirks &= ~quirks_mask; + return 0; +} + int modbus_connect(modbus_t *ctx) { if (ctx == NULL) { @@ -1818,15 +2262,27 @@ int modbus_set_debug(modbus_t *ctx, int flag) The modbus_mapping_new_start_address() function shall return the new allocated structure if successful. Otherwise it shall return NULL and set errno to ENOMEM. */ -modbus_mapping_t* modbus_mapping_new_start_address( - unsigned int start_bits, unsigned int nb_bits, - unsigned int start_input_bits, unsigned int nb_input_bits, - unsigned int start_registers, unsigned int nb_registers, - unsigned int start_input_registers, unsigned int nb_input_registers) +modbus_mapping_t *modbus_mapping_new_start_address(unsigned int start_bits, + unsigned int nb_bits, + unsigned int start_input_bits, + unsigned int nb_input_bits, + unsigned int start_registers, + unsigned int nb_registers, + unsigned int start_input_registers, + unsigned int nb_input_registers) { modbus_mapping_t *mb_mapping; - mb_mapping = (modbus_mapping_t *)malloc(sizeof(modbus_mapping_t)); + /* Reject dimensions larger than the addressable space to avoid excessively + large allocations driven by untrusted configuration. */ + if (nb_bits > MODBUS_MAX_TABLE_SIZE || nb_input_bits > MODBUS_MAX_TABLE_SIZE || + nb_registers > MODBUS_MAX_TABLE_SIZE || + nb_input_registers > MODBUS_MAX_TABLE_SIZE) { + errno = EINVAL; + return NULL; + } + + mb_mapping = (modbus_mapping_t *) malloc(sizeof(modbus_mapping_t)); if (mb_mapping == NULL) { return NULL; } @@ -1837,9 +2293,7 @@ modbus_mapping_t* modbus_mapping_new_start_address( if (nb_bits == 0) { mb_mapping->tab_bits = NULL; } else { - /* Negative number raises a POSIX error */ - mb_mapping->tab_bits = - (uint8_t *) malloc(nb_bits * sizeof(uint8_t)); + mb_mapping->tab_bits = (uint8_t *) malloc(nb_bits * sizeof(uint8_t)); if (mb_mapping->tab_bits == NULL) { free(mb_mapping); return NULL; @@ -1853,8 +2307,7 @@ modbus_mapping_t* modbus_mapping_new_start_address( if (nb_input_bits == 0) { mb_mapping->tab_input_bits = NULL; } else { - mb_mapping->tab_input_bits = - (uint8_t *) malloc(nb_input_bits * sizeof(uint8_t)); + mb_mapping->tab_input_bits = (uint8_t *) malloc(nb_input_bits * sizeof(uint8_t)); if (mb_mapping->tab_input_bits == NULL) { free(mb_mapping->tab_bits); free(mb_mapping); @@ -1869,8 +2322,7 @@ modbus_mapping_t* modbus_mapping_new_start_address( if (nb_registers == 0) { mb_mapping->tab_registers = NULL; } else { - mb_mapping->tab_registers = - (uint16_t *) malloc(nb_registers * sizeof(uint16_t)); + mb_mapping->tab_registers = (uint16_t *) malloc(nb_registers * sizeof(uint16_t)); if (mb_mapping->tab_registers == NULL) { free(mb_mapping->tab_input_bits); free(mb_mapping->tab_bits); @@ -1895,16 +2347,25 @@ modbus_mapping_t* modbus_mapping_new_start_address( free(mb_mapping); return NULL; } - memset(mb_mapping->tab_input_registers, 0, - nb_input_registers * sizeof(uint16_t)); + memset(mb_mapping->tab_input_registers, 0, nb_input_registers * sizeof(uint16_t)); } return mb_mapping; } -modbus_mapping_t* modbus_mapping_new(int nb_bits, int nb_input_bits, - int nb_registers, int nb_input_registers) +modbus_mapping_t *modbus_mapping_new(int nb_bits, + int nb_input_bits, + int nb_registers, + int nb_input_registers) { + /* Reject negative counts: they would otherwise be converted to very large + unsigned dimensions and drive huge allocations. */ + if (nb_bits < 0 || nb_input_bits < 0 || nb_registers < 0 || + nb_input_registers < 0) { + errno = EINVAL; + return NULL; + } + return modbus_mapping_new_start_address( 0, nb_bits, 0, nb_input_bits, 0, nb_registers, 0, nb_input_registers); } diff --git a/src/modbus.h b/src/modbus.h index a7d960b56..27242428f 100644 --- a/src/modbus.h +++ b/src/modbus.h @@ -1,5 +1,5 @@ /* - * Copyright © 2001-2013 Stéphane Raimbault + * Copyright © Stéphane Raimbault * * SPDX-License-Identifier: LGPL-2.1-or-later */ @@ -7,15 +7,16 @@ #ifndef MODBUS_H #define MODBUS_H +// clang-format off /* Add this for macros that defined unix flavor */ #if (defined(__unix__) || defined(unix)) && !defined(USG) -#include +# include #endif #ifndef _MSC_VER -#include +# include #else -#include "stdint.h" +# include "stdint.h" #endif #include "modbus-version.h" @@ -38,6 +39,7 @@ # define MODBUS_BEGIN_DECLS # define MODBUS_END_DECLS #endif +// clang-format on MODBUS_BEGIN_DECLS @@ -91,15 +93,15 @@ MODBUS_BEGIN_DECLS #define MODBUS_OID_CODE_EXTENDED 0x03 #define MODBUS_OID_CODE_SPECIFIC 0x04 -#define MODBUS_BROADCAST_ADDRESS 0 +#define MODBUS_BROADCAST_ADDRESS 0 /* Modbus_Application_Protocol_V1_1b.pdf (chapter 6 section 1 page 12) * Quantity of Coils to read (2 bytes): 1 to 2000 (0x7D0) * (chapter 6 section 11 page 29) * Quantity of Coils to write (2 bytes): 1 to 1968 (0x7B0) */ -#define MODBUS_MAX_READ_BITS 2000 -#define MODBUS_MAX_WRITE_BITS 1968 +#define MODBUS_MAX_READ_BITS 2000 +#define MODBUS_MAX_WRITE_BITS 1968 /* Modbus_Application_Protocol_V1_1b.pdf (chapter 6 section 3 page 15) * Quantity of Registers to read (2 bytes): 1 to 125 (0x7D) @@ -108,17 +110,17 @@ MODBUS_BEGIN_DECLS * (chapter 6 section 17 page 38) * Quantity of Registers to write in R/W registers (2 bytes) 1 to 121 (0x79) */ -#define MODBUS_MAX_READ_REGISTERS 125 -#define MODBUS_MAX_WRITE_REGISTERS 123 -#define MODBUS_MAX_WR_WRITE_REGISTERS 121 -#define MODBUS_MAX_WR_READ_REGISTERS 125 +#define MODBUS_MAX_READ_REGISTERS 125 +#define MODBUS_MAX_WRITE_REGISTERS 123 +#define MODBUS_MAX_WR_WRITE_REGISTERS 121 +#define MODBUS_MAX_WR_READ_REGISTERS 125 /* The size of the MODBUS PDU is limited by the size constraint inherited from * the first MODBUS implementation on Serial Line network (max. RS485 ADU = 256 * bytes). Therefore, MODBUS PDU for serial line communication = 256 - Server * address (1 byte) - CRC (2 bytes) = 253 bytes. */ -#define MODBUS_MAX_PDU_LENGTH 253 +#define MODBUS_MAX_PDU_LENGTH 253 /* Consequently: * - RTU MODBUS ADU = 253 bytes + Server address (1 byte) + CRC (2 bytes) = 256 @@ -128,7 +130,7 @@ MODBUS_BEGIN_DECLS * an array of bytes to store responses and it will be compatible with the two * backends. */ -#define MODBUS_MAX_ADU_LENGTH 260 +#define MODBUS_MAX_ADU_LENGTH 260 /* Random number to avoid errno conflicts */ #define MODBUS_ENOBASE 112345678 @@ -161,11 +163,11 @@ enum { #define EMBXGTAR (MODBUS_ENOBASE + MODBUS_EXCEPTION_GATEWAY_TARGET) /* Native libmodbus error codes */ -#define EMBBADCRC (EMBXGTAR + 1) -#define EMBBADDATA (EMBXGTAR + 2) -#define EMBBADEXC (EMBXGTAR + 3) -#define EMBUNKEXC (EMBXGTAR + 4) -#define EMBMDATA (EMBXGTAR + 5) +#define EMBBADCRC (EMBXGTAR + 1) +#define EMBBADDATA (EMBXGTAR + 2) +#define EMBBADEXC (EMBXGTAR + 3) +#define EMBUNKEXC (EMBXGTAR + 4) +#define EMBMDATA (EMBXGTAR + 5) #define EMBBADSLAVE (EMBXGTAR + 6) extern const unsigned int libmodbus_version_major; @@ -174,6 +176,10 @@ extern const unsigned int libmodbus_version_micro; typedef struct _modbus modbus_t; +/*! Memory layout in tab_xxx arrays is processor-endianness. + When receiving modbus data, it is converted to processor-endianness, + see read_registers(). +*/ typedef struct _modbus_mapping_t { int nb_bits; int start_bits; @@ -189,27 +195,39 @@ typedef struct _modbus_mapping_t { uint16_t *tab_registers; } modbus_mapping_t; -typedef enum -{ - MODBUS_ERROR_RECOVERY_NONE = 0, - MODBUS_ERROR_RECOVERY_LINK = (1<<1), - MODBUS_ERROR_RECOVERY_PROTOCOL = (1<<2) +typedef enum { + MODBUS_ERROR_RECOVERY_NONE = 0, + MODBUS_ERROR_RECOVERY_LINK = (1 << 1), + MODBUS_ERROR_RECOVERY_PROTOCOL = (1 << 2) } modbus_error_recovery_mode; -MODBUS_API int modbus_set_slave(modbus_t* ctx, int slave); -MODBUS_API int modbus_get_slave(modbus_t* ctx); -MODBUS_API int modbus_set_error_recovery(modbus_t *ctx, modbus_error_recovery_mode error_recovery); +typedef enum { + MODBUS_QUIRK_NONE = 0, + MODBUS_QUIRK_MAX_SLAVE = (1 << 1), + MODBUS_QUIRK_REPLY_TO_BROADCAST = (1 << 2), + MODBUS_QUIRK_ALL = 0xFF +} modbus_quirks; + +MODBUS_API int modbus_set_slave(modbus_t *ctx, int slave); +MODBUS_API int modbus_get_slave(modbus_t *ctx); +MODBUS_API int modbus_set_error_recovery(modbus_t *ctx, + modbus_error_recovery_mode error_recovery); MODBUS_API int modbus_set_socket(modbus_t *ctx, int s); MODBUS_API int modbus_get_socket(modbus_t *ctx); -MODBUS_API int modbus_get_response_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec); -MODBUS_API int modbus_set_response_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec); +MODBUS_API int +modbus_get_response_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec); +MODBUS_API int +modbus_set_response_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec); -MODBUS_API int modbus_get_byte_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec); +MODBUS_API int +modbus_get_byte_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec); MODBUS_API int modbus_set_byte_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec); -MODBUS_API int modbus_get_indication_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec); -MODBUS_API int modbus_set_indication_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec); +MODBUS_API int +modbus_get_indication_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec); +MODBUS_API int +modbus_set_indication_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec); MODBUS_API int modbus_get_header_length(modbus_t *ctx); @@ -226,73 +244,112 @@ MODBUS_API const char *modbus_strerror(int errnum); MODBUS_API int modbus_read_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest); MODBUS_API int modbus_read_input_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest); MODBUS_API int modbus_read_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest); -MODBUS_API int modbus_read_input_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest); +MODBUS_API int +modbus_read_input_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest); MODBUS_API int modbus_write_bit(modbus_t *ctx, int coil_addr, int status); MODBUS_API int modbus_write_register(modbus_t *ctx, int reg_addr, const uint16_t value); MODBUS_API int modbus_write_bits(modbus_t *ctx, int addr, int nb, const uint8_t *data); -MODBUS_API int modbus_write_registers(modbus_t *ctx, int addr, int nb, const uint16_t *data); -MODBUS_API int modbus_mask_write_register(modbus_t *ctx, int addr, uint16_t and_mask, uint16_t or_mask); -MODBUS_API int modbus_write_and_read_registers(modbus_t *ctx, int write_addr, int write_nb, - const uint16_t *src, int read_addr, int read_nb, +MODBUS_API int +modbus_write_registers(modbus_t *ctx, int addr, int nb, const uint16_t *data); +MODBUS_API int +modbus_mask_write_register(modbus_t *ctx, int addr, uint16_t and_mask, uint16_t or_mask); +MODBUS_API int modbus_write_and_read_registers(modbus_t *ctx, + int write_addr, + int write_nb, + const uint16_t *src, + int read_addr, + int read_nb, uint16_t *dest); MODBUS_API int modbus_report_slave_id(modbus_t *ctx, int max_dest, uint8_t *dest); -MODBUS_API modbus_mapping_t* modbus_mapping_new_start_address( - unsigned int start_bits, unsigned int nb_bits, - unsigned int start_input_bits, unsigned int nb_input_bits, - unsigned int start_registers, unsigned int nb_registers, - unsigned int start_input_registers, unsigned int nb_input_registers); - -MODBUS_API modbus_mapping_t* modbus_mapping_new(int nb_bits, int nb_input_bits, - int nb_registers, int nb_input_registers); +MODBUS_API modbus_mapping_t * +modbus_mapping_new_start_address(unsigned int start_bits, + unsigned int nb_bits, + unsigned int start_input_bits, + unsigned int nb_input_bits, + unsigned int start_registers, + unsigned int nb_registers, + unsigned int start_input_registers, + unsigned int nb_input_registers); + +MODBUS_API modbus_mapping_t *modbus_mapping_new(int nb_bits, + int nb_input_bits, + int nb_registers, + int nb_input_registers); MODBUS_API void modbus_mapping_free(modbus_mapping_t *mb_mapping); -MODBUS_API int modbus_send_raw_request(modbus_t *ctx, const uint8_t *raw_req, int raw_req_length); +MODBUS_API int +modbus_send_raw_request(modbus_t *ctx, const uint8_t *raw_req, int raw_req_length); + +MODBUS_API int modbus_send_raw_request_tid(modbus_t *ctx, + const uint8_t *raw_req, + int raw_req_length, + int tid); MODBUS_API int modbus_receive(modbus_t *ctx, uint8_t *req); MODBUS_API int modbus_receive_confirmation(modbus_t *ctx, uint8_t *rsp); -MODBUS_API int modbus_reply(modbus_t *ctx, const uint8_t *req, - int req_length, modbus_mapping_t *mb_mapping); -MODBUS_API int modbus_reply_exception(modbus_t *ctx, const uint8_t *req, - unsigned int exception_code); +MODBUS_API int modbus_reply(modbus_t *ctx, + const uint8_t *req, + int req_length, + modbus_mapping_t *mb_mapping); +MODBUS_API int +modbus_reply_exception(modbus_t *ctx, const uint8_t *req, unsigned int exception_code); +MODBUS_API int modbus_proxy(modbus_t *frontend_ctx, + modbus_t *backend_ctx, + const uint8_t *req, + int req_length); + +MODBUS_API int modbus_enable_quirks(modbus_t *ctx, unsigned int quirks_mask); +MODBUS_API int modbus_disable_quirks(modbus_t *ctx, unsigned int quirks_mask); /** * UTILS FUNCTIONS **/ #define MODBUS_GET_HIGH_BYTE(data) (((data) >> 8) & 0xFF) -#define MODBUS_GET_LOW_BYTE(data) ((data) & 0xFF) -#define MODBUS_GET_INT64_FROM_INT16(tab_int16, index) \ - (((int64_t)tab_int16[(index) ] << 48) | \ - ((int64_t)tab_int16[(index) + 1] << 32) | \ - ((int64_t)tab_int16[(index) + 2] << 16) | \ - (int64_t)tab_int16[(index) + 3]) -#define MODBUS_GET_INT32_FROM_INT16(tab_int16, index) ((tab_int16[(index)] << 16) | tab_int16[(index) + 1]) -#define MODBUS_GET_INT16_FROM_INT8(tab_int8, index) ((tab_int8[(index)] << 8) | tab_int8[(index) + 1]) -#define MODBUS_SET_INT16_TO_INT8(tab_int8, index, value) \ - do { \ - tab_int8[(index)] = (value) >> 8; \ - tab_int8[(index) + 1] = (value) & 0xFF; \ +#define MODBUS_GET_LOW_BYTE(data) ((data) & 0xFF) +/* The shifts are done in unsigned width and the result converted to the signed + type: shifting a (possibly negative or overflowing) signed value is undefined + behavior. The produced value is unchanged on two's-complement platforms. */ +#define MODBUS_GET_INT64_FROM_INT16(tab_int16, index) \ + ((int64_t) (((uint64_t) (uint16_t) tab_int16[(index)] << 48) | \ + ((uint64_t) (uint16_t) tab_int16[(index) + 1] << 32) | \ + ((uint64_t) (uint16_t) tab_int16[(index) + 2] << 16) | \ + (uint64_t) (uint16_t) tab_int16[(index) + 3])) +#define MODBUS_GET_INT32_FROM_INT16(tab_int16, index) \ + ((int32_t) (((uint32_t) (uint16_t) tab_int16[(index)] << 16) | \ + (uint32_t) (uint16_t) tab_int16[(index) + 1])) +#define MODBUS_GET_INT16_FROM_INT8(tab_int8, index) \ + ((int16_t) (((uint16_t) (uint8_t) tab_int8[(index)] << 8) | \ + (uint16_t) (uint8_t) tab_int8[(index) + 1])) +#define MODBUS_SET_INT16_TO_INT8(tab_int8, index, value) \ + do { \ + ((int8_t *) (tab_int8))[(index)] = (int8_t) ((value) >> 8); \ + ((int8_t *) (tab_int8))[(index) + 1] = (int8_t) (value); \ } while (0) -#define MODBUS_SET_INT32_TO_INT16(tab_int16, index, value) \ - do { \ - tab_int16[(index) ] = (value) >> 16; \ - tab_int16[(index) + 1] = (value); \ +#define MODBUS_SET_INT32_TO_INT16(tab_int16, index, value) \ + do { \ + ((int16_t *) (tab_int16))[(index)] = (int16_t) ((value) >> 16); \ + ((int16_t *) (tab_int16))[(index) + 1] = (int16_t) (value); \ } while (0) -#define MODBUS_SET_INT64_TO_INT16(tab_int16, index, value) \ - do { \ - tab_int16[(index) ] = (value) >> 48; \ - tab_int16[(index) + 1] = (value) >> 32; \ - tab_int16[(index) + 2] = (value) >> 16; \ - tab_int16[(index) + 3] = (value); \ +#define MODBUS_SET_INT64_TO_INT16(tab_int16, index, value) \ + do { \ + ((int16_t *) (tab_int16))[(index)] = (int16_t) ((value) >> 48); \ + ((int16_t *) (tab_int16))[(index) + 1] = (int16_t) ((value) >> 32); \ + ((int16_t *) (tab_int16))[(index) + 2] = (int16_t) ((value) >> 16); \ + ((int16_t *) (tab_int16))[(index) + 3] = (int16_t) (value); \ } while (0) MODBUS_API void modbus_set_bits_from_byte(uint8_t *dest, int idx, const uint8_t value); -MODBUS_API void modbus_set_bits_from_bytes(uint8_t *dest, int idx, unsigned int nb_bits, - const uint8_t *tab_byte); -MODBUS_API uint8_t modbus_get_byte_from_bits(const uint8_t *src, int idx, unsigned int nb_bits); +MODBUS_API void modbus_set_bits_from_bytes(uint8_t *dest, + int idx, + unsigned int nb_bits, + const uint8_t *tab_byte); +MODBUS_API uint8_t modbus_get_byte_from_bits(const uint8_t *src, + int idx, + unsigned int nb_bits); MODBUS_API float modbus_get_float(const uint16_t *src); MODBUS_API float modbus_get_float_abcd(const uint16_t *src); MODBUS_API float modbus_get_float_dcba(const uint16_t *src); @@ -305,9 +362,9 @@ MODBUS_API void modbus_set_float_dcba(float f, uint16_t *dest); 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-rtu.h" +#include "modbus-tcp.h" MODBUS_END_DECLS -#endif /* MODBUS_H */ +#endif /* MODBUS_H */ diff --git a/src/win32/README.md b/src/win32/README.md new file mode 100644 index 000000000..6b3957df0 --- /dev/null +++ b/src/win32/README.md @@ -0,0 +1,41 @@ +# Instructions to compile on Windows + +## Create a new Visual Studio project with the library included + +Download the latest version of libmodbus source code from Github +`https://github.com/stephane/libmodbus` -> Code -> Download ZIP. + +Once the archive is decompressed, launch a Windows terminal (`cmd`) in +`src/win32` directory and run `cscript configure.js`. + +1. copy the file `config.h` from `src/win32` to `src`. +2. create a new 'Console App' project under Visual Studio. +3. create a new directory called `libmodbus` inside your VS project (same level + as the `.vcxproj` file). +4. copy all `*.c` and `*.h` from libmodbus `src` in the new `libmodbus` folder + of your VS project. +5. copy `modbus.rc` in your VS project (same level as the `.vcxproj` file). +6. drag and drop `libmodbus/*.c` files (4) in *Solution Explorer -> Source Files*. +7. drag and drop `libmodbus/*.h` files (8) in *Solution Explorer -> Header Files*. +8. drag and drop `modbus.rc` file in *Solution Explorer -> Resource Files*. +9. check path is `#include "modbus-version.h"` in `modbus.rc`. +10. in the **Property Pages** of the project *Configuration Properties -> C/C++ + -> General -> Additional Include Directories*, add `libmodbus` folder. +11. in the **Property Pages** of the project *Configuration Properties -> + Resources -> Additional Include Directories*, add `libmodbus` folder. +12. in the **Property Pages** of the project *Configuration Properties -> Linker + -> Input*, define `ws2_32.lib`. +13. if required, add `_CRT_SECURE_NO_WARNINGS` to *C/C++ -> Preprocessor -> + Preprocessor Definitions*. + +## Create a libmodbus DLL + +This directory contains the project file for Visual Studio to build `modbus.dll` +and import library `modbus.lib`. + +In the Windows terminal, run `cscript configure.js` to generate: + +- `config.h` +- `modbus-version.h` are generated using configure.js. + +To write... diff --git a/src/win32/README.win32 b/src/win32/README.win32 deleted file mode 100644 index 86dbe2e08..000000000 --- a/src/win32/README.win32 +++ /dev/null @@ -1,20 +0,0 @@ -Intro ------ - -This directory contains the project file for Visual Studio 2008 to build -modbus.dll and the import library modbus.lib. - -The project file looks for D:/include/msvc_std to find stdint.h. -See ../../README.md file. - -config.h and ../modbus-version.h are generated using configure.js. - -Run - cscript configure.js -or - wscript configure.js -or - double click configure.js to generate these files. - -To get project file for Visual Studio 2005 open copy of file modbus.vcproj in -editor and change attribute `Version` of `VisualStudioProject` tag to "8,00". \ No newline at end of file diff --git a/src/win32/config.h.win32 b/src/win32/config.h.win32 index 02ac4448b..be0152c2d 100644 --- a/src/win32/config.h.win32 +++ b/src/win32/config.h.win32 @@ -30,9 +30,6 @@ /* Define to 1 if you have the `gettimeofday' function. */ /* #undef HAVE_GETTIMEOFDAY */ -/* Define to 1 if you have the `inet_ntoa' function. */ -/* #undef HAVE_INET_NTOA */ - /* Define to 1 if you have the header file. */ #define HAVE_INTTYPES_H 1 @@ -121,7 +118,7 @@ /* #undef HAVE_WORKING_VFORK */ /* Define to the sub-directory in which libtool stores uninstalled libraries. - */ + */ /* #undef LT_OBJDIR */ /* Name of package */ diff --git a/src/win32/configure.js b/src/win32/configure.js index aa21e49f9..5a579b9e5 100644 --- a/src/win32/configure.js +++ b/src/win32/configure.js @@ -29,135 +29,148 @@ var newFile; /* Displays the details about how to use this script. */ function usage() { - var txt; + var txt; - txt = "Usage:\n"; - txt += " cscript " + WScript.ScriptName + " \n"; - txt += " cscript " + WScript.ScriptName + " help\n\n"; - txt += "Options can be specified in the form