Initial commit.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/COPYING Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/COPYING.LESSER Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Makefile Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,44 @@
+
+default: all
+
+all: libmb.a libmb.so
+
+OBJ_FILES = mb_ascii.o mb_rtu.o mb_tcp.o mb_master.o mb_slave.o mb_slave_and_master.o sin_util.o
+
+libmb.a: $(OBJ_FILES)
+ ar cr libmb.a $(OBJ_FILES)
+
+libmb.so: $(OBJ_FILES)
+ gcc -shared -fPIC -o libmb.so $(OBJ_FILES)
+
+clean:
+ -rm -rf *.o libmb.a libmb.so
+
+
+
+# use gcc
+CC = gcc
+
+#get warnings, debugging information and optimization
+CFLAGS = -Wall -Wpointer-arith -Wstrict-prototypes -Wwrite-strings
+# CFLAGS += -Werror
+CFLAGS += -ggdb -O3 -funroll-loops
+# Note: if the optimizer crashes, we'll leave out the -O3 for those files
+
+# Required for compilation with beremiz, and to create shared object library
+CFLAGS += -fPIC
+
+
+
+#how to make things from other directories if they are missing
+../% /%:
+ $(MAKE) -C $(@D) $(@F)
+
+Makefile.depend depend:
+# gcc -MM -MG -I$(LLIB) *.c \
+ gcc -MM -MG *.c \
+ | perl -pe 's/:/ Makefile.depend:/' \
+ > Makefile.depend
+
+include Makefile.depend
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/README Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2001-2003,2016 Mario de Sousa (msousa@fe.up.pt)
+ *
+ * This file is part of the Modbus library for Beremiz and matiec.
+ *
+ * This Modbus library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this Modbus library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is made available on the understanding that it will not be
+ * used in safety-critical situations without a full and competent review.
+ */
+
+
+
+ Modbus Protocol Libraries
+ =========================
+
+ This directory contains the libararies that implement the modbus protocol
+ stack.
+
+ This protocol has been implemented as a two layer stack. Layer two includes
+ the mb_master and the mb_slave protocols. Layer one is composed of
+ the mb_rtu, mb_ascii and mb_tcp protocols.
+
+ Layer1 protocols all implement the same interface, defined in mb_layer1.h
+ Layer2 protocols implement different interfaces, defined in mb_master.h
+ and mb_slave.h
+
+ Which layer1 protocol that will be used by the program will depend on which
+ layer1 protocol implementation is linked to the final binary/executable.
+ It is not possible to define during run-time which layer1 protocol is to
+ be used. Each compiled program can only support a single layer1 protocol.
+
+ Users of these libraries should only use functions defined in the layer2
+ protocol header files (i.e. mb_master.h and mb_slave.h)
+
+ If writing a program that will simultaneously be a master and a slave,
+ then only use the mb_slave_and_master.h header file!
+ In this case, do not forget to link the final binary to both the
+ master and slave protocol implementations (as well as the chosen
+ layer1 protocol implementation).
+
+
+
+ ------------------------------------------
+ | | |
+ layer 2 | mb_master.h | mb_slave.h |
+ | mb_master.c | mb_slave.c |
+ | | |
+ |----------------------------------------|
+ | mb_layer1.h |
+ Layer 1 | | | |
+ | mb_rtu.c | mb_ascii.c | mb_tcp.c |
+ | | | |
+ ------------------------------------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mb_addr.h Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2002,2016 Mario de Sousa (msousa@fe.up.pt)
+ *
+ * This file is part of the Modbus library for Beremiz and matiec.
+ *
+ * This Modbus library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this Modbus library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is made available on the understanding that it will not be
+ * used in safety-critical situations without a full and competent review.
+ */
+
+
+#ifndef MODBUS_LAYER2_H
+#define MODBUS_LAYER2_H
+
+#include <time.h> /* struct timespec data type */
+
+//#include <sys/socket.h>
+//#include <netinet/in.h>
+#include <netinet/ip.h> /* superset of previous */ // Required for INADDR_ANY
+
+
+/* Library Error codes */
+#define PORT_FAILURE -101
+#define INTERNAL_ERROR -102
+#define TIMEOUT -103
+#define INVALID_FRAME -104
+#define MODBUS_ERROR -105
+
+/* NOTE: Modbus error codes are defined in mb_util.h */
+
+
+
+
+typedef enum {optimize_speed, optimize_size} optimization_t;
+
+
+typedef enum {
+ naf_ascii,
+ naf_rtu,
+ naf_tcp,
+ } node_addr_family_t;
+
+typedef struct {
+ const char *host;
+ const char *service;
+ int close_on_silence;
+ } node_addr_tcp_t;
+
+typedef struct {
+ const char *device;
+ int baud; /* plain baud rate, eg 2400; zero for the default 9600 */
+ int parity; /* 0 for none, 1 for odd, 2 for even */
+ int data_bits;
+ int stop_bits;
+ int ignore_echo; /* 1 => ignore echo; 0 => do not ignore echo */
+ } node_addr_rtu_t;
+
+typedef node_addr_rtu_t node_addr_ascii_t;
+
+typedef union {
+ node_addr_ascii_t ascii;
+ node_addr_rtu_t rtu;
+ node_addr_tcp_t tcp;
+ } node_addr_common_t;
+
+typedef struct {
+ node_addr_family_t naf;
+ node_addr_common_t addr;
+ } node_addr_t;
+
+#endif /* MODBUS_LAYER2_H */
+
+
+
+
+
+
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mb_ascii.c Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,1784 @@
+/*
+ * Copyright (c) 2002,2016 Mario de Sousa (msousa@fe.up.pt)
+ *
+ * This file is part of the Modbus library for Beremiz and matiec.
+ *
+ * This Modbus library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this Modbus library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is made available on the understanding that it will not be
+ * used in safety-critical situations without a full and competent review.
+ */
+
+
+
+#include <fcntl.h> /* File control definitions */
+#include <stdio.h> /* Standard input/output */
+#include <string.h>
+#include <stdlib.h>
+#include <termio.h> /* POSIX terminal control definitions */
+#include <sys/time.h> /* Time structures for select() */
+#include <unistd.h> /* POSIX Symbolic Constants */
+#include <assert.h>
+#include <errno.h> /* Error definitions */
+#include <ctype.h>
+#include <time.h> /* clock_gettime() */
+#include <limits.h> /* required for INT_MAX */
+
+#include "mb_layer1.h"
+#include "mb_ascii_private.h"
+
+
+/* #define DEBUG */ /* uncomment to see the data sent and received */
+
+
+
+
+/************************************/
+/** **/
+/** Include common code... **/
+/** **/
+/************************************/
+
+#include "mb_ds_util.h" /* data structures... */
+#include "mb_time_util.h" /* time conversion routines... */
+
+
+/**************************************************************/
+/**************************************************************/
+/**** ****/
+/**** ****/
+/**** Purpose and Formats ****/
+/**** ****/
+/**** ****/
+/**************************************************************/
+/**************************************************************/
+/*
+
+ This file implements the ascii formating of the modbus protocol.
+ Many values, protocol related, are hardcoded into the code, as it
+ seems very unlikely this code will ever get re-used for anything
+ else but this specific protocol.
+
+ Modbus ASCII frames have no timing restrictions whatsoever, and
+ abide by the following format:
+
+ Header
+ ------
+ size : 1 byte
+ value: ':' (i.e. '\0x3A')
+
+ Body
+ ----
+ size : variable, multiple of 2
+ value: binary data converted to ascii format,
+ i.e. each binary data byte is converted into two ascii characters
+ representing the byte in hexadecimal. Allowable characters are
+ '0' to '9' and 'A' to 'D'
+
+ LRC
+ ---
+ size : 2 bytes
+ value: Longitudinal Redundancy Check of data, excluding any headers, tails,
+ etc...
+
+ Tail
+ ----
+ size : 2 bytes
+ value: 'CR' + 'LF' (i.e. '\0x0D' + '\0x0A')
+
+
+*/
+
+
+
+/**************************************************************/
+/**************************************************************/
+/**** ****/
+/**** ****/
+/**** Forward Declarations ****/
+/**** and Defaults ****/
+/**** ****/
+/**************************************************************/
+/**************************************************************/
+
+
+typedef enum {fp_header, fp_body, fp_lrc, fp_tail, fp_done} frame_part_t;
+
+
+
+
+/**************************************************************/
+/**************************************************************/
+/**** ****/
+/**** ****/
+/**** Local Utility functions... ****/
+/**** ****/
+/**** ****/
+/**************************************************************/
+/**************************************************************/
+
+
+/*****************************************/
+/** **/
+/** lrc functions **/
+/** **/
+/*****************************************/
+
+
+static inline void lrc_init(u8 *lrc) {
+ *lrc = 0;
+}
+
+static inline void lrc_add_single(u8 *lrc, u8 data) {
+ *lrc += data;
+}
+
+static inline void lrc_add_many(u8 *lrc, u8 *data, int count) {
+ for (; count > 0; count--, *lrc += *data++);
+}
+
+static inline void lrc_end(u8 *lrc) {
+ *lrc = 1 + ~(*lrc);
+}
+
+
+
+/**************************************/
+/** **/
+/** Initialise a struct termios **/
+/** **/
+/**************************************/
+static int termios_init(struct termios *tios,
+ int baud,
+ int parity,
+ int data_bits,
+ int stop_bits) {
+ speed_t baud_rate;
+
+ if (tios == NULL)
+ return -1;
+
+ /* reset all the values... */
+ /* NOTE: the following are initialised later on...
+ tios->c_iflag = 0;
+ tios->c_oflag = 0;
+ tios->c_cflag = 0;
+ tios->c_lflag = 0;
+ */
+ tios->c_line = 0;
+
+ /* The minimum number of characters that should be received
+ * to satisfy a call to read().
+ */
+ tios->c_cc[VMIN ] = 0;
+
+ /* The maximum inter-arrival interval between two characters,
+ * in deciseconds.
+ *
+ * NOTE: we could use this to detect the end of RTU frames,
+ * but we prefer to use select() that has higher resolution,
+ * even though this higher resolution is most probably not
+ * supported, and the effective resolution is 10ms,
+ * one tenth of a decisecond.
+ */
+ tios->c_cc[VTIME] = 0;
+
+ /* configure the input modes... */
+ tios->c_iflag = IGNBRK | /* ignore BREAK condition on input */
+ IGNPAR | /* ignore framing errors and parity errors */
+ IXANY; /* enable any character to restart output */
+ /* BRKINT Only active if IGNBRK is not set.
+ * generate SIGINT on BREAK condition,
+ * otherwise read BREAK as character \0.
+ * PARMRK Only active if IGNPAR is not set.
+ * replace bytes with parity errors with
+ * \377 \0, instead of \0.
+ * INPCK enable input parity checking
+ * ISTRIP strip off eighth bit
+ * IGNCR ignore carriage return on input
+ * INLCR only active if IGNCR is not set.
+ * translate newline to carriage return on input
+ * ICRNL only active if IGNCR is not set.
+ * translate carriage return to newline on input
+ * IUCLC map uppercase characters to lowercase on input
+ * IXON enable XON/XOFF flow control on output
+ * IXOFF enable XON/XOFF flow control on input
+ * IMAXBEL ring bell when input queue is full
+ */
+
+ /* configure the output modes... */
+ tios->c_oflag = OPOST; /* enable implementation-defined output processing */
+ /* ONOCR don't output CR at column 0
+ * OLCUC map lowercase characters to uppercase on output
+ * ONLCR map NL to CR-NL on output
+ * OCRNL map CR to NL on output
+ * OFILL send fill characters for a delay, rather than
+ * using a timed delay
+ * OFDEL fill character is ASCII DEL. If unset, fill
+ * character is ASCII NUL
+ * ONLRET don't output CR
+ * NLDLY NL delay mask. Values are NL0 and NL1.
+ * CRDLY CR delay mask. Values are CR0, CR1, CR2, or CR3.
+ * TABDLY horizontal tab delay mask. Values are TAB0, TAB1,
+ * TAB2, TAB3, or XTABS. A value of XTABS expands
+ * tabs to spaces (with tab stops every eight columns).
+ * BSDLY backspace delay mask. Values are BS0 or BS1.
+ * VTDLY vertical tab delay mask. Values are VT0 or VT1.
+ * FFDLY form feed delay mask. Values are FF0 or FF1.
+ */
+
+ /* configure the control modes... */
+ tios->c_cflag = CREAD | /* enable receiver. */
+ CLOCAL; /* ignore modem control lines */
+ /* HUPCL lower modem control lines after last process
+ * closes the device (hang up).
+ * CRTSCTS flow control (Request/Clear To Send).
+ */
+ if (data_bits == 5) tios->c_cflag |= CS5;
+ else if (data_bits == 6) tios->c_cflag |= CS6;
+ else if (data_bits == 7) tios->c_cflag |= CS7;
+ else if (data_bits == 8) tios->c_cflag |= CS8;
+ else return -1;
+
+ if (stop_bits == 1) tios->c_cflag &=~ CSTOPB;
+ else if (stop_bits == 2) tios->c_cflag |= CSTOPB;
+ else return -1;
+
+ if(parity == 0) { /* none */
+ tios->c_cflag &=~ PARENB;
+ tios->c_cflag &=~ PARODD;
+ } else if(parity == 2) { /* even */
+ tios->c_cflag |= PARENB;
+ tios->c_cflag &=~ PARODD;
+ } else if(parity == 1) { /* odd */
+ tios->c_cflag |= PARENB;
+ tios->c_cflag |= PARODD;
+ } else return -1;
+
+
+ /* configure the local modes... */
+ tios->c_lflag = IEXTEN; /* enable implementation-defined input processing */
+ /* ISIG when any of the characters INTR, QUIT, SUSP, or DSUSP
+ * are received, generate the corresponding signal.
+ * ICANON enable canonical mode. This enables the special
+ * characters EOF, EOL, EOL2, ERASE, KILL, REPRINT,
+ * STATUS, and WERASE, and buffers by lines.
+ * ECHO echo input characters.
+ */
+
+ /* Set the baud rate */
+ /* Must be done before reseting all the values to 0! */
+ switch(baud) {
+ case 110: baud_rate = B110; break;
+ case 300: baud_rate = B300; break;
+ case 600: baud_rate = B600; break;
+ case 1200: baud_rate = B1200; break;
+ case 2400: baud_rate = B2400; break;
+ case 4800: baud_rate = B4800; break;
+ case 9600: baud_rate = B9600; break;
+ case 19200: baud_rate = B19200; break;
+ case 38400: baud_rate = B38400; break;
+ case 57600: baud_rate = B57600; break;
+ case 115200: baud_rate = B115200; break;
+ default: return -1;
+ } /* switch() */
+
+ if ((cfsetispeed(tios, baud_rate) < 0) ||
+ (cfsetospeed(tios, baud_rate) < 0))
+ return -1;;
+
+ return 0;
+}
+
+
+
+
+
+/*****************************************/
+/** **/
+/** u8/ascii conversion functions **/
+/** **/
+/*****************************************/
+
+/* Convert binary data to ascii format.
+ * Both xxx_data_lengths specify the available bytes in
+ * in the respective arrays.
+ */
+/* NOTE: this function is only called from a single location
+ * so we might just as well make it inline...
+ */
+static inline int bin_2_asc(u8 *bin_data, int bin_data_length,
+ u8 *asc_data, int asc_data_length) {
+ u8 nibble;
+ int count = 0;
+ u8 *last_bin_data = bin_data + bin_data_length;
+ /* we need L2_TO_ASC_CODING ascii bytes for each bin byte, therefore the
+ * '- (L2_TO_ASC_CODING - 1)'
+ */
+ u8 *last_asc_data = asc_data + asc_data_length - (L2_TO_ASC_CODING - 1);
+
+ while ((bin_data < last_bin_data) &&
+ (asc_data < last_asc_data)) {
+
+ nibble = (*bin_data & 0xF0) >> 4;
+ *(asc_data++) = (nibble <= 9)?nibble + '0':nibble - 10 + 'A';
+
+ nibble = (*bin_data & 0x0F);
+ *(asc_data++) = (nibble <= 9)?nibble + '0':nibble - 10 + 'A';
+
+ count++;
+ bin_data++;
+ }
+
+ /* return number of bytes converted... */
+ return count;
+}
+
+
+/* Convert from lowercase to upper case. */
+/* It probably does not make sense calling the generic toupper()
+ * whose functionality depends on the current locale.
+ * Our own specific function is most probably much faster...
+ */
+static inline u8 local_toupper(u8 val) {
+ if ((val >= 'a') && (val <= 'z'))
+ return val - 'a' + 'A';
+ return val;
+}
+
+/* Convert ascii data to bin format.
+ * *asc_data must be a two byte array.
+ *
+ * If a non-ascii character is found, returns -1
+ */
+/* NOTE: this function is only called from a single location
+ * so we might just as well make it inline...
+ */
+static inline int asc_2_bin(u8 *asc_data, u8 *bin_data) {
+ if ((isxdigit(asc_data[0]) == 0) ||
+ (isxdigit(asc_data[1]) == 0))
+ return -1;
+
+ asc_data[0] = local_toupper(asc_data[0]);
+ asc_data[1] = local_toupper(asc_data[1]);
+
+ /* hi */ *(bin_data) = ((asc_data[0] <= '9')?
+ (asc_data[0] - '0'):(asc_data[0] - 'A' + 10)) * 0x10;
+ /* lo */ *(bin_data) += (asc_data[1] <= '9')?
+ (asc_data[1] - '0'):(asc_data[1] - 'A' + 10);
+
+ return 0;
+}
+
+
+
+
+/************************************/
+/** **/
+/** A data structure - send buffer **/
+/** **/
+/************************************/
+
+/* data structure used to store the data to be sent in ascii format. */
+/* The binary data is converted into ascii format before transmission. The
+ * frame is not converted as a single whole, but is rather done in chunks.
+ * The size of the chunks depends on the data size of the send_buffer.
+ *
+ * A lrc variable keeps a tab on the current value of the lrc as the data
+ * is being converted.
+ *
+ * Three special functions add the header, lrc and tail to the ascii frame.
+ */
+
+/* NOTE: The algorithms in the insert functions require a minimum buffer
+ * size to work correctly...
+ */
+#define SEND_BUF_MIN_LENGTH ASC_FRAME_MIN_ELE_LENGTH
+
+typedef struct {
+ lb_buf_t data_buf;
+
+ u8 lrc; /* the current value of the lrc, in binary format */
+ } send_buf_t;
+
+/* A small auxiliary function... */
+static inline u8 *send_buf_init(send_buf_t *buf, int size, int max_data_start) {
+ /* The algorithms in other functions require a minimum size
+ * to work correctly...
+ */
+ if (size < SEND_BUF_MIN_LENGTH)
+ return NULL;
+
+ lrc_init(&buf->lrc);
+ return lb_init(&buf->data_buf, size, max_data_start);
+}
+
+/* A small auxiliary function... */
+static inline void send_buf_done(send_buf_t *buf) {
+ lb_done(&buf->data_buf);
+}
+
+/* A small auxiliary function... */
+static inline void send_buf_reset(send_buf_t *buf) {
+ lrc_init(&buf->lrc);
+ lb_data_purge_all(&buf->data_buf);
+}
+
+/* A small auxiliary function... */
+static inline int send_buf_data_count(send_buf_t *buf) {
+ return lb_data_count(&buf->data_buf);
+}
+
+/* A small auxiliary function... */
+static inline int send_buf_free_count(send_buf_t *buf) {
+ return lb_free_count(&buf->data_buf);
+}
+/* A small auxiliary function... */
+static inline u8 *send_buf_data(send_buf_t *buf) {
+ return lb_data(&buf->data_buf);
+}
+
+/* A small auxiliary function... */
+static inline u8 *send_buf_free(send_buf_t *buf) {
+ return lb_free(&buf->data_buf);
+}
+
+/* A small auxiliary function... */
+static inline int send_buf_data_add(send_buf_t *buf, u8 *data, int data_count) {
+ int res = bin_2_asc(data, data_count, send_buf_free(buf), send_buf_free_count(buf));
+ if (res <=0) return res;
+ lb_data_add(&buf->data_buf, L2_TO_ASC_CODING * res);
+ lrc_add_many(&buf->lrc, data, res);
+ return res;
+}
+
+/* A small auxiliary function... */
+static inline void send_buf_data_purge(send_buf_t *buf, int count) {
+ lb_data_purge(&buf->data_buf, count);
+}
+
+/* A small auxiliary function... */
+static inline void send_buf_data_purge_all(send_buf_t *buf) {
+ lb_data_purge_all(&buf->data_buf);
+}
+
+/* A small auxiliary function... */
+static inline int send_buf_lrc_append(send_buf_t *buf) {
+#if ASC_FRAME_LRC_LENGTH != 2
+#error Code assumes LRC length of 2 bytes, but ASC_FRAME_LRC_LENGTH != 2
+#endif
+ if (lb_free_count(&buf->data_buf) < ASC_FRAME_LRC_LENGTH)
+ return -1;
+ lrc_end(&buf->lrc);
+ bin_2_asc(&buf->lrc, sizeof(buf->lrc),
+ lb_free(&buf->data_buf), ASC_FRAME_LRC_LENGTH);
+ lb_data_add(&buf->data_buf, ASC_FRAME_LRC_LENGTH);
+ return 0;
+}
+
+static inline int send_buf_header_append(send_buf_t *buf) {
+#if ASC_FRAME_HEADER_LENGTH != 1
+#error Code assumes HEADER length of 1 bytes, but ASC_FRAME_HEADER_LENGTH != 1
+#endif
+ if (lb_free_count(&buf->data_buf) < ASC_FRAME_HEADER_LENGTH)
+ return -1;
+
+ /* add the ':' frame header */
+ *lb_free(&buf->data_buf) = ASC_FRAME_HEADER;
+ lb_data_add(&buf->data_buf, ASC_FRAME_HEADER_LENGTH);
+
+ return 0;
+}
+
+static inline int send_buf_tail_append(send_buf_t *buf) {
+#if ASC_FRAME_TAIL_LENGTH != 2
+#error Code assumes TAIL length of 2 bytes, but ASC_FRAME_TAIL_LENGTH != 2
+#endif
+ if (lb_free_count(&buf->data_buf) < ASC_FRAME_TAIL_LENGTH)
+ return -1;
+
+ /* add the CR+LF frame delimiter */
+ lb_free(&buf->data_buf)[0] = ASC_FRAME_TAIL_0;
+ lb_free(&buf->data_buf)[1] = ASC_FRAME_TAIL_1;
+ lb_data_add(&buf->data_buf, ASC_FRAME_TAIL_LENGTH);
+
+ return 0;
+}
+
+
+
+
+/************************************/
+/** **/
+/** A data structure - recv buffer **/
+/** **/
+/************************************/
+
+/* data structure used to store the data received from the bus. */
+
+/* The ascii data received from the bus is added to the buffer, and is
+ * dynamically converted to binary format. Once a valid frame has been
+ * converted, conversion stops until this valid frame is deleted/purged.
+ */
+
+/* NOTE: The algorithms in the insert functions require a minimum buffer
+ * size to work correctly...
+ */
+#define RECV_BUF_MIN_LENGTH ASC_FRAME_MIN_ELE_LENGTH
+
+#define RECV_BUF_BIN_BUF_SIZE (MAX_L2_FRAME_LENGTH + \
+ ASC_FRAME_LRC_LENGTH / L2_TO_ASC_CODING)
+
+typedef struct {
+ lb_buf_t asc_data_buf;
+ u8 bin_data_buf[RECV_BUF_BIN_BUF_SIZE];
+ int bin_data_free_ofs;
+
+ frame_part_t frame_part;
+ u8 lrc; /* the current value of the lrc, in binary format */
+ u8 lrc_1; /* the previous value of the lrc... */
+ /* NOTE: We do a running conversion between ascii and binary format,
+ * i.e. we start converting from ascii to binary before we
+ * have received the complete ascii frame. This means that
+ * we also do a running computation of our local version of
+ * the frame lrc.
+ * The lrc, transmitted at the end of the ascii frame,
+ * but before the frame tail, also gets converted to binary
+ * before we get a chance to realize that it is the lrc value,
+ * and should therefore not be taken into account when computing
+ * our local version of the lrc.
+ * So we keep the previous value of the running lrc, and use
+ * that to confirm whether we have a valid frame.
+ */
+ } recv_buf_t;
+
+/* A small auxiliary function... */
+static inline u8 *recv_buf_init(recv_buf_t *buf, int size, int max_data_start) {
+ /* The algorithms in other functions require a minimum size
+ * to work correctly...
+ */
+ if (size < RECV_BUF_MIN_LENGTH)
+ return NULL;
+
+ lrc_init(&buf->lrc);
+ buf->bin_data_free_ofs = 0;
+ buf->frame_part = fp_header;
+ return lb_init(&buf->asc_data_buf, size, max_data_start);
+}
+
+/* A small auxiliary function... */
+static inline void recv_buf_done(recv_buf_t *buf) {
+ lb_done(&buf->asc_data_buf);
+}
+
+/* A small auxiliary function... */
+static inline void recv_buf_reset(recv_buf_t *buf) {
+ lrc_init(&buf->lrc);
+ buf->bin_data_free_ofs = 0;
+ buf->frame_part = fp_header;
+ lb_data_purge_all(&buf->asc_data_buf);
+}
+
+/* A small auxiliary function... */
+static inline u8 *recv_buf_data(recv_buf_t *buf) {
+ return lb_data(&buf->asc_data_buf);
+}
+
+/* A small auxiliary function... */
+static inline u8 *recv_buf_free(recv_buf_t *buf) {
+ return lb_free(&buf->asc_data_buf);
+}
+
+/* The function that really does all the conversion work... */
+/* It finds frame limits, converts the data into binary format,
+ * and checks for correct lrc in the received frame.
+ */
+/* NOTE: called indirectly from various locations! Do NOT inline! */
+static void recv_buf_data_parse(recv_buf_t *buf) {
+ int count;
+ u8 *data;
+
+ data = lb_data(&buf->asc_data_buf);
+ count = lb_data_count(&buf->asc_data_buf);
+
+ /* NOTE: We need at least ASC_FRAME_MIN_ELE_LENGTH bytes to
+ * to be able to find that element of minimum length
+ */
+ while ((count >= ASC_FRAME_MIN_ELE_LENGTH) && (buf->frame_part != fp_done)) {
+ /* Check for frame header... */
+ /* The following few lines of code assume that ASC_FRAME_HEADER_LENGTH is 1! */
+#if ASC_FRAME_HEADER_LENGTH != 1
+#error The code is written in such a way that can only handle ASC_FRAME_HEADER_LENGTH == 1
+#endif
+ if (data[0] == ASC_FRAME_HEADER) {
+ /* found the beginning of a frame...
+ * Even if we were previously converting a frame without errors,
+ * if we receive a new frame header we discard the previous
+ * frame that went unfinished!
+ */
+ data += ASC_FRAME_HEADER_LENGTH;
+ count -= ASC_FRAME_HEADER_LENGTH;
+ buf->frame_part = fp_body;
+ lrc_init(&buf->lrc);
+ buf->bin_data_free_ofs = 0;
+ continue;
+ }
+
+ /* Check for frame tail... */
+ /*
+ * Note that the while() condition guarantees that we have at least
+ * two ascii bytes to handle.
+ */
+ /* The following few lines of code assume that ASC_FRAME_TAIL_LENGTH is 2! */
+#if ASC_FRAME_TAIL_LENGTH != 2
+#error The code is written in such a way that can only handle ASC_FRAME_TAIL_LENGTH == 2
+#endif
+ if ((data[0] == ASC_FRAME_TAIL_0) &&
+ (data[1] == ASC_FRAME_TAIL_1)) {
+ /* end of binary data... */
+ data += ASC_FRAME_TAIL_LENGTH;
+ count -= ASC_FRAME_TAIL_LENGTH;
+
+ /* let's check the lrc... */
+ if (buf->bin_data_free_ofs <= 0)
+ /* we have reached the end of a frame that did not include
+ * any binary data, not even the lrc value itself!
+ */
+ goto frame_error;
+
+ /* Remember that we do not use the most recent lrc value, as this
+ * incorrectly includes the ascii bytes in the frame that code the
+ * frame's lrc. (pls read the note in the recv_but_t typedef)
+ */
+ /* The following few lines of code assume that
+ * (ASC_FRAME_LRC_LENGTH / L2_TO_ASC_CODING) is 1!
+ */
+#if L2_TO_ASC_CODING != ASC_FRAME_LRC_LENGTH
+#error The code is written in such a way that can only handle L2_TO_ASC_CODING == ASC_FRAME_LRC_LENGTH
+#endif
+ lrc_end(&(buf->lrc_1));
+ if (buf->lrc_1 == buf->bin_data_buf[buf->bin_data_free_ofs-1]) {
+ /* we have received a correct frame... */
+ buf->frame_part = fp_done;
+ continue;
+ } else {
+ /* we have found a frame with an lrc error */
+ goto frame_error;
+ }
+ }
+
+ if (buf->frame_part == fp_header) {
+ /* we are searching for beginning of a frame...
+ * but we did not find it in the previous condition!
+ * We continue looking in the next ascii byte
+ */
+ data++;
+ count--;
+ continue;
+ }
+
+ if (buf->frame_part == fp_body) {
+ /* we have previously found the beginning of a frame,
+ * and are now converting the body into binary format...
+ *
+ * Note that the while() condition guarantees that we have at least
+ * two ascii bytes to convert into binary format.
+ */
+
+ /* this is normal data... let's convert... */
+ if (asc_2_bin(data, buf->bin_data_buf + buf->bin_data_free_ofs) < 0)
+ /* error converting from ascii to binary.
+ * This must be due to an invalid ascii character in the ascii data.
+ * We discard the current frame...
+ *
+ * Note that we *do not* increment the data pointer,
+ * nor do we decrement the count variable. One of the ascii bytes could
+ * be the begining of a new valid frame, and we don't want to skip it!
+ */
+ goto frame_error;
+
+ buf->lrc_1 = buf->lrc;
+ lrc_add_single(&(buf->lrc), *(buf->bin_data_buf + buf->bin_data_free_ofs));
+
+ data += L2_TO_ASC_CODING;
+ count -= L2_TO_ASC_CODING;
+ if (++(buf->bin_data_free_ofs) >= RECV_BUF_BIN_BUF_SIZE)
+ /* Whoops, this shouldn't be hapening!
+ * The frame we are receiving is larger than the alocated buffer!
+ * Our only alternative is to discard the frame
+ * we are currently receiving...
+ */
+ goto frame_error;
+
+ continue;
+ }
+
+ /* if none of the above, then it must be some transmission error */
+ /* Actually, the code will never fall through since if we are in this loop,
+ * (frame_part == header) || (frame_part == body) is always true!
+ */
+ data++;
+ count--;
+frame_error:
+ lrc_init(&buf->lrc);
+ buf->bin_data_free_ofs = 0;
+ buf->frame_part = fp_header;
+ } /* while () */
+
+ lb_data_purge(&buf->asc_data_buf, lb_data_count(&buf->asc_data_buf) - count);
+}
+
+/* A small auxiliary function... */
+static inline void recv_buf_search_frame(recv_buf_t *buf) {
+ if (buf->frame_part != fp_done)
+ recv_buf_data_parse(buf);
+}
+
+/* add ascii format data to buffer */
+static inline void recv_buf_data_add(recv_buf_t *buf, int count) {
+ lb_data_add(&buf->asc_data_buf, count);
+}
+
+/* A small auxiliary function... */
+static inline int recv_buf_data_count(recv_buf_t *buf) {
+ return lb_data_count(&buf->asc_data_buf);
+}
+
+/* A small auxiliary function... */
+static inline int recv_buf_free_count(recv_buf_t *buf) {
+ return lb_free_count(&buf->asc_data_buf);
+}
+
+/* Return pointer to frame, if a valid frame is available */
+static inline u8 *recv_buf_frame(recv_buf_t *buf) {
+ recv_buf_search_frame(buf);
+ if (buf->frame_part == fp_done)
+ /* we have found a frame...! */
+ return buf->bin_data_buf;
+
+ /* no frame... */
+ return NULL;
+}
+
+/* Return number of bytes in frame, if a valid frame is available */
+static inline int recv_buf_frame_count(recv_buf_t *buf) {
+ recv_buf_search_frame(buf);
+ if (buf->frame_part == fp_done)
+ /* we have found a frame...! */
+ return buf->bin_data_free_ofs - ASC_FRAME_LRC_LENGTH/L2_TO_ASC_CODING;
+
+ /* no frame... */
+ return -1;
+}
+
+/* Delete valid binary format frame! */
+static inline void recv_buf_frame_purge(recv_buf_t *buf) {
+ /* NOTE: we only delete valid frames!!
+ * Partially converted frames are not deleted, the
+ * remaining bytes may be received later!
+ */
+ if (buf->frame_part == fp_done) {
+ buf->frame_part = fp_header;
+ buf->bin_data_free_ofs = 0;
+ }
+}
+
+
+
+/************************************/
+/** **/
+/** A data structure - nd entry **/
+/** **/
+/************************************/
+
+/* NOTE: nd = node descriptor */
+
+typedef struct {
+ /* The file descriptor associated with this node */
+ /* NOTE: if the node is not yet in use, i.e. if the node is free,
+ * then fd will be set to -1
+ */
+ int fd;
+ struct timeval time_15_char_;
+
+ /* Modbus ascii frames are delimited by a ':' (colon) at the begining of
+ * a frame, and CR-LF sequence at the end the frame.
+ *
+ * Unless we want to take 'ages' reading the data off the serial line
+ * one byte at a time, we risk reading beyond the boundary of the
+ * frame currently being interpreted. The extra data belongs to the
+ * subsequent frame, and must therefore be buffered to be handled
+ * when the next frame is being interpreted.
+ *
+ * The receive buffer is therefore a static variable.
+ */
+ recv_buf_t recv_buf_;
+
+ /* The send ascii buffer could be a local function variable
+ * instead of a static variable, but we might just as well
+ * allocate the memory at startup and not risk running out
+ * of memory while the module is running.
+ */
+ send_buf_t send_buf_;
+
+ /* The old settings of the serial port, to be reset when the library is closed... */
+ struct termios old_tty_settings_;
+
+ /* ignore echo flag.
+ * If set to 1, then it means that we will be reading every byte we
+ * ourselves write out to the bus, so we must ignore those bytes read
+ * before we really read the data sent by remote nodes.
+ *
+ * This comes in useful when using a RS232-RS485 converter that does
+ * not correctly control the RTS-CTS lines...
+ */
+ int ignore_echo;
+ } nd_entry_t;
+
+
+static inline void nd_entry_init(nd_entry_t *nde) {
+ nde->fd = -1; /* The node is free... */
+}
+
+static int nd_entry_connect(nd_entry_t *nde,
+ node_addr_t *node_addr,
+ optimization_t opt) {
+
+ int parity_bits, start_bits, char_bits;
+ struct termios settings;
+ int buf_size;
+
+ /*
+ if (nde == NULL)
+ goto error_exit_0;
+ */
+ if (nde->fd >= 0)
+ goto error_exit_0;
+
+ /* initialise the termios data structure */
+ if (termios_init(&settings,
+ node_addr->addr.ascii.baud,
+ node_addr->addr.ascii.parity,
+ node_addr->addr.ascii.data_bits,
+ node_addr->addr.ascii.stop_bits)
+ < 0) {
+#ifdef DEBUG
+ fprintf(stderr, "Invalid serial line settings"
+ "(baud=%d, parity=%d, data_bits=%d, stop_bits=%d)",
+ node_addr->addr.ascii.baud,
+ node_addr->addr.ascii.parity,
+ node_addr->addr.ascii.data_bits,
+ node_addr->addr.ascii.stop_bits);
+#endif
+ goto error_exit_1;
+ }
+
+ /* set the ignore_echo flag */
+ nde->ignore_echo = node_addr->addr.ascii.ignore_echo;
+
+ /* initialise send buffer */
+ buf_size = (opt == optimize_size)?SEND_BUFFER_SIZE_SMALL:
+ SEND_BUFFER_SIZE_LARGE;
+ if (send_buf_init(&nde->send_buf_, buf_size, buf_size - SEND_BUF_MIN_LENGTH)
+ == NULL) {
+#ifdef DEBUG
+ fprintf(stderr, "Out of memory: error initializing send buffer");
+#endif
+ goto error_exit_1;
+ }
+
+ /* initialise recv buffer */
+ buf_size = (opt == optimize_size)?RECV_BUFFER_SIZE_SMALL:
+ RECV_BUFFER_SIZE_LARGE;
+ if (recv_buf_init(&nde->recv_buf_, buf_size, buf_size - RECV_BUF_MIN_LENGTH)
+ == NULL) {
+#ifdef DEBUG
+ fprintf(stderr, "Out of memory: error initializing receive buffer");
+#endif
+ goto error_exit_2;
+ }
+
+ /* open the serial port */
+ if((nde->fd = open(node_addr->addr.ascii.device, O_RDWR | O_NOCTTY | O_NDELAY))
+ < 0) {
+#ifdef DEBUG
+ fprintf(stderr, "Error opening device %s (errno=%d)",
+ node_addr->addr.ascii.device, errno);
+#endif
+ goto error_exit_3;
+ }
+
+ if(tcgetattr(nde->fd, &nde->old_tty_settings_) < 0) {
+#ifdef DEBUG
+ fprintf(stderr, "Error reading device's %s original settings.",
+ node_addr->addr.ascii.device);
+#endif
+ goto error_exit_4;
+ }
+
+ if(tcsetattr(nde->fd, TCSANOW, &settings) < 0) {
+#ifdef DEBUG
+ fprintf(stderr, "Error configuring device %s"
+ "(baud=%d, parity=%d, data_bits=%d, stop_bits=%d)",
+ node_addr->addr.ascii.device,
+ node_addr->addr.ascii.baud,
+ node_addr->addr.ascii.parity,
+ node_addr->addr.ascii.data_bits,
+ node_addr->addr.ascii.stop_bits);
+#endif
+ goto error_exit_4;
+ }
+
+ parity_bits = (node_addr->addr.ascii.parity == 0)?0:1;
+ start_bits = 1;
+ char_bits = start_bits + node_addr->addr.ascii.data_bits +
+ parity_bits + node_addr->addr.ascii.stop_bits;
+/* time_35_char_ = d_to_timeval(3.5*char_bits/baud); */
+ nde->time_15_char_ = d_to_timeval(1.5*char_bits/node_addr->addr.ascii.baud);
+
+#ifdef DEBUG
+ printf("nd_entry_connect(): %s open\n", node_addr->addr.ascii.device );
+ printf("nd_entry_connect(): returning fd=%d\n", nde->fd);
+#endif
+ return nde->fd;
+
+ error_exit_4:
+ close(nde->fd);
+ error_exit_3:
+ recv_buf_done(&nde->recv_buf_);
+ error_exit_2:
+ send_buf_done(&nde->send_buf_);
+ error_exit_1:
+ nde->fd = -1; /* set the node as free... */
+ error_exit_0:
+ return -1;
+}
+
+
+
+static int nd_entry_free(nd_entry_t *nde) {
+ if (nde->fd < 0)
+ /* already free */
+ return -1;
+
+ /* reset the tty device old settings... */
+ if(tcsetattr(nde->fd, TCSANOW, &nde->old_tty_settings_) < 0)
+ fprintf(stderr, "Error reconfiguring serial port to it's original settings.");
+
+ recv_buf_done(&nde->recv_buf_);
+ send_buf_done(&nde->send_buf_);
+ close(nde->fd);
+ nde->fd = -1;
+
+ return 0;
+}
+
+
+static inline int nd_entry_is_free(nd_entry_t *nde) {
+ return (nde->fd < 0);
+}
+
+
+
+
+/************************************/
+/** **/
+/** A data structure - nd table **/
+/** **/
+/************************************/
+
+typedef struct {
+ /* the array of node descriptors, and current size... */
+ nd_entry_t *node;
+ int node_count; /* total number of nodes in the node[] array */
+} nd_table_t;
+
+
+
+#if 1
+/* nd_table_init()
+ * Version 1 of the nd_table_init() function.
+ * If called more than once, 2nd and any subsequent calls will
+ * be interpreted as a request to confirm that it was already correctly
+ * initialized with the requested number of nodes.
+ */
+static int nd_table_init(nd_table_t *ndt, int nd_count) {
+ int count;
+
+ if (ndt->node != NULL) {
+ /* this function has already been called, and the node table is already initialised */
+ return (ndt->node_count == nd_count)?0:-1;
+ }
+
+ /* initialise the node descriptor metadata array... */
+ ndt->node = malloc(sizeof(nd_entry_t) * nd_count);
+ if (ndt->node == NULL) {
+#ifdef DEBUG
+ fprintf(stderr, "Out of memory: error initializing node address buffer");
+#endif
+ return -1;
+ }
+ ndt->node_count = nd_count;
+
+ /* initialise the state of each node in the array... */
+ for (count = 0; count < ndt->node_count; count++) {
+ nd_entry_init(&ndt->node[count]);
+ } /* for() */
+
+ return nd_count; /* number of succesfully created nodes! */
+}
+#else
+/* nd_table_init()
+ * Version 2 of the nd_table_init() function.
+ * If called more than once, 2nd and any subsequent calls will
+ * be interpreted as a request to reserve an extra new_nd_count
+ * number of nodes. This will be done using realloc().
+ */
+static int nd_table_init(nd_table_t *ndt, int new_nd_count) {
+ int count;
+
+ /* initialise the node descriptor metadata array... */
+ ndt->node = realloc(ndt->node, sizeof(nd_entry_t) * (ndt->node_count + new_nd_count));
+ if (ndt->node == NULL) {
+#ifdef ERRMSG
+ fprintf(stderr, ERRMSG_HEAD "Out of memory: error initializing node address buffer\n");
+#endif
+ return -1;
+ }
+
+ /* initialise the state of each newly added node in the array... */
+ for (count = ndt->node_count; count < ndt->node_count + new_nd_count; count++) {
+ nd_entry_init(&ndt->node[count]);
+ } /* for() */
+ ndt->node_count += new_nd_count;
+
+ return new_nd_count; /* number of succesfully created nodes! */
+}
+#endif
+
+
+static inline nd_entry_t *nd_table_get_nd(nd_table_t *ndt, int nd) {
+ if ((nd < 0) || (nd >= ndt->node_count))
+ return NULL;
+
+ return &ndt->node[nd];
+}
+
+
+static inline void nd_table_done(nd_table_t *ndt) {
+ int i;
+
+ if (ndt->node == NULL)
+ return;
+
+ /* close all the connections... */
+ for (i = 0; i < ndt->node_count; i++)
+ nd_entry_free(&ndt->node[i]);
+
+ /* Free memory... */
+ free(ndt->node);
+ *ndt = (nd_table_t){.node=NULL, .node_count=0};
+}
+
+
+
+static inline int nd_table_get_free_nd(nd_table_t *ndt) {
+ int count;
+
+ for (count = 0; count < ndt->node_count; count++) {
+ if (nd_entry_is_free(&ndt->node[count]))
+ return count;
+ }
+
+ /* none found... */
+ return -1;
+}
+
+
+static inline int nd_table_free_nd(nd_table_t *ndt, int nd) {
+ if ((nd < 0) || (nd >= ndt->node_count))
+ return -1;
+
+ return nd_entry_free(&ndt->node[nd]);
+}
+
+
+
+/**************************************************************/
+/**************************************************************/
+/**** ****/
+/**** ****/
+/**** Global Library State ****/
+/**** ****/
+/**** ****/
+/**************************************************************/
+/**************************************************************/
+
+
+ /* The node descriptor table... */
+ /* NOTE: This variable must be correctly initialised here!! */
+static nd_table_t nd_table_ = {.node=NULL, .node_count=0};
+
+ /* The optimization choice... */
+static optimization_t optimization_;
+
+
+
+/**************************************************************/
+/**************************************************************/
+/**** ****/
+/**** ****/
+/**** Sending of Modbus ASCII Frames ****/
+/**** ****/
+/**** ****/
+/**************************************************************/
+/**************************************************************/
+
+/*
+ * NOTE: for now the transmit_timeout is silently ignored in ASCII version!
+ */
+int modbus_ascii_write(int nd,
+ u8 *data,
+ size_t data_length,
+ u16 transaction_id,
+ const struct timespec *transmit_timeout
+ )
+ {
+ fd_set rfds;
+ struct timeval timeout;
+ int res, bin_data_conv, send_retries;
+ frame_part_t frame_part;
+ nd_entry_t *nd_entry;
+
+ /* check if nd is correct... */
+ if ((nd_entry = nd_table_get_nd(&nd_table_, nd)) == NULL)
+ return -1;
+
+ /* check if nd is initialzed... */
+ if (nd_entry->fd < 0)
+ return -1;
+
+ /* THE MAIN LOOP!!! */
+ send_retries = ASC_FRAME_SEND_RETRY + 1; /* must try at least once... */
+ while (send_retries > 0) {
+
+ /*******************************
+ * synchronise with the bus... *
+ *******************************/
+ /* Remember that a RS485 bus is half-duplex, so we have to wait until
+ * nobody is transmitting over the bus for our turn to transmit.
+ * This will never happen on a modbus network if the master and
+ * slave state machines never get out of synch (granted, it probably
+ * only has two states, but a state machine nonetheless), but we want
+ * to make sure we can re-synchronise if they ever do get out of synch.
+ *
+ * The following lines are an attempt at re-synchronising with the bus.
+ * Unfortunately, due to the completely asynchronous nature of the
+ * modbus-ascii protocol (there are no time boundaries for sending a frame!),
+ * it is impossible to guarantee that we will synchronise correctly.
+ *
+ * Use of RTS/CTS over a half-duplex coms chanel would eliminate this
+ * 'feature', but not all RS232/RS485 converters delay activating the
+ * CTS signal, even though there may be current activity on the bus.
+ *
+ * We first wait until the bus is silent for at least 1.5 character interval.
+ * Note that we only get feedback from the device driver once a whole byte
+ * has been received, so we must wait longer than 1 character interval to make
+ * sure there is no current activity on the bus). We then flush the input and
+ * output queues.
+ */
+ /* NOTES:
+ * - we do not need to reset the rfds with FD_SET(ttyfd, &rfds)
+ * before every call to select! We only wait on one file descriptor,
+ * so if select returns succesfully, it must have that same file
+ * decriptor set in the rdfs!
+ * If select returns with a timeout, then we do not get to call
+ * select again!
+ * - We do not reset the timeout value. Normally this value is left
+ * unchanged when select() returns, so we will be witing for longer
+ * than the desired period.
+ * On Linux the timeout is changed to reflect the time remaining.
+ * In this case, things will work more nicely.
+ */
+ FD_ZERO(&rfds);
+ FD_SET(nd_entry->fd, &rfds);
+ timeout = nd_entry->time_15_char_;
+ while ((res = select(nd_entry->fd+1, &rfds, NULL, NULL, &timeout)) != 0) {
+ if (res > 0) {
+ /* we are receiving data over the serial port! */
+ /* Throw the data away! */
+ tcflush(nd_entry->fd, TCIFLUSH); /* flush the input stream */
+ /* reset the timeout value! */
+ timeout = nd_entry->time_15_char_;
+ } else {
+ /* some kind of error ocurred */
+ if (errno != EINTR)
+ /* we were not interrupted by a signal */
+ return -1;
+ /* We will be callind select() again.
+ * We need to reset the FD SET !
+ */
+ FD_ZERO(&rfds);
+ FD_SET(nd_entry->fd, &rfds);
+ }
+ } /* while (select()) */
+
+ /* Flush both input and output streams... */
+ /* NOTE: Due to the nature of the modbus protocol,
+ * when a frame is sent all previous
+ * frames that may have arrived at the sending node become
+ * irrelevant.
+ */
+ tcflush(nd_entry->fd, TCIOFLUSH); /* flush the input & output streams */
+ recv_buf_reset(&nd_entry->recv_buf_); /* reset the recv buffer */
+
+ /**********************
+ * write to output... *
+ **********************/
+ send_buf_reset(&nd_entry->send_buf_);
+
+ frame_part = fp_header; /* start off by sending the header... */
+ bin_data_conv = 0; /* binary format data already converted to ascii format... */
+ while ((frame_part != fp_done) ||
+ (send_buf_data_count(&nd_entry->send_buf_) > 0)) {
+
+ /* build the frame we will send over the wire... */
+ /* We use a state machine with four states... */
+ if (frame_part == fp_header) {
+ if (send_buf_header_append(&nd_entry->send_buf_) >= 0)
+ frame_part = fp_body;
+ }
+ if (frame_part == fp_body) {
+ res = send_buf_data_add(&nd_entry->send_buf_, data + bin_data_conv, data_length - bin_data_conv);
+ bin_data_conv += res;
+ if (bin_data_conv == data_length)
+ frame_part = fp_lrc;
+ }
+ if (frame_part == fp_lrc) {
+ if (send_buf_lrc_append(&nd_entry->send_buf_) >= 0)
+ frame_part = fp_tail;
+ }
+ if (frame_part == fp_tail) {
+ if (send_buf_tail_append(&nd_entry->send_buf_) >= 0)
+ frame_part = fp_done;
+ }
+
+ /* send the frame... */
+ if ((res = write(nd_entry->fd,
+ send_buf_data(&nd_entry->send_buf_),
+ send_buf_data_count(&nd_entry->send_buf_))) < 0) {
+ if ((errno != EAGAIN ) && (errno != EINTR )) {
+ break;
+ } else {
+ /* there is no harm if we get interrupted while writing the frame.
+ * The ascii version of modbus does not place any timing
+ * constraints whatsoever...
+ *
+ * We simply continue sending the frame...
+ */
+ res = 0;
+ }
+ }
+#ifdef DEBUG
+/* Print each character that has just been sent on the bus */
+ { int i;
+ printf("bytes sent -> [");
+ for(i = 0; i < res; i++)
+ printf("%c", send_buf_data(&nd_entry->send_buf_)[i]);
+ printf("]\n");
+ }
+#endif
+ send_buf_data_purge(&nd_entry->send_buf_, res);
+
+ if ((frame_part == fp_done) &&
+ (send_buf_data_count(&nd_entry->send_buf_) == 0))
+ /* sent frame successfully! */
+ return data_length;
+
+ } /* while(frame_not_sent) */
+ /* NOTE: Some error occured while trying to transmit. It will probably
+ * not go away by itself, but there is no harm in trying again
+ * if the upper layer so wishes...
+ */
+ send_retries--;
+ } /* while() MAIN LOOP */
+
+ /* maximum retries exceeded */
+ return -1;
+}
+
+
+
+/**************************************************************/
+/**************************************************************/
+/**** ****/
+/**** ****/
+/**** Receiving Modbus ASCII Frames ****/
+/**** ****/
+/**** ****/
+/**************************************************************/
+/**************************************************************/
+
+
+/************************************/
+/** **/
+/** Read a frame **/
+/** **/
+/************************************/
+
+/* A function to read a valid frame off the modbus ascii bus.
+ *
+ * NOTES:
+ * - The returned frame is guaranteed to be a valid frame.
+ * - The returned length does *not* include the LRC.
+ */
+/* NOTE: This function is only called from one place in the rest of the code,
+ * so we might just as well make it inline...
+ */
+/* RETURNS: number of bytes in received frame
+ * -1 on read file error
+ * -2 on timeout
+ */
+static inline int read_frame(nd_entry_t *nd_entry,
+ u8 **recv_data_ptr,
+ struct timespec *end_time)
+{
+ /* temporary variables... */
+ fd_set rfds;
+ int res;
+
+ /* start by deleting any previously converted frames... */
+ recv_buf_frame_purge(&nd_entry->recv_buf_);
+
+ /*==============*
+ * read a frame *
+ *==============*/
+#ifdef DEBUG
+ printf("bytes read -> <");
+#endif
+ /* The main loop that reads one frame */
+ /* (multiple calls to read() ) */
+ /* and jumps out as soon as it finds a valid frame. */
+ /* NOTE: Do not change this into a do {...} until() loop!
+ * The while loop may find valid frames in the data read off the
+ * bus in previous invocations of this same fucntion, and may
+ * therefore not execute any loop iteration whatsoever,
+ * and not call read()
+ */
+ while ((*recv_data_ptr = recv_buf_frame(&nd_entry->recv_buf_)) == NULL) {
+ /* We need more bytes... */
+
+ /*-----------------------------*
+ * check for data availability *
+ *-----------------------------*/
+ /* if we can't find a valid frame in the existing data, or no data
+ * was left over, then we need to read more bytes!
+ */
+ FD_ZERO(&rfds);
+ FD_SET(nd_entry->fd, &rfds);
+ {int sel_res = my_select(nd_entry->fd + 1, &rfds, NULL, end_time);
+ if (sel_res < 0)
+ return -1;
+ if (sel_res == 0)
+ return -2;
+ }
+
+ /*------------------*
+ * read frame bytes *
+ *------------------*/
+ /* Read in as many bytes as possible... */
+ res = read(nd_entry->fd,
+ recv_buf_free(&nd_entry->recv_buf_),
+ recv_buf_free_count(&nd_entry->recv_buf_));
+ if (res < 0) {
+ if (errno != EINTR)
+ return -1;
+ else
+ res = 0;
+ }
+#ifdef DEBUG
+ {/* display the hex code of each character received */
+ int i;
+ for (i=0; i < res; i++)
+ printf("%c", recv_buf_free(&nd_entry->recv_buf_)[i]);
+ }
+#endif
+ recv_buf_data_add(&nd_entry->recv_buf_, res);
+ } /* while ()*/
+#ifdef DEBUG
+ printf(">\n");
+#endif
+
+ /* We have a frame! */
+ return recv_buf_frame_count(&nd_entry->recv_buf_);
+}
+
+
+
+
+
+/**************************************/
+/** **/
+/** Read a Modbus ASCII frame **/
+/** **/
+/**************************************/
+
+/* The public function that reads a valid modbus frame.
+ *
+ * The returned frame is guaranteed to be different to the
+ * the frame stored in send_data, and to start with the
+ * same slave address stored in send_data[0].
+ *
+ * If send_data is NULL, send_data_length = 0, or
+ * ignore_echo == 0, then the first valid frame read off
+ * the bus is returned.
+ *
+ * return value: The length (in bytes) of the valid frame,
+ * -1 on error
+ * -2 on timeout
+ */
+
+int modbus_ascii_read(int *nd,
+ u8 **recv_data_ptr,
+ u16 *transaction_id,
+ const u8 *send_data,
+ int send_length,
+ const struct timespec *recv_timeout) {
+
+ struct timespec end_time, *ts_ptr;
+ int res, recv_length;
+ int iter; /* Warning: if you change the var type of iter from int,
+ * then you must also change the INT_MAX constant
+ * further on in this function...
+ */
+ u8 *local_recv_data_ptr;
+ nd_entry_t *nd_entry;
+
+ /* Check input parameters... */
+ if (nd == NULL)
+ return -1;
+
+ if (recv_data_ptr == NULL)
+ recv_data_ptr = &local_recv_data_ptr;
+
+ if ((send_data == NULL) && (send_length != 0))
+ return -1;
+
+ /* check if nd is correct... */
+ if ((nd_entry = nd_table_get_nd(&nd_table_, *nd)) == NULL)
+ return -1;
+
+ /* check if nd is initialzed... */
+ if (nd_entry->fd < 0)
+ return -1;
+
+ /* We will potentially read many frames, and we cannot reset the timeout
+ * for every frame we read. We therefore determine the absolute time_out,
+ * and use this as a parameter for each call to read_frame() instead of
+ * using a relative timeout.
+ *
+ * NOTE: see also the timeout related comment in the read_frame()= function!
+ */
+ ts_ptr = NULL;
+ if (recv_timeout != NULL) {
+ ts_ptr = &end_time;
+ *ts_ptr = timespec_add_curtime(*recv_timeout);
+ }
+
+ /* NOTE: When using a half-duplex RS-485 bus, some (most ?) RS232-485
+ * converters will send back to the RS232 port whatever we write,
+ * so we will read in whatever we write out onto the bus.
+ * We will therefore have to compare
+ * the first frame we read with the one we sent. If they are
+ * identical it is because we are in fact working on a RS-485
+ * bus and must therefore read in a second frame which will be
+ * the true response to our query.
+ * If the first frame we receive is different to the last frame we
+ * just sent, then we are *not* working on a RS-485 bus, and
+ * that is already the real response to our query.
+ *
+ * Flushing the input cache immediately *after* sending a frame
+ * could solve this issue, but we have no guarantee that this
+ * process would not get swapped out between the write() and
+ * flush() calls, and we could therefore be flushing the response
+ * frame along with the last frame we sent!
+ */
+
+ iter = 0;
+ while ((res = recv_length = read_frame(nd_entry, recv_data_ptr, ts_ptr)) >= 0) {
+ if (iter < INT_MAX) iter++;
+
+ if ((send_length <= 0) || (nd_entry->ignore_echo == 0))
+ /* any valid frame will do... */
+ return recv_length;
+
+ if ((send_length > L2_FRAME_SLAVEID_OFS + 1) && (iter == 1))
+ /* We have a frame in send_data,
+ * so we must make sure we are not reading in the frame just sent...
+ *
+ * We must only do this for the first frame we read. Subsequent
+ * frames are guaranteed not to be the previously sent frame
+ * since the modbus_ascii_write() resets the recv buffer.
+ * Remember too that valid modbus responses may be exactly the same
+ * as the request frame!!
+ */
+ if (recv_length == send_length)
+ if (memcmp(*recv_data_ptr, send_data, recv_length) == 0)
+ /* recv == send !!! */
+ /* read in another frame. */
+ continue;
+
+ /* The frame read is either:
+ * - different to the frame in send_data
+ * - or there is only the slave id in send_data[L2_FRAME_SLAVEID_OFS]
+ * - or both of the above...
+ */
+ if (send_length > L2_FRAME_SLAVEID_OFS)
+ if (recv_length > L2_FRAME_SLAVEID_OFS)
+ /* check that frame is from/to the correct slave... */
+ if ((*recv_data_ptr)[L2_FRAME_SLAVEID_OFS] == send_data[L2_FRAME_SLAVEID_OFS])
+ /* yep, it is... */
+ return recv_length;
+
+ /* The frame we have received is not acceptable...
+ * Let's read a new frame.
+ */
+ } /* while(...) */
+
+ /* error reading response! */
+ /* Return the error returned by read_frame! */
+ return res;
+}
+
+
+
+
+
+/**************************************************************/
+/**************************************************************/
+/**** ****/
+/**** ****/
+/**** Initialising and Shutting Down Library ****/
+/**** ****/
+/**** ****/
+/**************************************************************/
+/**************************************************************/
+
+/******************************/
+/** **/
+/** Load Default Values **/
+/** **/
+/******************************/
+
+static void set_defaults(int *baud,
+ int *parity,
+ int *data_bits,
+ int *stop_bits) {
+ /* Set the default values, if required... */
+ if (*baud == 0)
+ *baud = DEF_BAUD_RATE;
+ if (*data_bits == 0)
+ *data_bits = DEF_DATA_BITS;
+ if (*stop_bits == 0) {
+ if (*parity == 0)
+ *stop_bits = DEF_STOP_BITS_NOP; /* no parity */
+ else
+ *stop_bits = DEF_STOP_BITS_PAR; /* parity used */
+ }
+}
+
+
+
+
+
+/******************************/
+/** **/
+/** Initialise Library **/
+/** **/
+/******************************/
+
+int modbus_ascii_init(int nd_count,
+ optimization_t opt,
+ int *extra_bytes)
+{
+#ifdef DEBUG
+ printf("modbus_asc_init(): called...\n");
+ printf("creating %d node descriptors\n", nd_count);
+ if (opt == optimize_speed)
+ printf("optimizing for speed\n");
+ if (opt == optimize_size)
+ printf("optimizing for size\n");
+#endif
+
+ /* check input parameters...*/
+ if (0 == nd_count) {
+ if (extra_bytes != NULL)
+ // Not the corect value for this layer.
+ // What we set it to in case this layer is not used!
+ *extra_bytes = 0;
+ return 0;
+ }
+ if (nd_count <= 0)
+ goto error_exit_0;
+
+ if (extra_bytes == NULL)
+ goto error_exit_0;
+
+ /* initialise nd table... */
+ if (nd_table_init(&nd_table_, nd_count) < 0)
+ goto error_exit_0;
+
+ /* remember the optimization choice for later reference... */
+ optimization_ = opt;
+
+#ifdef DEBUG
+ printf("modbus_asc_init(): returning succesfuly...\n");
+#endif
+ return 0;
+
+error_exit_0:
+ if (extra_bytes != NULL)
+ *extra_bytes = 0; // The value we set this to in case of error.
+ return -1;
+}
+
+
+
+/******************************/
+/** **/
+/** Open node descriptor **/
+/** **/
+/******************************/
+
+/* Open a node for master or slave operation.
+ * Returns the node descriptor, or -1 on error.
+ *
+ * This function is mapped onto both
+ * modbus_connect() and modbus_listen()
+ */
+int modbus_ascii_connect(node_addr_t node_addr) {
+ int node_descriptor;
+ nd_entry_t *nd_entry;
+
+#ifdef DEBUG
+ printf("modbus_ascii_connect(): called...\n");
+ printf("opening %s\n", node_addr.addr.ascii.device);
+ printf("baud_rate = %d\n", node_addr.addr.ascii.baud);
+ printf("parity = %d\n", node_addr.addr.ascii.parity);
+ printf("data_bits = %d\n", node_addr.addr.ascii.data_bits);
+ printf("stop_bits = %d\n", node_addr.addr.ascii.stop_bits);
+ printf("ignore_echo = %d\n", node_addr.addr.ascii.ignore_echo);
+#endif
+
+ /* Check for valid address family */
+ if (node_addr.naf != naf_ascii)
+ /* wrong address type... */
+ goto error_exit_0;
+
+ /* find a free node descriptor */
+ if ((node_descriptor = nd_table_get_free_nd(&nd_table_)) < 0)
+ /* if no free nodes to initialize, then we are finished... */
+ goto error_exit_0;
+ if ((nd_entry = nd_table_get_nd(&nd_table_, node_descriptor)) == NULL)
+ /* strange, this should not occur... */
+ goto error_exit_0;
+
+ /* set the default values... */
+ set_defaults(&(node_addr.addr.ascii.baud),
+ &(node_addr.addr.ascii.parity),
+ &(node_addr.addr.ascii.data_bits),
+ &(node_addr.addr.ascii.stop_bits));
+
+ if (nd_entry_connect(nd_entry, &node_addr, optimization_) < 0)
+ goto error_exit_0;
+
+#ifdef DEBUG
+ printf("modbus_ascii_connect(): %s open\n", node_addr.addr.ascii.device );
+ printf("modbus_ascii_connect(): returning nd=%d\n", node_descriptor);
+#endif
+ return node_descriptor;
+
+ error_exit_0:
+ return -1;
+}
+
+
+
+int modbus_ascii_listen(node_addr_t node_addr) {
+ return modbus_ascii_connect(node_addr);
+}
+
+
+
+/******************************/
+/** **/
+/** Close node descriptor **/
+/** **/
+/******************************/
+
+int modbus_ascii_close(int nd) {
+ return nd_table_free_nd(&nd_table_, nd);
+}
+
+
+
+/******************************/
+/** **/
+/** Shutdown Library **/
+/** **/
+/******************************/
+
+int modbus_ascii_done(void) {
+ nd_table_done(&nd_table_);
+ return 0;
+}
+
+
+
+
+
+/******************************/
+/** **/
+/** **/
+/** **/
+/******************************/
+int modbus_ascii_silence_init(void) {
+ return 0;
+}
+
+
+
+
+/******************************/
+/** **/
+/** **/
+/** **/
+/******************************/
+
+
+double modbus_ascii_get_min_timeout(int baud,
+ int parity,
+ int data_bits,
+ int stop_bits) {
+ int parity_bits, start_bits, char_bits;
+
+ set_defaults(&baud, &parity, &data_bits, &stop_bits);
+ parity_bits = (parity == 0)?0:1;
+ start_bits = 1;
+ char_bits = start_bits + data_bits + parity_bits + stop_bits;
+ return (double)((MAX_ASC_FRAME_LENGTH * char_bits) / baud);
+}
+
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mb_ascii_private.h Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2002,2016 Mario de Sousa (msousa@fe.up.pt)
+ *
+ * This file is part of the Modbus library for Beremiz and matiec.
+ *
+ * This Modbus library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this Modbus library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is made available on the understanding that it will not be
+ * used in safety-critical situations without a full and competent review.
+ */
+
+
+
+#ifndef MODBUS_ASCII_PRIVATE_H
+#define MODBUS_ASCII_PRIVATE_H
+
+
+
+#include "mb_util.h"
+
+
+/* serial port default configuration... */
+#define DEF_DATA_BITS 7
+#define DEF_STOP_BITS_PAR 1 /* default stop bits if parity is used */
+#define DEF_STOP_BITS_NOP 2 /* default stop bits if parity is not used */
+#define DEF_BAUD_RATE 9600
+
+
+/* Send retries of ascii frames... */
+#define ASC_FRAME_SEND_RETRY 0
+ /* NOTES:
+ * - the above are the retries at the layer1 level,
+ * higher layers may decide to retry for themselves!
+ * - For ascii frames, it doesn't make much sense to retry
+ * if the first try failed...
+ */
+
+
+/* Buffer sizes... */
+#define SEND_BUFFER_SIZE_SMALL (MAX_ASC_FRAME_LENGTH / 2)
+#define SEND_BUFFER_SIZE_LARGE (MAX_ASC_FRAME_LENGTH)
+#define RECV_BUFFER_SIZE_SMALL (MAX_ASC_FRAME_LENGTH / 2)
+#define RECV_BUFFER_SIZE_LARGE (MAX_ASC_FRAME_LENGTH)
+
+
+/* Frame lengths... */
+
+/* The smallest element in an ascii frame.
+ * This is used later to define the smallest value of certain buffers...
+ */
+#define ASC_FRAME_MIN_ELE_LENGTH ASC_FRAME_HEADER_LENGTH
+
+#if ASC_FRAME_MIN_ELE_LENGTH < ASC_FRAME_TAIL_LENGTH
+#undef ASC_FRAME_MIN_ELE_LENGTH
+#define ASC_FRAME_MIN_ELE_LENGTH ASC_FRAME_TAIL_LENGTH
+#endif
+
+#if ASC_FRAME_MIN_ELE_LENGTH < ASC_FRAME_LRC_LENGTH
+#undef ASC_FRAME_MIN_ELE_LENGTH
+#define ASC_FRAME_MIN_ELE_LENGTH ASC_FRAME_LRC_LENGTH
+#endif
+
+#if ASC_FRAME_MIN_ELE_LENGTH < L2_TO_ASC_CODING
+#undef ASC_FRAME_MIN_ELE_LENGTH
+#define ASC_FRAME_MIN_ELE_LENGTH L2_TO_ASC_CODING
+#endif
+
+
+#endif /* MODBUS_ASCII_PRIVATE_H */
+
+
+
+
+
+
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mb_ds_util.h Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2002,2016 Mario de Sousa (msousa@fe.up.pt)
+ *
+ * This file is part of the Modbus library for Beremiz and matiec.
+ *
+ * This Modbus library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this Modbus library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is made available on the understanding that it will not be
+ * used in safety-critical situations without a full and competent review.
+ */
+
+
+
+ /* Data structures used by the modbus protocols... */
+
+
+#ifndef __MODBUS_DS_UTIL_H
+#define __MODBUS_DS_UTIL_H
+
+
+#include "mb_types.h" /* get the data types */
+
+/**************************************/
+/** **/
+/** A data structure - linear buffer **/
+/** **/
+/**************************************/
+
+/* An unbounded FIFO data structure.
+ *
+ * The user/caller writes and reads directly from the data structure's buffer,
+ * which eliminates slow copying of bytes between the user's and the structure's
+ * local memory.
+ *
+ * The data structure stores the current data linearly in a single memory array,
+ * i.e. the current data is stored from start to finish from a low address
+ * to a high address, and does *not* circle back to the bottom of the address
+ * space as is usual in a circular buffer. This allows the user/caller to
+ * pass the structure's own byte array on to other functions such as
+ * read() and write() for file operations.
+ *
+ * The FIFO is implemented by allocating more memory than the maximum number
+ * of bytes it will ever hold, and using the extra bytes at the top of the
+ * array as the bottom data bytes are released. When we run out of extra bytes,
+ * (actually, when the number of un-used bytes at the beginning is larger than
+ * a configured maximum), the whole data is moved down, freeing once again the
+ * extra bytes at the top of the array.
+ *
+ * Remember that we can optimize the data structure so that whenever it becomes
+ * empty, we can reset it to start off at the bottom of the byte array, i.e. we
+ * can set the start = end = 0; instead of simply setting the start = end, which
+ * may point to any position in the array.
+ *
+ * Taking the above into consideration, it would probably be a little more efficient
+ * to implement it as a circular buffer with an additional linearize() function
+ * the user could call whenever (s)he required the data to be stored linearly.
+ * Nevertheless, since it has already been implemented as a linear buffer, and since
+ * under normal circumstances the start and end pointers will be reset to 0 quite
+ * often (and therefore we get no additional benefit under normal circumstances),
+ * we will leave it as it is for the time being...
+ *
+ *
+ * The user is expected to call
+ * lb_init() -> to initialize the structure
+ * lb_done() -> to free the data structure's memory
+ *
+ * The user can store data starting off from...
+ * lb_free() -> pointer to address of free memory
+ * lb_free_count() -> number of free bytes available
+ * and then call
+ * lb_data_add()
+ * to add the data to the data structure
+ *
+ * Likewise, the user can read the data directly from
+ * lb_data() -> pointer to address of data location
+ * lb_free_count() -> number of data bytes available
+ * and free the data using
+ * lb_data_purge()
+ * to remove the data from the data structure
+ */
+
+
+typedef struct {
+ u8 *data;
+ int data_size; /* size of the *data buffer */
+ int data_start; /* offset within *data were valid data starts */
+ int data_end; /* offset within *data were valid data ends */
+ int max_data_start; /* optimization parameter! When should it be normalised? */
+ } lb_buf_t;
+
+/* NOTE: lb = Linear Buffer */
+
+static inline u8 *lb_init(lb_buf_t *buf, int size, int max_data_start) {
+ if (size <= 0)
+ return NULL;
+
+ if (max_data_start >= size)
+ max_data_start = size - 1;
+
+ buf->data_size = size;
+ buf->data_start = 0;
+ buf->data_end = 0;
+ buf->max_data_start = max_data_start;
+ buf->data = (u8 *)malloc(size);
+ return buf->data;
+}
+
+static inline void lb_done(lb_buf_t *buf) {
+ free(buf->data);
+ buf->data = NULL;
+}
+
+static inline u8 *lb_normalize(lb_buf_t *buf) {
+ return (u8 *)memmove(buf->data,
+ buf->data + buf->data_start,
+ buf->data_end - buf->data_start);
+}
+
+static inline u8 *lb_data(lb_buf_t *buf) {
+ return buf->data + buf->data_start;
+}
+
+static inline int lb_data_count(lb_buf_t *buf) {
+ return buf->data_end - buf->data_start;
+}
+
+static inline void lb_data_add(lb_buf_t *buf, int count) {
+ if ((buf->data_end += count) >= buf->data_size)
+ buf->data_end = buf->data_size - 1;
+}
+
+static inline u8 *lb_data_purge(lb_buf_t *buf, int count) {
+ buf->data_start += count;
+ if (buf->data_start > buf->data_end)
+ buf->data_start = buf->data_end;
+
+ if ((buf->data_end == buf->data_size) || (buf->data_start >= buf->max_data_start))
+ return lb_normalize(buf);
+
+ return buf->data + buf->data_start;
+}
+
+static inline void lb_data_purge_all(lb_buf_t *buf) {
+ buf->data_start = buf->data_end = 0;
+}
+
+static inline u8 *lb_free(lb_buf_t *buf) {
+ return buf->data + buf->data_end;
+}
+
+static inline int lb_free_count(lb_buf_t *buf) {
+ return buf->data_size - buf->data_end;
+}
+
+
+
+
+#endif /* __MODBUS_DS_UTIL_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mb_layer1.h Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2001,2016 Mario de Sousa (msousa@fe.up.pt)
+ *
+ * This file is part of the Modbus library for Beremiz and matiec.
+ *
+ * This Modbus library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this Modbus library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is made available on the understanding that it will not be
+ * used in safety-critical situations without a full and competent review.
+ */
+
+
+
+#ifndef MODBUS_LAYER1_H
+#define MODBUS_LAYER1_H
+
+#include <time.h> /* struct timespec data type */
+
+#include "mb_types.h" /* get the data types */
+#include "mb_addr.h" /* get definitions of common variable types */
+
+
+/* Define max registers and bits */
+#define MAX_READ_BITS 2000 /* Functions 0x01 and 0x02 */
+#define MAX_READ_REGS 125 /* Functions 0x03 and 0x04 */
+#define MAX_WRITE_COILS 1968 /* Function 0x0F */
+#define MAX_WRITE_REGS 123 /* Function 0x10 */
+
+
+/* Declare TCP layer1 functions */
+#define modbus_write modbus_tcp_write
+#define modbus_read modbus_tcp_read
+#define modbus_init modbus_tcp_init
+#define modbus_done modbus_tcp_done
+#define modbus_connect modbus_tcp_connect
+#define modbus_listen modbus_tcp_listen
+#define modbus_close modbus_tcp_close
+#define modbus_silence_init modbus_tcp_silence_init
+#define modbus_get_min_timeout modbus_tcp_get_min_timeout
+
+#include "mb_layer1_prototypes.h"
+
+#undef modbus_write
+#undef modbus_read
+#undef modbus_init
+#undef modbus_done
+#undef modbus_connect
+#undef modbus_listen
+#undef modbus_close
+#undef modbus_silence_init
+#undef modbus_get_min_timeout
+
+
+
+
+
+/* Declare RTU layer1 functions */
+#define modbus_write modbus_rtu_write
+#define modbus_read modbus_rtu_read
+#define modbus_init modbus_rtu_init
+#define modbus_done modbus_rtu_done
+#define modbus_connect modbus_rtu_connect
+#define modbus_listen modbus_rtu_listen
+#define modbus_close modbus_rtu_close
+#define modbus_silence_init modbus_rtu_silence_init
+#define modbus_get_min_timeout modbus_rtu_get_min_timeout
+
+#include "mb_layer1_prototypes.h"
+
+#undef modbus_write
+#undef modbus_read
+#undef modbus_init
+#undef modbus_done
+#undef modbus_connect
+#undef modbus_listen
+#undef modbus_close
+#undef modbus_silence_init
+#undef modbus_get_min_timeout
+
+
+
+
+
+/* Declare ASCII layer1 functions */
+#define modbus_write modbus_ascii_write
+#define modbus_read modbus_ascii_read
+#define modbus_init modbus_ascii_init
+#define modbus_done modbus_ascii_done
+#define modbus_connect modbus_ascii_connect
+#define modbus_listen modbus_ascii_listen
+#define modbus_close modbus_ascii_close
+#define modbus_silence_init modbus_ascii_silence_init
+#define modbus_get_min_timeout modbus_ascii_get_min_timeout
+
+#include "mb_layer1_prototypes.h"
+
+#undef modbus_write
+#undef modbus_read
+#undef modbus_init
+#undef modbus_done
+#undef modbus_connect
+#undef modbus_listen
+#undef modbus_close
+#undef modbus_silence_init
+#undef modbus_get_min_timeout
+
+
+
+
+#define modbus_write (*modbus_write )
+#define modbus_read (*modbus_read )
+#define modbus_init (*modbus_init )
+#define modbus_done (*modbus_done )
+#define modbus_connect (*modbus_connect )
+#define modbus_listen (*modbus_listen )
+#define modbus_close (*modbus_close )
+#define modbus_silence_init (*modbus_silence_init )
+#define modbus_get_min_timeout (*modbus_get_min_timeout)
+
+typedef struct {
+ #include "mb_layer1_prototypes.h"
+} layer1_funct_ptr_t;
+
+#undef modbus_write
+#undef modbus_read
+#undef modbus_init
+#undef modbus_done
+#undef modbus_connect
+#undef modbus_listen
+#undef modbus_close
+#undef modbus_silence_init
+#undef modbus_get_min_timeout
+
+
+
+extern layer1_funct_ptr_t fptr_[4];
+
+
+
+
+
+#endif /* MODBUS_LAYER1_H */
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mb_layer1_prototypes.h Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2002,2016 Mario de Sousa (msousa@fe.up.pt)
+ *
+ * This file is part of the Modbus library for Beremiz and matiec.
+ *
+ * This Modbus library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this Modbus library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is made available on the understanding that it will not be
+ * used in safety-critical situations without a full and competent review.
+ */
+
+
+
+
+ /* write a modbus frame */
+ /* WARNING: when calling this function, the *frame_data buffer
+ * must be allocated with an extra *extra_bytes
+ * beyond those required for the frame_length.
+ * This is because the extra bytes will be used
+ * to store the crc before sending the frame.
+ *
+ * The *extra_bytes value will be returned by the
+ * modbus_init() function call.
+ */
+ /* NOTE: calling this function will flush the input stream,
+ * which means any frames that may have arrived
+ * but have not yet been read using modbus_read()
+ * will be permanently lost...
+ */
+int modbus_write(int nd,
+ u8 *frame_data,
+ size_t frame_length,
+ u16 transaction_id,
+ const struct timespec *transmit_timeout
+ );
+
+ /* read a modbus frame */
+/*
+ * The frame is read from:
+ * - the node descriptor nd, if nd >= 0
+ * - any valid and initialised node descriptor, if nd = -1
+ * In this case, the node where the data is eventually read from
+ * is returned in *nd.
+ * NOTE: (only avaliable if using TCP)
+ */
+ /* NOTE: calling modbus_write() will flush the input stream,
+ * which means any frames that may have arrived
+ * but have not yet been read using modbus_read()
+ * will be permanently lost...
+ *
+ * NOTE: Ususal select semantics for (a: recv_timeout == NULL) and
+ * (b: *recv_timeout == 0) also apply.
+ * (a) Indefinite timeout
+ * (b) Try once, and and quit if no data available.
+ */
+ /* NOTE: send_data and send_length is used to pass to the modbus_read() function
+ * the frame that was previously sent over the same connection (node).
+ * This data is then allows the modbus_read() function to ignore any
+ * data that is read but identical to the previously sent data. This
+ * is used when using serial ports that echoes back all the data that is
+ * sent out over the same serial port. When using some RS232 to RS485
+ * converters, this functionality is essential as not all these converters
+ * are capable of not echoing back the sent data.
+ * These parameters are ignored when using TCP!
+ */
+
+ /* RETURNS: number of bytes read
+ * -1 on read from file/node error
+ * -2 on timeout
+ */
+int modbus_read(int *nd, /* node descriptor */
+ u8 **recv_data_ptr,
+ u16 *transaction_id,
+ const u8 *send_data,
+ int send_length,
+ const struct timespec *recv_timeout);
+
+
+ /* init the library */
+int modbus_init(int nd_count, /* maximum number of nodes... */
+ optimization_t opt,
+ int *extra_bytes);
+
+ /* shutdown the library...*/
+int modbus_done(void);
+
+
+/* Open a node for master / slave operation.
+ * Returns the node descriptor, or -1 on error.
+ */
+int modbus_connect(node_addr_t node_addr);
+int modbus_listen(node_addr_t node_addr);
+
+/* Close a node, needs a node descriptor as argument... */
+int modbus_close(int nd);
+
+/* Tell the library that the user will probably not be communicating
+ * for some time...
+ * This will allow the library to release any resources it will not
+ * be needing during the silence.
+ * NOTE: This is onlyused by the TCP version to close down tcp connections
+ * when the silence will going to be longer than second.
+ */
+int modbus_silence_init(void);
+
+ /* determine the minmum acceptable timeout... */
+ /* NOTE: timeout values passed to modbus_read() lower than the value returned
+ * by this function may result in frames being aborted midway, since they
+ * take at least modbus_get_min_timeout() seconds to transmit.
+ */
+double modbus_get_min_timeout(int baud,
+ int parity,
+ int data_bits,
+ int stop_bits);
+
+
+
+
+
+
+
+
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mb_master.c Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,1277 @@
+/*
+ * Copyright (c) 2001-2003,2016 Mario de Sousa (msousa@fe.up.pt)
+ *
+ * This file is part of the Modbus library for Beremiz and matiec.
+ *
+ * This Modbus library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this Modbus library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is made available on the understanding that it will not be
+ * used in safety-critical situations without a full and competent review.
+ */
+
+
+/* mb_master.c */
+
+
+#include <fcntl.h> /* File control definitions */
+#include <stdio.h> /* Standard input/output */
+#include <string.h>
+#include <stdlib.h>
+#include <termio.h> /* POSIX terminal control definitions */
+#include <sys/time.h> /* Time structures for select() */
+#include <unistd.h> /* POSIX Symbolic Constants */
+#include <errno.h> /* Error definitions */
+
+#include <pthread.h> /* pthread_mutex_[un]lock() */
+
+#include <netinet/in.h> /* required for htons() and ntohs() */
+#include "mb_layer1.h"
+#include "mb_master.h"
+#include "mb_master_private.h"
+
+/* #define DEBUG */ /* uncomment to see the data sent and received */
+
+#define modbus_write fptr_[layer1_fin].modbus_write
+#define modbus_read fptr_[layer1_fin].modbus_read
+#define modbus_init fptr_[layer1_fin].modbus_init
+#define modbus_done fptr_[layer1_fin].modbus_done
+#define modbus_connect fptr_[layer1_fin].modbus_connect
+#define modbus_listen fptr_[layer1_fin].modbus_listen
+#define modbus_close fptr_[layer1_fin].modbus_close
+#define modbus_silence_init fptr_[layer1_fin].modbus_silence_init
+#define modbus_get_min_timeout fptr_[layer1_fin].modbus_get_min_timeout
+
+/* the lower two bits of ttyfd are used to store the index to layer1 function pointers */
+/* layer1_fin index to fptr_[] is in lowest 2 bits of fd */
+#define get_ttyfd() int layer1_fin = fd & 3; int ttyfd = fd / 4;
+
+
+
+/******************************************/
+/******************************************/
+/** **/
+/** Global Variables... **/
+/** **/
+/******************************************/
+/******************************************/
+ /* The layer 1 (RTU, ASCII, TCP) implementation will be adding some headers and CRC (at the end)
+ * of the packet we build here (actually currently it is only at the end). Since we want to
+ * re-use the same buffer so as not to continuosly copy the same info from buffer to buffer,
+ * we need tp allocate more bytes than the ones we need for this layer. Therefore, the
+ * extra_bytes parameter.
+ *
+ * Note that we add one more extra byte. This is because some packets will not be
+ * starting off at byte 0, but rather at byte 1 of the buffer. This is in order to guarantee
+ * that the data that is sent on the buffer is aligned on even bytes (the 16 bit words!).
+ * This will allow us to reference this memory as an u16 *, without producing 'bus error'
+ * messages in some embedded devices that do not allow acessing u16 on odd numbered addresses.
+ */
+static int buff_extra_bytes_;
+#define QUERY_BUFFER_SIZE (MAX_L2_FRAME_LENGTH + buff_extra_bytes_ + 1)
+
+
+/******************************************/
+/******************************************/
+/** **/
+/** Local Utility functions... **/
+/** **/
+/******************************************/
+/******************************************/
+
+
+/*
+ * Function to determine next transaction id.
+ *
+ * We use a library wide transaction id, which means that we
+ * use a new transaction id no matter what slave to which we will
+ * be sending the request...
+ */
+static inline u16 next_transaction_id(void) {
+ static u16 next_id = 0;
+ return next_id++;
+}
+
+
+/*
+ * Functions to convert u16 variables
+ * between network and host byte order
+ *
+ * NOTE: Modbus uses MSByte first, just like
+ * tcp/ip, so we use the htons() and
+ * ntoh() functions to guarantee
+ * code portability.
+ */
+static inline u16 mb_hton(u16 h_value) {return htons(h_value);}
+static inline u16 mb_ntoh(u16 m_value) {return ntohs(m_value);}
+static inline u8 msb (u16 value) {return (value >> 8) & 0xFF;}
+static inline u8 lsb (u16 value) {return value & 0xFF;}
+
+
+
+
+/*************************************************/
+/*************************************************/
+/** **/
+/** Common functions for Modbus Protocol. **/
+/** **/
+/*************************************************/
+/*************************************************/
+
+/* build the common elements of a query frame */
+static inline int build_packet(u8 slave,
+ u8 function,
+ u16 start_addr,
+ u16 count,
+ u8 *packet) {
+ union {
+ u16 u16;
+ u8 u8[2];
+ } tmp;
+
+ packet[0] = slave,
+ packet[1] = function;
+ /* NOTE:
+ * Modbus uses high level addressing starting off from 1, but
+ * this is sent as 0 on the wire!
+ * We could expect the user to specify high level addressing
+ * starting at 1, and do the conversion to start off at 0 here.
+ * However, to do this we would then need to use an u32 data type
+ * to correctly hold the address supplied by the user (which could
+ * correctly be 65536, which does not fit in an u16), which would
+ * in turn require us to check whether the address supplied by the user
+ * is correct (i.e. <= 65536).
+ * I decided to go with the other option of using an u16, and
+ * requiring the user to use addressing starting off at 0!
+ */
+ /* NOTE: we do not use up casting - i.e. the following
+ * *((u16 *)(packet+2)) = mb_hton(start_addr);
+ * because packet+2 is NOT aligned with an even address, and would
+ * therefore result in 'bus error' when using compilers that do not
+ * automatically do the required decomposing of this supposedly
+ * single bus access into two distinct bus accesses.
+ * (Note that some compilers do do this decomposing automatically
+ * in which case the following is not necessary).
+ * At the moment, I (Mario de Sousa) know of at least one cross-compiler
+ * that does not do the decomposing automatically, i.e. the
+ * AVR32 cross-compiler.
+ */
+ tmp.u16 = mb_hton(start_addr);
+ packet[2] = tmp.u8[0];
+ packet[3] = tmp.u8[1];
+ tmp.u16 = mb_hton(count);
+ packet[4] = tmp.u8[0];
+ packet[5] = tmp.u8[1];
+
+ return 6;
+}
+
+
+
+/* Execute a Query/Response transaction between client and server */
+/* returns: <0 -> ERROR: error codes
+ * >2 -> SUCCESS: frame length
+ * 0..2 -> will never be returned!
+ */
+static int mb_transaction(u8 *packet,
+ int query_length,
+ u8 **data,
+ int fd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout) {
+ int error = TIMEOUT;
+ int response_length = INTERNAL_ERROR;
+ u16 send_transaction_id, recv_transaction_id;
+ get_ttyfd(); /* declare the ttyfd variable, ... */
+
+ /* We must also initialize the recv_transaction_id with the same value,
+ * since some layer 1 protocols do not support transaction id's, so
+ * simply return the recv_transaction_id variable without any changes...
+ */
+ /* NOTE: we re-use the same transaction id for all send re-tries., since, in truth,
+ * it is still the same transaction. This will also simplify re-synchronising with
+ * some slaves that keep a buffer of outstanding requests, and will reply to all of
+ * them, in FIFO order. In this case, once an error occurs we will be swamping the
+ * slave with requests. By using the same transaction id, we may correctly consider
+ * the reply to the first request sent as the reply to the third request! This means
+ * we stop re-trying the sending of further requests, and no longer swamp the slave...
+ */
+ send_transaction_id = recv_transaction_id = next_transaction_id();
+
+ for (send_retries++; send_retries > 0; send_retries--) {
+ error = TIMEOUT;
+
+ if (modbus_write(ttyfd, packet, query_length, send_transaction_id, response_timeout) < 0)
+ {error = PORT_FAILURE; continue;}
+
+ /* if we receive a correct response but with a wrong transaction id or wrong modbus function, we try to
+ * receive another frame instead of returning an error or re-sending the request! This first frame could
+ * have been a response to a previous request of ours that timed out waiting for a response, and the
+ * response we are waiting for could be coming 'any minute now'.
+ */
+ do {
+ response_length = modbus_read(&ttyfd, data, &recv_transaction_id,
+ packet, query_length, response_timeout);
+
+ /* TIMEOUT condition */
+ /* However, if we had previously received an invalid frame, or some other error,
+ * we return that error instead!
+ * Note that the 'error' variable was initialised with the TIMEOUT error
+ * condition, so if no previous error ocurred, we will be returning the
+ * TIMEOUT error condition here!
+ */
+ if(response_length == -2) return error;
+ /* NOTE we want to break out of this while loop without even running the while()
+ * condition, as that condition is only valid if response_length > 3 !!
+ */
+ if(response_length < 0) {error = PORT_FAILURE; break;}
+ /* This should never occur! Modbus_read() should only return valid frames! */
+ if(response_length < 3) return INTERNAL_ERROR;
+
+ } while (/* we have the wrong transaction id */
+ (send_transaction_id != recv_transaction_id)
+ /* not a response frame to _our_ query */
+ ||
+ (((*data)[1] & ~0x80) != packet[1])
+ /* NOTE: no need to check whether (*data)[0] = slave! */
+ /* This has already been done by the modbus_read() function! */
+ );
+
+ if(response_length < 0) {error = PORT_FAILURE; continue;}
+
+ /* Now check whether we received a Modbus Exception frame */
+ if (((*data)[1] & 0x80) != 0) { /* we have an exception frame! */
+ /* NOTE: we have already checked above that data[2] exists! */
+ if (error_code != NULL) *error_code = (*data)[2];
+ return MODBUS_ERROR;
+ }
+ /* success! Let's get out of the send retry loop... */
+ return response_length;
+ }
+ /* reached the end of the retries... */
+ return error;
+}
+
+
+/**************************************/
+/**************************************/
+/** **/
+/** Modbus Protocol Functions. **/
+/** **/
+/**************************************/
+/**************************************/
+
+
+
+/* Execute a transaction for functions that READ BITS.
+ * Bits are stored on an int array, one bit per int.
+ * Called by: read_input_bits()
+ * read_output_bits()
+ */
+static int read_bits(u8 function,
+ u8 slave,
+ u16 start_addr,
+ u16 count,
+ u16 *dest,
+ int dest_size,
+ int ttyfd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout,
+ pthread_mutex_t *data_access_mutex) {
+
+ u8 packet[QUERY_BUFFER_SIZE];
+ u8 *data;
+ int response_length, query_length;
+ int temp, i, bit, dest_pos = 0;
+ int coils_processed = 0;
+
+ query_length = build_packet(slave, function, start_addr, count, packet);
+ if (query_length < 0) return INTERNAL_ERROR;
+
+ response_length = mb_transaction(packet, query_length, &data, ttyfd,
+ send_retries, error_code, response_timeout);
+
+ if (response_length < 0) return response_length;
+ /* NOTE: Integer division. (count+7)/8 is equivalent to ceil(count/8) */
+ if (response_length != 3 + (count+7)/8) return INVALID_FRAME;
+ if (data[2] != (count+7)/8) return INVALID_FRAME;
+
+ if (NULL != data_access_mutex) pthread_mutex_lock(data_access_mutex);
+ for( i = 0; (i < data[2]) && (i < dest_size); i++ ) {
+ temp = data[3 + i];
+ for( bit = 0x01; (bit & 0xff) && (coils_processed < count); ) {
+ dest[dest_pos] = (temp & bit)?1:0;
+ coils_processed++;
+ dest_pos++;
+ bit = bit << 1;
+ }
+ }
+ if (NULL != data_access_mutex) pthread_mutex_unlock(data_access_mutex);
+
+ return response_length;
+}
+
+
+
+/* Execute a transaction for functions that READ BITS.
+ * Bits are stored on an u32 array, 32 bits per u32.
+ * Unused bits in last u32 are set to 0.
+ * Called by: read_input_bits_u32()
+ * read_output_bits_u32()
+ */
+static int read_bits_u32(u8 function,
+ u8 slave,
+ u16 start_addr,
+ u16 count, /* number of bits !! */
+ u32 *dest,
+ int ttyfd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout) {
+ u8 packet[QUERY_BUFFER_SIZE];
+ u8 *data;
+ int response_length, query_length;
+ int byte_count, i, dest_pos = 0;
+
+ query_length = build_packet(slave, function, start_addr, count, packet);
+ if (query_length < 0) return INTERNAL_ERROR;
+
+ response_length = mb_transaction(packet, query_length, &data, ttyfd,
+ send_retries, error_code, response_timeout);
+
+ if (response_length < 0) return response_length;
+ /* NOTE: Integer division. (count+7)/8 is equivalent to ceil(count/8) */
+ if (response_length != 3 + (count+7)/8) return INVALID_FRAME;
+ if (data[2] != (count+7)/8) return INVALID_FRAME;
+
+ byte_count = data[2];
+ data += 3;
+ /* handle groups of 4 bytes... */
+ for(i = 0, dest_pos = 0; i + 3 < byte_count; i += 4, dest_pos++)
+ dest[dest_pos] = data[i] + data[i+1]*0x100 + data[i+2]*0x10000 + data[i+3]*0x1000000;
+ /* handle any remaining bytes... begining with the last! */
+ if (i < byte_count) dest[dest_pos] = 0;
+ for(byte_count--; i <= byte_count; byte_count--)
+ dest[dest_pos] = dest[dest_pos]*0x100 + data[byte_count];
+
+ return response_length;
+}
+
+
+
+/* FUNCTION 0x01 - Read Coils
+ * Bits are stored on an int array, one bit per int.
+ */
+inline int read_output_bits(u8 slave,
+ u16 start_addr,
+ u16 count,
+ u16 *dest,
+ int dest_size,
+ int ttyfd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout,
+ pthread_mutex_t *data_access_mutex) {
+ if( count > MAX_READ_BITS ) {
+ count = MAX_READ_BITS;
+ #ifdef DEBUG
+ fprintf( stderr, "Too many coils requested.\n" );
+ #endif
+ }
+
+ return read_bits(0x01 /* function */,
+ slave, start_addr, count, dest, dest_size, ttyfd,
+ send_retries, error_code, response_timeout, data_access_mutex);
+}
+
+
+
+/* FUNCTION 0x01 - Read Coils
+ * Bits are stored on an u32 array, 32 bits per u32.
+ * Unused bits in last u32 are set to 0.
+ */
+inline int read_output_bits_u32(u8 slave,
+ u16 start_addr,
+ u16 count,
+ u32 *dest,
+ int ttyfd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout) {
+ if( count > MAX_READ_BITS ) {
+ count = MAX_READ_BITS;
+ #ifdef DEBUG
+ fprintf( stderr, "Too many coils requested.\n" );
+ #endif
+ }
+
+ return read_bits_u32(0x01 /* function */,
+ slave, start_addr, count, dest, ttyfd,
+ send_retries, error_code, response_timeout);
+}
+
+
+/* FUNCTION 0x02 - Read Discrete Inputs
+ * Bits are stored on an int array, one bit per int.
+ */
+inline int read_input_bits(u8 slave,
+ u16 start_addr,
+ u16 count,
+ u16 *dest,
+ int dest_size,
+ int ttyfd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout,
+ pthread_mutex_t *data_access_mutex) {
+ if( count > MAX_READ_BITS ) {
+ count = MAX_READ_BITS;
+ #ifdef DEBUG
+ fprintf( stderr, "Too many coils requested.\n" );
+ #endif
+ }
+
+ return read_bits(0x02 /* function */,
+ slave, start_addr, count, dest, dest_size, ttyfd,
+ send_retries, error_code, response_timeout, data_access_mutex);
+}
+
+
+
+/* FUNCTION 0x02 - Read Discrete Inputs
+ * Bits are stored on an u32 array, 32 bits per u32.
+ * Unused bits in last u32 are set to 0.
+ */
+inline int read_input_bits_u32(u8 slave,
+ u16 start_addr,
+ u16 count,
+ u32 *dest,
+ int ttyfd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout) {
+ if( count > MAX_READ_BITS ) {
+ count = MAX_READ_BITS;
+ #ifdef DEBUG
+ fprintf( stderr, "Too many coils requested.\n" );
+ #endif
+ }
+
+ return read_bits_u32(0x02 /* function */,
+ slave, start_addr, count, dest, ttyfd,
+ send_retries, error_code, response_timeout);
+}
+
+
+
+
+
+
+/* Execute a transaction for functions that READ REGISTERS.
+ * Called by: read_input_words()
+ * read_output_words()
+ */
+static int read_registers(u8 function,
+ u8 slave,
+ u16 start_addr,
+ u16 count,
+ u16 *dest,
+ int dest_size,
+ int ttyfd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout,
+ pthread_mutex_t *data_access_mutex) {
+ u8 *data;
+ u8 packet[QUERY_BUFFER_SIZE];
+ int response_length;
+ int query_length;
+ int temp,i;
+
+ query_length = build_packet(slave, function, start_addr, count, packet);
+ if (query_length < 0) return INTERNAL_ERROR;
+
+ response_length = mb_transaction(packet, query_length, &data, ttyfd,
+ send_retries, error_code, response_timeout);
+
+ if (response_length < 0) return response_length;
+ if (response_length != 3 + 2*count) return INVALID_FRAME;
+ if (data[2] != 2*count) return INVALID_FRAME;
+
+ if (NULL != data_access_mutex) pthread_mutex_lock(data_access_mutex);
+ for(i = 0; (i < (data[2]*2)) && (i < dest_size); i++ ) {
+ temp = data[3 + i *2] << 8; /* copy reg hi byte to temp hi byte*/
+ temp = temp | data[4 + i * 2]; /* copy reg lo byte to temp lo byte*/
+ dest[i] = temp;
+ }
+ if (NULL != data_access_mutex) pthread_mutex_unlock(data_access_mutex);
+
+ return response_length;
+}
+
+
+
+/* Execute a transaction for functions that READ REGISTERS.
+ * return the array with the data to the calling function
+ * Called by: read_input_words_u16_ref()
+ * read_output_words_u16_ref()
+ */
+
+static int read_registers_u16_ref(u8 function,
+ u8 slave,
+ u16 start_addr,
+ u16 count,
+ u16 **dest,
+ int ttyfd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout) {
+ u8 *data;
+ u8 packet[QUERY_BUFFER_SIZE];
+ int response_length;
+ int query_length;
+ int i, byte_count;
+
+ query_length = build_packet(slave, function, start_addr, count, packet);
+ if (query_length < 0) return INTERNAL_ERROR;
+
+ response_length = mb_transaction(packet, query_length, &data, ttyfd,
+ send_retries, error_code, response_timeout);
+
+ if (response_length < 0) return response_length;
+ if (response_length != 3 + 2*count) return INVALID_FRAME;
+ if (data[2] != 2*count) return INVALID_FRAME;
+
+ byte_count = data[2];
+ data = data + 3; /* & data[3] */
+
+ if (ntohs(0x0102) != 0x0102) {
+ /* little endian host... => we need to swap the bytes! */
+ for(i = 0; i < byte_count; i++ ) {
+ /* the following 3 lines result in the two values being exchanged! */
+ data[i ] = data[i] ^ data[i+1];
+ data[i+1] = data[i] ^ data[i+1];
+ data[i ] = data[i] ^ data[i+1];
+ }
+ }
+ *dest = (u16 *)data;
+ return byte_count;
+}
+
+
+
+
+/* Execute a transaction for functions that READ REGISTERS.
+ * u16 registers are stored in array of u32, two registers per u32.
+ * Unused bits of last u32 element are set to 0.
+ * Called by: read_input_words_u32()
+ * read_output_words_u32()
+ */
+static int read_registers_u32(u8 function,
+ u8 slave,
+ u16 start_addr,
+ u16 count,
+ u32 *dest,
+ int ttyfd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout) {
+ u8 *data;
+ u8 packet[QUERY_BUFFER_SIZE];
+ int response_length;
+ int query_length;
+ int i, byte_count, dest_pos;
+
+ query_length = build_packet(slave, function, start_addr, count, packet);
+ if (query_length < 0) return INTERNAL_ERROR;
+
+ response_length = mb_transaction(packet, query_length, &data, ttyfd,
+ send_retries, error_code, response_timeout);
+
+ if (response_length < 0) return response_length;
+ if (response_length != 3 + 2*count) return INVALID_FRAME;
+ if (data[2] != 2*count) return INVALID_FRAME;
+
+ byte_count = data[2];
+ data += 3;
+
+ if (ntohs(0x0102) == 0x0102) {
+ /* big endian host... */
+ /* handle groups of 4 bytes... */
+ for(i = 0, dest_pos = 0; i + 3 < byte_count; i += 4, dest_pos++) {
+ *(((u8 *)(dest + dest_pos))+ 0) = *(data+i+3);
+ *(((u8 *)(dest + dest_pos))+ 1) = *(data+i+4);
+ *(((u8 *)(dest + dest_pos))+ 2) = *(data+i+0);
+ *(((u8 *)(dest + dest_pos))+ 3) = *(data+i+1);
+ }
+ /* handle any remaining bytes...
+ * since byte_count is supposed to be multiple of 2,
+ * (and has already been verified above 'if (data[2] != 2*count)')
+ * this will be either 2, or none at all!
+ */
+ if (i + 1 < byte_count)
+ *(((u8 *)(dest + dest_pos))+ 0) = 0;
+ *(((u8 *)(dest + dest_pos))+ 1) = 0;
+ *(((u8 *)(dest + dest_pos))+ 2) = *(data+i+0);
+ *(((u8 *)(dest + dest_pos))+ 3) = *(data+i+1);
+ } else {
+ /* little endian host... */
+ /* handle groups of 4 bytes... */
+ for(i = 0, dest_pos = 0; i + 3 < byte_count; i += 4, dest_pos++) {
+ *(((u8 *)(dest + dest_pos))+ 0) = *(data+i+1);
+ *(((u8 *)(dest + dest_pos))+ 1) = *(data+i+0);
+ *(((u8 *)(dest + dest_pos))+ 2) = *(data+i+3);
+ *(((u8 *)(dest + dest_pos))+ 3) = *(data+i+2);
+ }
+ /* handle any remaining bytes...
+ * since byte_count is supposed to be multiple of 2,
+ * (and has already been verified above 'if (data[2] != 2*count)')
+ * this will be either 2, or none at all!
+ */
+ if (i + 1 < byte_count)
+ *(((u8 *)(dest + dest_pos))+ 0) = *(data+i+1);
+ *(((u8 *)(dest + dest_pos))+ 1) = *(data+i+0);
+ *(((u8 *)(dest + dest_pos))+ 2) = 0;
+ *(((u8 *)(dest + dest_pos))+ 3) = 0;
+ }
+
+ return response_length;
+}
+
+
+
+
+
+
+
+/* FUNCTION 0x03 - Read Holding Registers */
+inline int read_output_words(u8 slave,
+ u16 start_addr,
+ u16 count,
+ u16 *dest,
+ int dest_size,
+ int ttyfd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout,
+ pthread_mutex_t *data_access_mutex) {
+ if( count > MAX_READ_REGS ) {
+ count = MAX_READ_REGS;
+ #ifdef DEBUG
+ fprintf( stderr, "Too many registers requested.\n" );
+ #endif
+ }
+
+ return read_registers(0x03 /* function */,
+ slave, start_addr, count, dest, dest_size, ttyfd,
+ send_retries, error_code, response_timeout, data_access_mutex);
+}
+
+
+
+
+/* FUNCTION 0x03 - Read Holding Registers
+ * u16 registers are stored in array of u32, two registers per u32.
+ * Unused bits of last u32 element are set to 0.
+ */
+inline int read_output_words_u32(u8 slave,
+ u16 start_addr,
+ u16 count,
+ u32 *dest,
+ int ttyfd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout) {
+ if( count > MAX_READ_REGS ) {
+ count = MAX_READ_REGS;
+ #ifdef DEBUG
+ fprintf( stderr, "Too many registers requested.\n" );
+ #endif
+ }
+
+ return read_registers_u32(0x03 /* function */,
+ slave, start_addr, count, dest, ttyfd,
+ send_retries, error_code, response_timeout);
+}
+
+
+
+
+/* FUNCTION 0x03 - Read Holding Registers
+ * return the array with the data to the calling function
+ */
+inline int read_output_words_u16_ref(u8 slave,
+ u16 start_addr,
+ u16 count,
+ u16 **dest,
+ int ttyfd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout) {
+ if( count > MAX_READ_REGS ) {
+ count = MAX_READ_REGS;
+ #ifdef DEBUG
+ fprintf( stderr, "Too many registers requested.\n" );
+ #endif
+ }
+
+ return read_registers_u16_ref(0x03 /* function */,
+ slave, start_addr, count, dest, ttyfd, send_retries,
+ error_code, response_timeout);
+}
+
+
+
+
+/* FUNCTION 0x04 - Read Input Registers */
+inline int read_input_words(u8 slave,
+ u16 start_addr,
+ u16 count,
+ u16 *dest,
+ int dest_size,
+ int ttyfd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout,
+ pthread_mutex_t *data_access_mutex) {
+ if( count > MAX_READ_REGS ) {
+ count = MAX_READ_REGS;
+ #ifdef DEBUG
+ fprintf( stderr, "Too many input registers requested.\n" );
+ #endif
+ }
+
+ return read_registers(0x04 /* function */,
+ slave, start_addr, count, dest, dest_size, ttyfd, send_retries,
+ error_code, response_timeout, data_access_mutex);
+}
+
+
+/* FUNCTION 0x04 - Read Input Registers
+ * u16 registers are stored in array of u32, two registers per u32.
+ * Unused bits of last u32 element are set to 0.
+ */
+inline int read_input_words_u32(u8 slave,
+ u16 start_addr,
+ u16 count,
+ u32 *dest,
+ int ttyfd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout) {
+ if( count > MAX_READ_REGS ) {
+ count = MAX_READ_REGS;
+ #ifdef DEBUG
+ fprintf( stderr, "Too many input registers requested.\n" );
+ #endif
+ }
+
+ return read_registers_u32(0x04 /* function */,
+ slave, start_addr, count, dest, ttyfd, send_retries,
+ error_code, response_timeout);
+}
+
+
+
+
+/* FUNCTION 0x04 - Read Input Registers
+ * return the array with the data to the calling function
+ */
+inline int read_input_words_u16_ref(u8 slave,
+ u16 start_addr,
+ u16 count,
+ u16 **dest,
+ int ttyfd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout) {
+ if( count > MAX_READ_REGS ) {
+ count = MAX_READ_REGS;
+ #ifdef DEBUG
+ fprintf( stderr, "Too many input registers requested.\n" );
+ #endif
+ }
+
+ return read_registers_u16_ref(0x04 /* function */,
+ slave, start_addr, count, dest, ttyfd, send_retries,
+ error_code, response_timeout);
+}
+
+
+
+/* Execute a transaction for functions that WRITE a sinlge BIT.
+ * Called by: write_output_bit()
+ * write_output_word()
+ */
+static int set_single(u8 function,
+ u8 slave,
+ u16 addr,
+ u16 value,
+ int ttyfd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout,
+ pthread_mutex_t *data_access_mutex) {
+ u8 packet[QUERY_BUFFER_SIZE];
+ u8 *data;
+ int query_length, response_length;
+
+ if (NULL != data_access_mutex) pthread_mutex_lock(data_access_mutex);
+ query_length = build_packet(slave, function, addr, value, packet);
+ if (NULL != data_access_mutex) pthread_mutex_unlock(data_access_mutex);
+ if (query_length < 0) return INTERNAL_ERROR;
+
+ response_length = mb_transaction(packet, query_length, &data, ttyfd, send_retries,
+ error_code, response_timeout);
+
+ if (response_length < 0) return response_length;
+ if (response_length != 6) return INVALID_FRAME;
+
+ if ((data[2] != packet[2]) || (data[3] != packet[3]) ||
+ (data[4] != packet[4]) || (data[5] != packet[5]))
+ return INVALID_FRAME;
+
+ return response_length;
+}
+
+
+
+
+
+
+/* FUNCTION 0x05 - Force Single Coil */
+inline int write_output_bit(u8 slave,
+ u16 coil_addr,
+ u16 state,
+ int fd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout,
+ pthread_mutex_t *data_access_mutex) {
+ if (state) state = 0xFF00;
+
+ return set_single(0x05 /* function */,
+ slave, coil_addr, state, fd, send_retries,
+ error_code, response_timeout, data_access_mutex);
+}
+
+
+
+
+
+/* FUNCTION 0x06 - Write Single Register */
+inline int write_output_word(u8 slave,
+ u16 reg_addr,
+ u16 value,
+ int fd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout,
+ pthread_mutex_t *data_access_mutex) {
+ return set_single(0x06 /* function */,
+ slave, reg_addr, value, fd, send_retries,
+ error_code, response_timeout, data_access_mutex);
+}
+
+
+
+
+/* FUNCTION 0x0F - Force Multiple Coils */
+int write_output_bits(u8 slave,
+ u16 start_addr,
+ u16 coil_count,
+ u16 *data,
+ int ttyfd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout,
+ pthread_mutex_t *data_access_mutex) {
+ int byte_count, i;
+ u8 bit;
+ int coil_check = 0;
+ int data_array_pos = 0;
+ int query_length, response_length;
+ u8 packet[QUERY_BUFFER_SIZE];
+ u8 *rdata;
+
+ if( coil_count > MAX_WRITE_COILS ) {
+ coil_count = MAX_WRITE_COILS;
+ #ifdef DEBUG
+ fprintf( stderr, "Writing to too many coils.\n" );
+ #endif
+ }
+
+ query_length = build_packet(slave, 0x0F /* function */,
+ start_addr, coil_count, packet);
+ if (query_length < 0) return INTERNAL_ERROR;
+
+ /* NOTE: Integer division. (count+7)/8 is equivalent to ceil(count/8) */
+ byte_count = (coil_count+7)/8;
+ packet[query_length] = byte_count;
+
+ if (NULL != data_access_mutex) pthread_mutex_lock(data_access_mutex);
+ bit = 0x01;
+ for(i = 0; i < byte_count; i++) {
+ packet[++query_length] = 0;
+ while((bit & 0xFF) && (coil_check++ < coil_count)) {
+ if(data[data_array_pos++]) {packet[query_length] |= bit;}
+ else {packet[query_length] &= ~bit;}
+ bit <<= 1;
+ }
+ bit = 0x01;
+ }
+ if (NULL != data_access_mutex) pthread_mutex_unlock(data_access_mutex);
+
+ response_length = mb_transaction(packet, ++query_length, &rdata, ttyfd, send_retries,
+ error_code, response_timeout);
+
+ if (response_length < 0) return response_length;
+ if (response_length != 6) return INVALID_FRAME;
+ if ((rdata[2] != packet[2]) ||
+ (rdata[3] != packet[3]) ||
+ (rdata[4] != packet[4]) ||
+ (rdata[5] != packet[5])) return INVALID_FRAME;
+
+ return response_length;
+}
+
+
+
+/* FUNCTION 0x0F - Force Multiple Coils
+ * Bits should be stored on an u32 array, 32 bits per u32.
+ * Unused bits in last u32 should be set to 0.
+ */
+int write_output_bits_u32(u8 slave,
+ u16 start_addr,
+ u16 coil_count,
+ u32 *data,
+ int ttyfd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout) {
+ int org_pos, byte_count, i;
+ int query_length, response_length;
+ u8 packet[QUERY_BUFFER_SIZE];
+ u8 *rdata;
+
+ if( coil_count > MAX_WRITE_COILS ) {
+ coil_count = MAX_WRITE_COILS;
+ #ifdef DEBUG
+ fprintf( stderr, "Writing to too many coils.\n" );
+ #endif
+ }
+
+ query_length = build_packet(slave, 0x0F /* function */,
+ start_addr, coil_count, packet);
+ if (query_length < 0) return INTERNAL_ERROR;
+
+ /* NOTE: Integer division. This is equivalent of determining the ceil(count/8) */
+ byte_count = (coil_count+7)/8;
+ packet[query_length] = byte_count;
+
+ /* handle groups of 4 bytes... */
+ for(i = 0, org_pos = 0; i + 3 < byte_count; i += 4, org_pos++) {
+ packet[++query_length] = data[org_pos] & 0xFF; data[org_pos] >>= 8;
+ packet[++query_length] = data[org_pos] & 0xFF; data[org_pos] >>= 8;
+ packet[++query_length] = data[org_pos] & 0xFF; data[org_pos] >>= 8;
+ packet[++query_length] = data[org_pos] & 0xFF;
+ }
+ /* handle any remaining bytes... */
+ for(; i < byte_count; i++) {
+ packet[++query_length] = data[org_pos] & 0xFF;
+ data[org_pos] >>= 8;
+ }
+
+ response_length = mb_transaction(packet, ++query_length, &rdata, ttyfd, send_retries,
+ error_code, response_timeout);
+
+ if (response_length < 0) return response_length;
+ if (response_length != 6) return INVALID_FRAME;
+ if ((rdata[2] != packet[2]) ||
+ (rdata[3] != packet[3]) ||
+ (rdata[4] != packet[4]) ||
+ (rdata[5] != packet[5])) return INVALID_FRAME;
+
+ return response_length;
+}
+
+
+
+
+
+/* FUNCTION 0x10 - Force Multiple Registers */
+int write_output_words(u8 slave,
+ u16 start_addr,
+ u16 reg_count,
+ u16 *data,
+ int ttyfd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout,
+ pthread_mutex_t *data_access_mutex) {
+ u8 byte_count;
+ int i, query_length, response_length;
+ u8 packet[QUERY_BUFFER_SIZE];
+ u8 *rdata;
+
+ if( reg_count > MAX_WRITE_REGS ) {
+ reg_count = MAX_WRITE_REGS;
+ #ifdef DEBUG
+ fprintf( stderr, "Trying to write to too many registers.\n" );
+ #endif
+ }
+
+ query_length = build_packet(slave, 0x10 /* function */,
+ start_addr, reg_count, packet);
+ if (query_length < 0) return INTERNAL_ERROR;
+
+ byte_count = reg_count*2;
+ packet[query_length] = byte_count;
+
+ if (NULL != data_access_mutex) pthread_mutex_lock(data_access_mutex);
+ for( i = 0; i < reg_count; i++ ) {
+ packet[++query_length] = data[i] >> 8;
+ packet[++query_length] = data[i] & 0x00FF;
+ }
+ if (NULL != data_access_mutex) pthread_mutex_unlock(data_access_mutex);
+
+ response_length = mb_transaction(packet, ++query_length, &rdata, ttyfd, send_retries,
+ error_code, response_timeout);
+
+ if (response_length < 0) return response_length;
+ if (response_length != 6) return INVALID_FRAME;
+ if ((rdata[2] != packet[2]) ||
+ (rdata[3] != packet[3]) ||
+ (rdata[4] != packet[4]) ||
+ (rdata[5] != packet[5])) return INVALID_FRAME;
+
+ return response_length;
+}
+
+
+
+
+/* FUNCTION 0x10 - Force Multiple Registers
+ * u16 registers are stored in array of u32, two registers per u32.
+ * Unused bits of last u32 element are set to 0.
+ */
+int write_output_words_u32(u8 slave,
+ u16 start_addr,
+ /* number of 16 bit registers packed in the u32 array! */
+ u16 reg_count,
+ u32 *data,
+ int ttyfd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout) {
+ u8 byte_count;
+ int i, query_length, response_length;
+ u8 packet_[QUERY_BUFFER_SIZE];
+ u8 *packet = packet_; /* remove the const'ness of packet_ */
+ u8 *rdata;
+
+ if( reg_count > MAX_WRITE_REGS ) {
+ reg_count = MAX_WRITE_REGS;
+ #ifdef DEBUG
+ fprintf( stderr, "Trying to write to too many registers.\n" );
+ #endif
+ }
+
+ /* Make sure that the de-referencing and up-casting going on later on in
+ * this function, i.e. code like the following line:
+ * *((u16 *)packet) = XXX
+ * will result in u16 words starting off on even addresses.
+ * If we don't do this, some compilers (e.g. AVR32 cross-compiler) will
+ * generate code which, when executed, will result in 'bus error'.
+ *
+ * The following packet++ means that the first byte of the packet array is
+ * essentially never used. Notice too that the size of thepacket array
+ * already takes into account this un-used byte.
+ */
+ packet++;
+
+ query_length = build_packet(slave, 0x10 /* function */,
+ start_addr, reg_count, packet);
+ if (query_length < 0) return INTERNAL_ERROR;
+
+ byte_count = reg_count*2;
+ packet[query_length] = byte_count;
+
+ /* handle groups of 4 bytes... */
+ for(i = 0; 4*i + 3 < byte_count; i++) {
+ *((u16 *)(packet+(++query_length))) = mb_hton(data[i]); ++query_length;
+ *((u16 *)(packet+(++query_length))) = mb_hton(data[i] >> 16); ++query_length;
+ }
+
+ /* handle any remaining bytes...
+ * since byte_count is supposed to be multiple of 2,
+ * (and has already been verified above 'if (data[2] != 2*count)')
+ * this will be either 2, or none at all!
+ */
+ if (4*i + 1 < byte_count) {
+ *((u16 *)(packet+(++query_length))) = mb_hton(data[i]); ++query_length;
+ }
+
+ response_length = mb_transaction(packet, ++query_length, &rdata, ttyfd, send_retries,
+ error_code, response_timeout);
+
+ if (response_length < 0) return response_length;
+ if (response_length != 6) return INVALID_FRAME;
+ if ((rdata[2] != packet[2]) ||
+ (rdata[3] != packet[3]) ||
+ (rdata[4] != packet[4]) ||
+ (rdata[5] != packet[5])) return INVALID_FRAME;
+
+ return response_length;
+}
+
+
+
+
+
+
+/************************************************/
+/************************************************/
+/** **/
+/** Modbus Library Management Functions. **/
+/** **/
+/************************************************/
+/************************************************/
+
+
+
+/* Initialise the Modbus Master Layer */
+int mb_master_init__(int extra_bytes) {
+ #ifdef DEBUG
+ fprintf(stderr, "mb_master_init__(extra_bytes=%d), QUERY_BUFFER_SIZE=%d\n", extra_bytes, QUERY_BUFFER_SIZE);
+ #endif
+ buff_extra_bytes_ = extra_bytes;
+ return 0;
+}
+
+
+/* Shut down the Modbus Master Layer */
+int mb_master_done__(void) {
+ return 0;
+}
+
+
+#if 0
+int mb_master_init(int nd_count) {
+ int extra_bytes;
+
+ #ifdef DEBUG
+ fprintf( stderr, "mb_master_init()\n");
+ fprintf( stderr, "creating %d nodes\n", nd_count);
+ #endif
+
+ /* initialise layer 1 library */
+ if (modbus_init(nd_count, DEF_OPTIMIZATION, &extra_bytes) < 0)
+ goto error_exit_0;
+
+ /* initialise this library */
+ if (mb_master_init__(extra_bytes) < 0)
+ goto error_exit_1;
+
+ return 0;
+
+error_exit_1:
+ modbus_done();
+error_exit_0:
+ return -1;
+}
+
+
+int mb_master_done(void) {
+ mb_master_done__();
+ return modbus_done();
+}
+#endif
+
+
+/* Establish a connection to a remote server/slave */
+/* NOTE: We use the lower 2 bits of the returned node id to identify which
+ * layer1 implementation to use.
+ * 0 -> TCP
+ * 1 -> RTU
+ * 2 -> ASCII
+ * 4 -> unused
+ * The node id used by the layer1 is shifted left 2 bits
+ * before returning the node id to the caller!
+ */
+int mb_master_connect(node_addr_t node_addr) {
+ int res = -1;
+
+ #ifdef DEBUG
+ fprintf( stderr, "mb_master_tcp connect()\n");
+ #endif
+
+ /* call layer 1 library */
+ switch(node_addr.naf) {
+ case naf_tcp:
+ res = modbus_tcp_connect(node_addr);
+ if (res >= 0) res = res*4 + 0 /* offset into fptr_ with TCP functions */;
+ return res;
+ case naf_rtu:
+ res = modbus_rtu_connect(node_addr);
+ if (res >= 0) res = res*4 + 1 /* offset into fptr_ with RTU functions */;
+ return res;
+ case naf_ascii:
+ res = modbus_ascii_connect(node_addr);
+ if (res >= 0) res = res*4 + 2 /* offset into fptr_ with ASCII functions */;
+ return res;
+ }
+
+ return -1;
+}
+
+
+
+
+
+/* Shut down a connection to a remote server/slave */
+int mb_master_close(int fd) {
+ #ifdef DEBUG
+ fprintf( stderr, "mb_master_close(): nd = %d\n", fd);
+ #endif
+ get_ttyfd(); /* declare the ttyfd variable, ... */
+ /* call layer 1 library */
+ return modbus_close(ttyfd);
+}
+
+
+
+
+
+
+/* Tell the library that communications will be suspended for some time. */
+/* RTU and ASCII versions ignore this function
+ * TCP version closes all the open tcp connections (connections are automatically
+ * re-established the next time an IO function to the slave is requested).
+ * To be more precise, the TCP version makes an estimate of how long
+ * the silence will be based on previous invocations to this exact same
+ * function, and will only close the connections if this silence is
+ * expected to be longer than 1 second!
+ * (The closing of connections is specified in Modbus specification)
+ */
+int mb_master_tcp_silence_init(void) {
+ #ifdef DEBUG
+ fprintf( stderr, "mb_master_silence_init():\n");
+ #endif
+ /* call layer 1 library */
+ return modbus_tcp_silence_init();
+}
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mb_master.h Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,359 @@
+/*
+ * Copyright (c) 2001-2003,2016 Mario de Sousa (msousa@fe.up.pt)
+ *
+ * This file is part of the Modbus library for Beremiz and matiec.
+ *
+ * This Modbus library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this Modbus library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is made available on the understanding that it will not be
+ * used in safety-critical situations without a full and competent review.
+ */
+
+
+/* mb_master.h */
+
+
+#ifndef MODBUS_MASTER_H
+#define MODBUS_MASTER_H
+
+#include <time.h> /* struct timespec data structure */
+
+#include "mb_types.h" /* get the data types */
+#include "mb_addr.h" /* get definition of common variable types and error codes */
+
+
+
+/***********************************************************************
+
+ Note: All functions used for sending or receiving data via
+ modbus return these return values.
+
+
+ Returns: string_length if OK
+ -1 on internal error or port failure
+ -2 on timeout
+ -3 if a valid yet un-expected frame is received!
+ -4 for modbus exception errors
+ (in this case exception code is returned in *error_code)
+
+***********************************************************************/
+
+
+/* FUNCTION 0x01 - Read Coils
+ * Bits are stored on an int array, one bit per int.
+ */
+inline int read_output_bits(u8 slave,
+ u16 start_addr,
+ u16 count,
+ u16 *dest,
+ int dest_size,
+ int fd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout,
+ pthread_mutex_t *data_access_mutex);
+#define read_coils(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10) \
+ read_output_bits(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10)
+
+
+/* FUNCTION 0x01 - Read Coils
+ * Bits are stored on an u32 array, 32 bits per u32.
+ * Unused bits in last u32 are set to 0.
+ */
+inline int read_output_bits_u32(u8 slave,
+ u16 start_addr,
+ u16 count,
+ u32 *dest,
+ int fd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout);
+#define read_coils_u32(p1,p2,p3,p4,p5,p6,p7,p8) \
+ read_output_bits_u32(p1,p2,p3,p4,p5,p6,p7,p8)
+
+
+
+
+/* FUNCTION 0x02 - Read Discrete Inputs
+ * Bits are stored on an int array, one bit per int.
+ */
+inline int read_input_bits(u8 slave,
+ u16 start_addr,
+ u16 count,
+ u16 *dest,
+ int dest_size,
+ int fd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout,
+ pthread_mutex_t *data_access_mutex);
+#define read_discrete_inputs(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10) \
+ read_input_bits (p1,p2,p3,p4,p5,p6,p7,p8,p9,p10)
+
+
+/* FUNCTION 0x02 - Read Discrete Inputs
+ * Bits are stored on an u32 array, 32 bits per u32.
+ * Unused bits in last u32 are set to 0.
+ */
+inline int read_input_bits_u32(u8 slave,
+ u16 start_addr,
+ u16 count,
+ u32 *dest,
+ int fd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout);
+#define read_discrete_inputs_u32(p1,p2,p3,p4,p5,p6,p7,p8) \
+ read_input_bits_u32 (p1,p2,p3,p4,p5,p6,p7,p8)
+
+
+
+
+/* FUNCTION 0x03 - Read Holding Registers */
+inline int read_output_words(u8 slave,
+ u16 start_addr,
+ u16 count,
+ u16 *dest,
+ int dest_size,
+ int fd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout,
+ pthread_mutex_t *data_access_mutex);
+#define read_holding_registers(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10) \
+ read_output_words (p1,p2,p3,p4,p5,p6,p7,p8,p9,p10)
+
+
+/* FUNCTION 0x03 - Read Holding Registers
+ * u16 registers are stored in array of u32, two registers per u32.
+ * Unused bits of last u32 element are set to 0.
+ */
+inline int read_output_words_u32(u8 slave,
+ u16 start_addr,
+ u16 count,
+ u32 *dest,
+ int fd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout);
+#define read_holding_registers_u32(p1,p2,p3,p4,p5,p6,p7,p8) \
+ read_output_words_u32 (p1,p2,p3,p4,p5,p6,p7,p8)
+
+
+/* FUNCTION 0x03 - Read Holding Registers
+ * return the array with the data to the calling function
+ */
+inline int read_output_words_u16_ref(u8 slave,
+ u16 start_addr,
+ u16 count,
+ u16 **dest,
+ int ttyfd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout);
+#define read_holding_registers_u16_ref(p1,p2,p3,p4,p5,p6,p7,p8) \
+ read_output_words_u16_ref (p1,p2,p3,p4,p5,p6,p7,p8)
+
+
+
+/* FUNCTION 0x04 - Read Input Registers */
+inline int read_input_words(u8 slave,
+ u16 start_addr,
+ u16 count,
+ u16 *dest,
+ int dest_size,
+ int fd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout,
+ pthread_mutex_t *data_access_mutex);
+#define read_input_registers(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10) \
+ read_input_words (p1,p2,p3,p4,p5,p6,p7,p8,p9,p10)
+
+
+
+/* FUNCTION 0x04 - Read Input Registers
+ * u16 registers are stored in array of u32, two registers per u32.
+ * Unused bits of last u32 element are set to 0.
+ */
+inline int read_input_words_u32(u8 slave,
+ u16 start_addr,
+ u16 count,
+ u32 *dest,
+ int fd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout);
+#define read_input_registers_u32(p1,p2,p3,p4,p5,p6,p7,p8) \
+ read_input_words_u32 (p1,p2,p3,p4,p5,p6,p7,p8)
+
+
+
+/* FUNCTION 0x04 - Read Input Registers
+ * return the array with the data to the calling function
+ */
+inline int read_input_words_u16_ref(u8 slave,
+ u16 start_addr,
+ u16 count,
+ u16 **dest,
+ int ttyfd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout);
+#define read_input_registers_u16_ref(p1,p2,p3,p4,p5,p6,p7,p8) \
+ read_input_words_u16_ref (p1,p2,p3,p4,p5,p6,p7,p8)
+
+
+
+
+/* FUNCTION 0x05 - Force Single Coil */
+inline int write_output_bit(u8 slave,
+ u16 coil_addr,
+ u16 state,
+ int fd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout,
+ pthread_mutex_t *data_access_mutex);
+#define force_single_coil(p1,p2,p3,p4,p5,p6,p7,p8) \
+ write_output_bit (p1,p2,p3,p4,p5,p6,p7,p8)
+
+
+
+
+
+
+/* FUNCTION 0x06 - Write Single Register */
+inline int write_output_word(u8 slave,
+ u16 reg_addr,
+ u16 value,
+ int fd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout,
+ pthread_mutex_t *data_access_mutex);
+#define write_single_register(p1,p2,p3,p4,p5,p6,p7,p8) \
+ write_output_word (p1,p2,p3,p4,p5,p6,p7,p8)
+
+
+
+
+
+/* FUNCTION 0x0F - Force Multiple Coils */
+int write_output_bits(u8 slave,
+ u16 start_addr,
+ u16 coil_count,
+ u16 *data,
+ int fd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout,
+ pthread_mutex_t *data_access_mutex);
+#define force_multiple_coils(p1,p2,p3,p4,p5,p6,p7,p8,p9) \
+ write_output_bits (p1,p2,p3,p4,p5,p6,p7,p8,p9)
+
+
+/* FUNCTION 0x0F - Force Multiple Coils
+ * Bits should be stored on an u32 array, 32 bits per u32.
+ * Unused bits in last u32 should be set to 0.
+ */
+int write_output_bits_u32(u8 slave,
+ u16 start_addr,
+ u16 coil_count,
+ u32 *data,
+ int fd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout);
+#define force_multiple_coils_u32(p1,p2,p3,p4,p5,p6,p7,p8) \
+ write_output_bits_u32 (p1,p2,p3,p4,p5,p6,p7,p8)
+
+
+
+/* FUNCTION 0x10 - Force Multiple Registers */
+int write_output_words(u8 slave,
+ u16 start_addr,
+ u16 reg_count,
+ u16 *data,
+ int fd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout,
+ pthread_mutex_t *data_access_mutex);
+#define force_multiple_registers(p1,p2,p3,p4,p5,p6,p7,p8,p9) \
+ write_output_words (p1,p2,p3,p4,p5,p6,p7,p8,p9)
+
+
+
+/* FUNCTION 0x10 - Force Multiple Registers
+ * u16 registers are stored in array of u32, two registers per u32.
+ * Unused bits of last u32 element are set to 0.
+ */
+int write_output_words_u32(u8 slave,
+ u16 start_addr,
+ u16 reg_count,
+ u32 *data,
+ int fd,
+ int send_retries,
+ u8 *error_code,
+ const struct timespec *response_timeout);
+
+#define force_multiple_registers_u32(p1,p2,p3,p4,p5,p6,p7,p8) \
+ write_output_words_u32 (p1,p2,p3,p4,p5,p6,p7,p8)
+
+
+
+
+
+
+/* Initialise the Modbus Library to work as Master only */
+int mb_master_init(int nd_count);
+/* Shut down the Modbus Library */
+int mb_master_done(void);
+
+
+
+/* Establish a connection to a remote server/slave.
+ * The address type (naf_tcp, naf_rtu, naf_ascii) specifies the lower
+ * layer to use for the newly opened node.
+ */
+int mb_master_connect(node_addr_t node_addr);
+/* Shut down a connection to a remote server/slave */
+int mb_master_close(int nd);
+
+
+
+
+/* Tell the library that communications will be suspended for some time. */
+/* RTU and ASCII versions ignore this function
+ * TCP version closes all the open tcp connections (connections are automatically
+ * re-established the next time an IO function to the slave is requested).
+ * To be more precise, the TCP version makes an estimate of how long
+ * the silence will be based on previous invocations to this exact same
+ * function, and will only close the connections if this silence is
+ * expected to be longer than 1 second!
+ */
+int mb_master_tcp_silence_init(void);
+
+
+
+#endif /* MODBUS_MASTER_H */
+
+
+
+
+
+
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mb_master_private.h Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2002,2016 Mario de Sousa (msousa@fe.up.pt)
+ *
+ * This file is part of the Modbus library for Beremiz and matiec.
+ *
+ * This Modbus library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this Modbus library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is made available on the understanding that it will not be
+ * used in safety-critical situations without a full and competent review.
+ */
+
+
+
+#ifndef MODBUS_MASTER_PRIVATE_H
+#define MODBUS_MASTER_PRIVATE_H
+
+#include "mb_util.h"
+#include "mb_master.h"
+
+
+
+
+#define DEF_LAYER2_SEND_RETRIES 1
+
+#define DEF_IGNORE_ECHO 0
+
+#define DEF_OPTIMIZATION optimize_speed
+
+
+int mb_master_init__(int extra_bytes);
+int mb_master_done__(void);
+
+
+#endif /* MODBUS_MASTER_PRIVATE_H */
+
+
+
+
+
+
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mb_rtu.c Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,2116 @@
+/*
+ * Copyright (c) 2001,2016 Mario de Sousa (msousa@fe.up.pt)
+ *
+ * This file is part of the Modbus library for Beremiz and matiec.
+ *
+ * This Modbus library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this Modbus library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is made available on the understanding that it will not be
+ * used in safety-critical situations without a full and competent review.
+ */
+
+
+
+#include <fcntl.h> /* File control definitions */
+#include <stdio.h> /* Standard input/output */
+#include <string.h>
+#include <stdlib.h>
+#include <termio.h> /* POSIX terminal control definitions */
+#include <sys/time.h> /* Time structures for select() */
+#include <unistd.h> /* POSIX Symbolic Constants */
+#include <assert.h>
+#include <errno.h> /* Error definitions */
+#include <time.h> /* clock_gettime() */
+#include <limits.h> /* required for INT_MAX */
+
+#include <netinet/in.h> /* required for htons() and ntohs() */
+
+#include "mb_layer1.h" /* The public interface this file implements... */
+#include "mb_rtu_private.h"
+
+
+#define ERRMSG
+#define ERRMSG_HEAD "ModbusRTU: "
+
+// #define DEBUG /* uncomment to see the data sent and received */
+
+#ifdef DEBUG
+#ifndef ERRMSG
+#define ERRMSG
+#endif
+#endif
+
+
+#define SAFETY_MARGIN 10
+
+/************************************/
+/** **/
+/** Include common code... **/
+/** **/
+/************************************/
+
+#include "mb_ds_util.h" /* data structures... */
+#include "mb_time_util.h" /* time conversion routines... */
+
+
+
+/**************************************************************/
+/**************************************************************/
+/**** ****/
+/**** ****/
+/**** Forward Declarations ****/
+/**** and Defaults ****/
+/**** ****/
+/**************************************************************/
+/**************************************************************/
+
+ /* CRC funtions... */
+typedef u16 (*crc_func_t)(u8 *buf, int cnt);
+static u16 crc_slow(u8 *buf, int cnt);
+static u16 crc_fast(u8 *buf, int cnt);
+
+ /* slow version does not need to be initialised, so we use it as default. */
+#define DEF_CRC_FUNCTION crc_slow
+
+
+/**************************************************************/
+/**************************************************************/
+/**** ****/
+/**** ****/
+/**** Local Utility functions... ****/
+/**** ****/
+/**** ****/
+/**************************************************************/
+/**************************************************************/
+
+/************************************/
+/** **/
+/** Miscelaneous Utility functions **/
+/** **/
+/************************************/
+
+/*
+ * Functions to convert u16 variables
+ * between network and host byte order
+ *
+ * NOTE: Modbus uses MSByte first, just like
+ * tcp/ip, so we use the htons() and
+ * ntoh() functions to guarantee
+ * code portability.
+ */
+static inline u16 mb_hton(u16 h_value)
+ {return htons(h_value);} /* return h_value; */
+
+static inline u16 mb_ntoh(u16 m_value)
+ {return ntohs(m_value);} /* return m_value; */
+
+/* return Most Significant Byte of value; */
+static inline u8 msb(u16 value)
+ {return (value >> 8) & 0xFF;}
+
+/* return Least Significant Byte of value; */
+static inline u8 lsb(u16 value)
+ {return value & 0xFF;}
+
+#define u16_v(char_ptr) (*((u16 *)(&(char_ptr))))
+
+
+
+/**************************************/
+/** **/
+/** Initialise a termios struct **/
+/** **/
+/**************************************/
+static int termios_init(struct termios *tios,
+ int baud,
+ int parity,
+ int data_bits,
+ int stop_bits) {
+ speed_t baud_rate;
+
+ if (tios == NULL)
+ return -1;
+
+ /* reset all the values... */
+ /* NOTE: the following are initialised later on...
+ tios->c_iflag = 0;
+ tios->c_oflag = 0;
+ tios->c_cflag = 0;
+ tios->c_lflag = 0;
+ */
+ tios->c_line = 0;
+
+ /* The minimum number of characters that should be received
+ * to satisfy a call to read().
+ */
+ tios->c_cc[VMIN ] = 0;
+
+ /* The maximum inter-arrival interval between two characters,
+ * in deciseconds.
+ *
+ * NOTE: we could use this to detect the end of RTU frames,
+ * but we prefer to use select() that has higher resolution,
+ * even though this higher resolution is most probably not
+ * supported, and the effective resolution is 10ms,
+ * one tenth of a decisecond.
+ */
+ tios->c_cc[VTIME] = 0;
+
+ /* configure the input modes... */
+ tios->c_iflag = IGNBRK | /* ignore BREAK condition on input */
+ IGNPAR | /* ignore framing errors and parity errors */
+ IXANY; /* enable any character to restart output */
+ /* BRKINT Only active if IGNBRK is not set.
+ * generate SIGINT on BREAK condition,
+ * otherwise read BREAK as character \0.
+ * PARMRK Only active if IGNPAR is not set.
+ * replace bytes with parity errors with
+ * \377 \0, instead of \0.
+ * INPCK enable input parity checking
+ * ISTRIP strip off eighth bit
+ * IGNCR ignore carriage return on input
+ * INLCR only active if IGNCR is not set.
+ * translate newline to carriage return on input
+ * ICRNL only active if IGNCR is not set.
+ * translate carriage return to newline on input
+ * IUCLC map uppercase characters to lowercase on input
+ * IXON enable XON/XOFF flow control on output
+ * IXOFF enable XON/XOFF flow control on input
+ * IMAXBEL ring bell when input queue is full
+ */
+
+ /* configure the output modes... */
+ tios->c_oflag = OPOST; /* enable implementation-defined output processing */
+ /* ONOCR don't output CR at column 0
+ * OLCUC map lowercase characters to uppercase on output
+ * ONLCR map NL to CR-NL on output
+ * OCRNL map CR to NL on output
+ * OFILL send fill characters for a delay, rather than
+ * using a timed delay
+ * OFDEL fill character is ASCII DEL. If unset, fill
+ * character is ASCII NUL
+ * ONLRET don't output CR
+ * NLDLY NL delay mask. Values are NL0 and NL1.
+ * CRDLY CR delay mask. Values are CR0, CR1, CR2, or CR3.
+ * TABDLY horizontal tab delay mask. Values are TAB0, TAB1,
+ * TAB2, TAB3, or XTABS. A value of XTABS expands
+ * tabs to spaces (with tab stops every eight columns).
+ * BSDLY backspace delay mask. Values are BS0 or BS1.
+ * VTDLY vertical tab delay mask. Values are VT0 or VT1.
+ * FFDLY form feed delay mask. Values are FF0 or FF1.
+ */
+
+ /* configure the control modes... */
+ tios->c_cflag = CREAD | /* enable receiver. */
+ CLOCAL; /* ignore modem control lines */
+ /* HUPCL lower modem control lines after last process
+ * closes the device (hang up).
+ * CRTSCTS flow control (Request/Clear To Send).
+ */
+ if (data_bits == 5) tios->c_cflag |= CS5;
+ else if (data_bits == 6) tios->c_cflag |= CS6;
+ else if (data_bits == 7) tios->c_cflag |= CS7;
+ else if (data_bits == 8) tios->c_cflag |= CS8;
+ else return -1;
+
+ if (stop_bits == 1) tios->c_cflag &=~ CSTOPB;
+ else if (stop_bits == 2) tios->c_cflag |= CSTOPB;
+ else return -1;
+
+ if(parity == 0) { /* none */
+ tios->c_cflag &=~ PARENB;
+ tios->c_cflag &=~ PARODD;
+ } else if(parity == 2) { /* even */
+ tios->c_cflag |= PARENB;
+ tios->c_cflag &=~ PARODD;
+ } else if(parity == 1) { /* odd */
+ tios->c_cflag |= PARENB;
+ tios->c_cflag |= PARODD;
+ } else return -1;
+
+
+ /* configure the local modes... */
+ tios->c_lflag = IEXTEN; /* enable implementation-defined input processing */
+ /* ISIG when any of the characters INTR, QUIT, SUSP, or DSUSP
+ * are received, generate the corresponding signal.
+ * ICANON enable canonical mode. This enables the special
+ * characters EOF, EOL, EOL2, ERASE, KILL, REPRINT,
+ * STATUS, and WERASE, and buffers by lines.
+ * ECHO echo input characters.
+ */
+
+ /* Set the baud rate */
+ /* Must be done before reseting all the values to 0! */
+ switch(baud) {
+ case 110: baud_rate = B110; break;
+ case 300: baud_rate = B300; break;
+ case 600: baud_rate = B600; break;
+ case 1200: baud_rate = B1200; break;
+ case 2400: baud_rate = B2400; break;
+ case 4800: baud_rate = B4800; break;
+ case 9600: baud_rate = B9600; break;
+ case 19200: baud_rate = B19200; break;
+ case 38400: baud_rate = B38400; break;
+ case 57600: baud_rate = B57600; break;
+ case 115200: baud_rate = B115200; break;
+ default: return -1;
+ } /* switch() */
+
+ if ((cfsetispeed(tios, baud_rate) < 0) ||
+ (cfsetospeed(tios, baud_rate) < 0))
+ return -1;;
+
+ return 0;
+}
+
+
+/************************************/
+/** **/
+/** A data structure - recv buffer **/
+/** **/
+/************************************/
+
+/* A data structutre used for the receive buffer, i.e. the buffer
+ * that stores the bytes we receive from the bus.
+ *
+ * What we realy needed here is an unbounded buffer. This may be
+ * implemented by:
+ * - a circular buffer the size of the maximum frame length
+ * - a linear buffer somewhat larger than the maximum frame length
+ *
+ * Due to the fact that this library's API hands over the frame data
+ * in a linear buffer, and also reads the data (i,e, calls to read())
+ * into a linear buffer:
+ * - the circular buffer would be more efficient in aborted frame
+ * situations
+ * - the linear is more efficient when no aborted frames are recieved.
+ *
+ * I have decided to optimize for the most often encountered situation,
+ * i.e. when no aborted frames are received.
+ *
+ * The linear buffer has a size larger than the maximum
+ * number of bytes we intend to store in it. We simply start ignoring
+ * the first bytes in the buffer in which we are not interested in, and
+ * continue with the extra bytes of the buffer. When we reach the limit
+ * of these extra bytes, we shift the data down so it once again
+ * uses the first bytes of the buffer. The more number of extra bytes,
+ * the more efficient it will be.
+ *
+ * Note that if we don't receive any aborted frames, it will work as a
+ * simple linear buffer, and no memory shifts will be required!
+ */
+
+typedef struct {
+ lb_buf_t data_buf;
+ /* Flag:
+ * 1 => We have detected a frame boundary using 3.5 character silence
+ * 0 => We have not yet detected any frame boundary
+ */
+ int found_frame_boundary; /* ==1 => valid data ends at a frame boundary. */
+ /* Flag:
+ * Used in the call to search_for_frame() as the history parameter!
+ */
+ int frame_search_history;
+ } recv_buf_t;
+
+/* A small auxiliary function... */
+static inline u8 *recv_buf_init(recv_buf_t *buf, int size, int max_data_start) {
+ buf->found_frame_boundary = 0;
+ buf->frame_search_history = 0;
+ return lb_init(&buf->data_buf, size, max_data_start);
+}
+
+
+/* A small auxiliary function... */
+static inline void recv_buf_done(recv_buf_t *buf) {
+ buf->found_frame_boundary = 0;
+ buf->frame_search_history = 0;
+ lb_done(&buf->data_buf);
+}
+
+
+/* A small auxiliary function... */
+static inline void recv_buf_reset(recv_buf_t *buf) {
+ buf->found_frame_boundary = 0;
+ buf->frame_search_history = 0;
+ lb_data_purge_all(&buf->data_buf);
+}
+
+
+/************************************/
+/** **/
+/** A data structure - nd entry **/
+/** **/
+/************************************/
+
+/* NOTE: nd = node descriptor */
+
+typedef struct {
+ /* The file descriptor associated with this node */
+ /* NOTE: if the node is not yet in use, i.e. if the node is free,
+ * then fd will be set to -1
+ */
+ int fd;
+
+ /* the time it takes to transmit 1.5 characters at the current baud rate */
+ struct timeval time_15_char_;
+ /* the time it takes to transmit 3.5 characters at the current baud rate */
+ struct timeval time_35_char_;
+
+ /* Due to the algorithm used to work around aborted frames, the modbus_read()
+ * function might read beyond the current modbus frame. The extra bytes
+ * must be stored for the subsequent call to modbus_read().
+ */
+ recv_buf_t recv_buf_;
+
+ /* The old settings of the serial port, to be reset when the library is closed... */
+ struct termios old_tty_settings_;
+
+ /* ignore echo flag.
+ * If set to 1, then it means that we will be reading every byte we
+ * ourselves write out to the bus, so we must ignore those bytes read
+ * before we really read the data sent by remote nodes.
+ *
+ * This comes in useful when using a RS232-RS485 converter that does
+ * not correctly control the RTS-CTS lines...
+ */
+ int ignore_echo;
+ } nd_entry_t;
+
+
+static inline void nd_entry_init(nd_entry_t *nde) {
+ nde->fd = -1; /* The node is free... */
+}
+
+
+
+static int nd_entry_connect(nd_entry_t *nde,
+ node_addr_t *node_addr,
+ optimization_t opt) {
+
+ int parity_bits, start_bits, char_bits;
+ struct termios settings;
+ int buf_size;
+
+ /*
+ if (nde == NULL)
+ goto error_exit_0;
+ */
+ if (nde->fd >= 0)
+ goto error_exit_0;
+
+ /* initialise the termios data structure */
+ if (termios_init(&settings,
+ node_addr->addr.rtu.baud,
+ node_addr->addr.rtu.parity,
+ node_addr->addr.rtu.data_bits,
+ node_addr->addr.rtu.stop_bits)
+ < 0) {
+#ifdef ERRMSG
+ fprintf(stderr, ERRMSG_HEAD "Invalid serial line settings"
+ "(baud=%d, parity=%d, data_bits=%d, stop_bits=%d)\n",
+ node_addr->addr.rtu.baud,
+ node_addr->addr.rtu.parity,
+ node_addr->addr.rtu.data_bits,
+ node_addr->addr.rtu.stop_bits);
+#endif
+ goto error_exit_1;
+ }
+
+ /* set the ignore_echo flag */
+ nde->ignore_echo = node_addr->addr.rtu.ignore_echo;
+
+ /* initialise recv buffer */
+ buf_size = (opt == optimize_size)?RECV_BUFFER_SIZE_SMALL:
+ RECV_BUFFER_SIZE_LARGE;
+ if (recv_buf_init(&nde->recv_buf_, buf_size, buf_size - MAX_RTU_FRAME_LENGTH)
+ == NULL) {
+#ifdef ERRMSG
+ fprintf(stderr, ERRMSG_HEAD "Out of memory: error initializing receive buffer\n");
+#endif
+ goto error_exit_2;
+ }
+
+ /* open the serial port */
+ if((nde->fd = open(node_addr->addr.rtu.device, O_RDWR | O_NOCTTY | O_NDELAY))
+ < 0) {
+#ifdef ERRMSG
+ perror("open()");
+ fprintf(stderr, ERRMSG_HEAD "Error opening device %s\n",
+ node_addr->addr.rtu.device);
+#endif
+ goto error_exit_3;
+ }
+
+ if(tcgetattr(nde->fd, &nde->old_tty_settings_) < 0) {
+#ifdef ERRMSG
+ perror("tcgetattr()");
+ fprintf(stderr, ERRMSG_HEAD "Error reading device's %s original settings.\n",
+ node_addr->addr.rtu.device);
+#endif
+ goto error_exit_4;
+ }
+
+ if(tcsetattr(nde->fd, TCSANOW, &settings) < 0) {
+#ifdef ERRMSG
+ perror("tcsetattr()");
+ fprintf(stderr, ERRMSG_HEAD "Error configuring device %s "
+ "(baud=%d, parity=%d, data_bits=%d, stop_bits=%d)\n",
+ node_addr->addr.rtu.device,
+ node_addr->addr.rtu.baud,
+ node_addr->addr.rtu.parity,
+ node_addr->addr.rtu.data_bits,
+ node_addr->addr.rtu.stop_bits);
+#endif
+ goto error_exit_4;
+ }
+
+ parity_bits = (node_addr->addr.rtu.parity == 0)?0:1;
+ start_bits = 1;
+ char_bits = start_bits + node_addr->addr.rtu.data_bits +
+ parity_bits + node_addr->addr.rtu.stop_bits;
+ nde->time_15_char_ = d_to_timeval(SAFETY_MARGIN*1.5*char_bits/node_addr->addr.rtu.baud);
+ nde->time_35_char_ = d_to_timeval(SAFETY_MARGIN*3.5*char_bits/node_addr->addr.rtu.baud);
+
+#ifdef DEBUG
+ fprintf(stderr, "nd_entry_connect(): %s ope{.node=NULL, .node_count=0};n\n", node_addr->addr.rtu.device );
+ fprintf(stderr, "nd_entry_connect(): returning fd=%d\n", nde->fd);
+#endif
+ return nde->fd;
+
+ error_exit_4:
+ close(nde->fd);
+ error_exit_3:
+ recv_buf_done(&nde->recv_buf_);
+ error_exit_2:
+ error_exit_1:
+ nde->fd = -1; /* set the node as free... */
+ error_exit_0:
+ return -1;
+}
+
+
+
+static int nd_entry_free(nd_entry_t *nde) {
+ if (nde->fd < 0)
+ /* already free */
+ return -1;
+
+ /* reset the tty device old settings... */
+#ifdef ERRMSG
+ int res =
+#endif
+ tcsetattr(nde->fd, TCSANOW, &nde->old_tty_settings_);
+#ifdef ERRMSG
+ if(res < 0)
+ fprintf(stderr, ERRMSG_HEAD "Error reconfiguring serial port to it's original settings.\n");
+#endif
+
+ recv_buf_done(&nde->recv_buf_);
+ close(nde->fd);
+ nde->fd = -1;
+
+ return 0;
+}
+
+
+
+
+static inline int nd_entry_is_free(nd_entry_t *nde) {
+ return (nde->fd < 0);
+}
+
+
+
+
+/************************************/
+/** **/
+/** A data structure - nd table **/
+/** **/
+/************************************/
+
+typedef struct {
+ /* the array of node descriptors, and current size... */
+ nd_entry_t *node;
+ int node_count; /* total number of nodes in the node[] array */
+} nd_table_t;
+
+
+#if 1
+/* nd_table_init()
+ * Version 1 of the nd_table_init() function.
+ * If called more than once, 2nd and any subsequent calls will
+ * be interpreted as a request to confirm that it was already correctly
+ * initialized with the requested number of nodes.
+ */
+static int nd_table_init(nd_table_t *ndt, int nd_count) {
+ int count;
+
+ if (ndt->node != NULL) {
+ /* this function has already been called, and the node table is already initialised */
+ return (ndt->node_count == nd_count)?0:-1;
+ }
+
+ /* initialise the node descriptor metadata array... */
+ ndt->node = malloc(sizeof(nd_entry_t) * nd_count);
+ if (ndt->node == NULL) {
+#ifdef ERRMSG
+ fprintf(stderr, ERRMSG_HEAD "Out of memory: error initializing node address buffer\n");
+#endif
+ return -1;
+ }
+ ndt->node_count = nd_count;
+
+ /* initialise the state of each node in the array... */
+ for (count = 0; count < ndt->node_count; count++) {
+ nd_entry_init(&ndt->node[count]);
+ } /* for() */
+
+ return nd_count; /* number of succesfully created nodes! */
+}
+#else
+/* nd_table_init()
+ * Version 2 of the nd_table_init() function.
+ * If called more than once, 2nd and any subsequent calls will
+ * be interpreted as a request to reserve an extra new_nd_count
+ * number of nodes. This will be done using realloc().
+ */
+static int nd_table_init(nd_table_t *ndt, int new_nd_count) {
+ int count;
+
+ /* initialise the node descriptor metadata array... */
+ ndt->node = realloc(ndt->node, sizeof(nd_entry_t) * (ndt->node_count + new_nd_count));
+ if (ndt->node == NULL) {
+#ifdef ERRMSG
+ fprintf(stderr, ERRMSG_HEAD "Out of memory: error initializing node address buffer\n");
+#endif
+ return -1;
+ }
+
+ /* initialise the state of each newly added node in the array... */
+ for (count = ndt->node_count; count < ndt->node_count + new_nd_count; count++) {
+ nd_entry_init(&ndt->node[count]);
+ } /* for() */
+ ndt->node_count += new_nd_count;
+
+ return new_nd_count; /* number of succesfully created nodes! */
+}
+#endif
+
+
+static inline nd_entry_t *nd_table_get_nd(nd_table_t *ndt, int nd) {
+ if ((nd < 0) || (nd >= ndt->node_count))
+ return NULL;
+
+ return &ndt->node[nd];
+}
+
+
+static inline void nd_table_done(nd_table_t *ndt) {
+ int i;
+
+ if (ndt->node == NULL)
+ return;
+
+ /* close all the connections... */
+ for (i = 0; i < ndt->node_count; i++)
+ nd_entry_free(&ndt->node[i]);
+
+ /* Free memory... */
+ free(ndt->node);
+ *ndt = (nd_table_t){.node=NULL, .node_count=0};
+}
+
+
+
+static inline int nd_table_get_free_nd(nd_table_t *ndt) {
+ int count;
+
+ for (count = 0; count < ndt->node_count; count++) {
+ if (nd_entry_is_free(&ndt->node[count]))
+ return count;
+ }
+
+ /* none found... */
+ return -1;
+}
+
+
+static inline int nd_table_free_nd(nd_table_t *ndt, int nd) {
+ if ((nd < 0) || (nd >= ndt->node_count))
+ return -1;
+
+ return nd_entry_free(&ndt->node[nd]);
+}
+
+
+
+/**************************************************************/
+/**************************************************************/
+/**** ****/
+/**** ****/
+/**** Global Library State ****/
+/**** ****/
+/**** ****/
+/**************************************************************/
+/**************************************************************/
+
+ /* The node descriptor table... */
+ /* NOTE: This variable must be correctly initialised here!! */
+static nd_table_t nd_table_ = {.node=NULL, .node_count=0};
+
+ /* The optimization choice... */
+static optimization_t optimization_;
+
+ /* the crc function currently in use... */
+ /* This will depend on the optimisation choice... */
+crc_func_t crc_calc = DEF_CRC_FUNCTION;
+
+
+
+/**************************************************************/
+/**************************************************************/
+/**** ****/
+/**** ****/
+/**** CRC functions ****/
+/**** ****/
+/**** ****/
+/**************************************************************/
+/**************************************************************/
+
+#if RTU_FRAME_CRC_LENGTH < 2
+#error The CRC on modbus RTU frames requires at least 2 bytes in the frame length.
+#endif
+
+
+/************************************/
+/** **/
+/** Read the CRC of a frame **/
+/** **/
+/************************************/
+
+/* NOTE: cnt is number of bytes in the frame _excluding_ CRC! */
+static inline u16 crc_read(u8 *buf, int cnt) {
+ /* For some strange reason, the crc is transmited
+ * LSB first, unlike all other values...
+ */
+ return (buf[cnt + 1] << 8) | buf[cnt];
+}
+
+
+/************************************/
+/** **/
+/** Write the CRC of a frame **/
+/** **/
+/************************************/
+
+/* NOTE: cnt is number of bytes in the frame _excluding_ CRC! */
+static inline void crc_write(u8 *buf, int cnt) {
+ /* For some strange reason, the crc is transmited
+ * LSB first, unlike all other values...
+ *
+ * u16_v(query[string_length]) = mb_hton(temp_crc); -> This is wrong !!
+ */
+ /* NOTE: We have already checked above that RTU_FRAME_CRC_LENGTH is >= 2 */
+ u16 crc = crc_calc(buf, cnt);
+ buf[cnt] = lsb(crc);
+ buf[cnt+1] = msb(crc);
+}
+
+
+
+/************************************/
+/** **/
+/** A slow version of the **/
+/** CRC function **/
+/** **/
+/************************************/
+
+/* crc optimized for smallest memory footprint */
+static u16 crc_slow(u8 *buf, int cnt)
+{
+ int bit;
+ u16 temp,flag;
+
+ temp=0xFFFF;
+
+ while (cnt-- != 0) {
+ temp=temp ^ *buf++;
+ for (bit=1; bit<=8; bit++) {
+ flag = temp & 0x0001;
+ /* NOTE:
+ * - since temp is unsigned, we are guaranteed a zero in MSbit;
+ * - if it were signed, the value placed in the MSbit would be
+ * compiler dependent!
+ */
+ temp >>= 1;
+ if (flag)
+ temp=temp ^ 0xA001;
+ }
+ }
+ return(temp);
+}
+
+
+
+
+/************************************/
+/** **/
+/** A fast version of the **/
+/** CRC function **/
+/** **/
+/************************************/
+static u8 *crc_fast_buf = NULL;
+
+/* crc optimized for speed */
+static u16 crc_fast(u8 *buf, int cnt)
+{
+/* NOTE: The following arrays have been replaced by an equivalent
+ * array (crc_fast_buf[]) initialised at run-time.
+ */
+/*
+ static u8 buf_lsb[] = {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
+ };
+
+ static u8 buf_msb[] = {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
+ };
+*/
+ u8 crc_msb = 0xFF;
+ u8 crc_lsb = 0xFF;
+ int index;
+
+ if (cnt <= 0) {
+ fprintf(stderr, "\nInternal program error in file %s at line %d\n\n\n", __FILE__, __LINE__);
+ exit(EXIT_FAILURE);
+ }
+
+ while (cnt-- != 0) {
+ index = 2 * (crc_lsb ^ *buf++);
+ crc_lsb = crc_msb ^ crc_fast_buf[index]/* buf_lsb[index/2] */;
+ crc_msb = crc_fast_buf[index + 1] /* buf_msb[index/2] */;
+ }
+
+ return crc_msb*0x0100 + crc_lsb;
+}
+
+
+/************************************/
+/** **/
+/** init() and done() functions **/
+/** of fast CRC version **/
+/** **/
+/************************************/
+
+static inline int crc_fast_init(void) {
+ int i;
+ u8 data[2];
+ u16 tmp_crc;
+
+ if ((crc_fast_buf = (u8 *)malloc(256 * 2)) == NULL)
+ return -1;
+
+ for (i = 0x00; i < 0x100; i++) {
+ data[0] = 0xFF;
+ data[1] = i;
+ data[1] = ~data[1];
+ tmp_crc = crc_slow(data, 2);
+ crc_fast_buf[2*i ] = lsb(tmp_crc);
+ crc_fast_buf[2*i + 1] = msb(tmp_crc);
+ }
+
+ return 0;
+}
+
+
+static inline void crc_fast_done(void) {
+ free(crc_fast_buf);
+}
+
+
+/************************************/
+/** **/
+/** init() and done() functions **/
+/** of generic CRC **/
+/** **/
+/************************************/
+
+static inline int crc_init(optimization_t opt) {
+ switch (opt) {
+ case optimize_speed:
+ if (crc_fast_init() < 0)
+ return -1;
+ crc_calc = crc_fast;
+ return 0;
+ case optimize_size :
+ crc_calc = crc_slow;
+ return 0;
+ default:
+ return -1;
+ }
+
+ /* humour the compiler */
+ return -1;
+}
+
+
+static inline int crc_done(void) {
+ if (crc_calc == crc_fast)
+ crc_fast_done();
+
+ crc_calc = DEF_CRC_FUNCTION;
+ return 0;
+}
+
+
+
+/**************************************************************/
+/**************************************************************/
+/**** ****/
+/**** ****/
+/**** Sending of Modbus RTU Frames ****/
+/**** ****/
+/**** ****/
+/**************************************************************/
+/**************************************************************/
+
+/* W A R N I N G
+ * =============
+ * The modbus_rtu_write() function assumes that the caller
+ * has allocated a few bytes extra for the buffer containing
+ * the data. These bytes will be used to write the crc.
+ *
+ * The caller of this function MUST make sure that the data
+ * buffer, although only containing data_length bytes, has
+ * been allocated with a size equal to or larger than
+ * data_length + RTU_FRAME_CRC_LENGTH bytes
+ *
+ * I know, this is a very ugly hack, but we don't have much
+ * choice (please read other comments further on for more
+ * explanations)
+ *
+ * We will nevertheless try and make this explicit by having the
+ * library initialisation function (modbus_rtu_init() ) return a
+ * value specifying how many extra bytes this buffer should have.
+ * Maybe this way this very ugly hack won't go unnoticed, and we
+ * won't be having any segmentation faults...!
+ *
+ * NOTE: for now the transmit_timeout is silently ignored in RTU version!
+ */
+int modbus_rtu_write(int nd,
+ u8 *data,
+ size_t data_length,
+ u16 transaction_id,
+ const struct timespec *transmit_timeout
+ )
+{
+ fd_set rfds;
+ struct timeval timeout;
+ int res, send_retries;
+ nd_entry_t *nd_entry;
+
+#ifdef DEBUG
+ fprintf(stderr, "modbus_rtu_write(fd=%d) called...\n", nd);
+#endif
+ /* check if nd is correct... */
+ if ((nd_entry = nd_table_get_nd(&nd_table_, nd)) == NULL)
+ return -1;
+
+ /* check if nd is initialzed... */
+ if (nd_entry->fd < 0)
+ return -1;
+
+ /**************************
+ * append crc to frame... *
+ **************************/
+/* WARNING:
+ * The crc_write() function assumes that we have an extra
+ * RTU_FRAME_CRC_LENGTH free bytes at the end of the *data
+ * buffer.
+ * The caller of this function had better make sure he has
+ * allocated those extra bytes, or a segmentation fault will
+ * occur.
+ * Please read on why we leave this as it is...
+ *
+ * REASONS:
+ * We want to write the data and the crc in a single call to
+ * the OS. This is the only way we can minimally try to gurantee
+ * that we will not be introducing a silence of more than 1.5
+ * character transmission times between any two characters.
+ *
+ * We could do the above using one of two methods:
+ * (a) use a special writev() call in which the data
+ * to be sent is stored in two buffers (one for the
+ * data and the other for the crc).
+ * (b) place all the data in a single linear buffer and
+ * use the normal write() function.
+ *
+ * We cannot use (a) since the writev(2) function does not seem
+ * to be POSIX compliant...
+ * (b) has the drawback that we would need to allocate a new buffer,
+ * and copy all the data into that buffer. We have enough copying of
+ * data between buffers as it is, so we won't be doing it here
+ * yet again!
+ *
+ * The only option that seems left over is to have the caller
+ * of this function allocate a few extra bytes. Let's hope he
+ * does not forget!
+*/
+ crc_write(data, data_length);
+ data_length += RTU_FRAME_CRC_LENGTH;
+
+#ifdef DEBUG
+/* Print the hex value of each character that is about to be
+ * sent over the bus.
+ */
+ { int i;
+ for(i = 0; i < data_length; i++)
+ fprintf(stderr, "[0x%2X]", data[i]);
+ fprintf(stderr, "\n");
+ }
+#endif
+ /* THE MAIN LOOP!!! */
+ /* NOTE: The modbus standard specifies that the message must
+ * be sent continuosly over the wire with maximum
+ * inter-character delays of 1.5 character intervals.
+ *
+ * If the write() call is interrupted by a signal, then
+ * this delay will most probably be exceeded. We should then
+ * re-start writing the query from the begining.
+ *
+ * BUT, can we really expect the write() call to return
+ * query_length on every platform when no error occurs?
+ * The write call would still be correct if it only wrote
+ * 1 byte at a time!
+ *
+ * To protect ourselves getting into an infinte loop in the
+ * above cases, we specify a maximum number of retries, and
+ * hope for the best...! The worst will now be we simply do
+ * not get to send out a whole frame, and will therefore always
+ * fail on writing a modbus frame!
+ */
+ send_retries = RTU_FRAME_SEND_RETRY + 1; /* must try at least once... */
+ while (send_retries > 0) {
+
+ /*******************************
+ * synchronise with the bus... *
+ *******************************/
+ /* Remember that a RS485 bus is half-duplex, so we have to wait until
+ * nobody is transmitting over the bus for our turn to transmit.
+ * This will never happen on a modbus network if the master and
+ * slave state machines never get out of synch (granted, it probably
+ * only has two states, but a state machine nonetheless), but we want
+ * to make sure we can re-synchronise if they ever do get out of synch.
+ *
+ * The following lines will guarantee that we will re-synchronise our
+ * state machine with the current state of the bus.
+ *
+ * We first wait until the bus has been silent for at least
+ * char_interval_timeout (i.e. 3.5 character interval). We then flush
+ * any input and output that might be on the cache.
+ */
+ /* NOTES:
+ * - we do not need to reset the rfds with FD_SET(ttyfd, &rfds)
+ * before every call to select! We only wait on one file descriptor,
+ * so if select returns succesfully, it must have that same file
+ * decriptor set in the rdfs!
+ * If select returns with a timeout, then we do not get to call
+ * select again!
+ * - On Linux, timeout (i.e. timeout) is modified by select() to
+ * reflect the amount of time not slept; most other implementations
+ * do not do this. In the cases in which timeout is not modified,
+ * we will simply have to wait for longer periods if select is
+ * interrupted by a signal.
+ */
+ FD_ZERO(&rfds);
+ FD_SET(nd_entry->fd, &rfds);
+ timeout = nd_entry->time_35_char_;
+ while ((res = select(nd_entry->fd+1, &rfds, NULL, NULL, &timeout)) != 0) {
+ if (res > 0) {
+ /* we are receiving data over the serial port! */
+ /* Throw the data away! */
+ tcflush(nd_entry->fd, TCIFLUSH); /* flush the input stream */
+ /* reset the timeout value! */
+ timeout = nd_entry->time_35_char_;
+ /* We do not need to reset the FD SET here! */
+ } else {
+ /* some kind of error ocurred */
+ if (errno != EINTR)
+ /* we were not interrupted by a signal */
+ return -1;
+ /* We will be calling select() again.
+ * We need to reset the FD SET !
+ */
+ FD_ZERO(&rfds);
+ FD_SET(nd_entry->fd, &rfds);
+ }
+ } /* while (select()) */
+
+ /* Flush both input and output streams... */
+ /* NOTE: Due to the nature of the modbus protocol,
+ * when a frame is sent all previous
+ * frames that may have arrived at the sending node become
+ * irrelevant.
+ */
+ tcflush(nd_entry->fd, TCIOFLUSH); /* flush the input & output streams */
+ recv_buf_reset(&nd_entry->recv_buf_); /* reset the recv buffer */
+
+ /**********************
+ * write to output... *
+ **********************/
+ /* Please see the comment just above the main loop!! */
+ if ((res = write(nd_entry->fd, data, data_length)) != data_length) {
+ if ((res < 0) && (errno != EAGAIN ) && (errno != EINTR ))
+ return -1;
+ } else {
+ /* query succesfully sent! */
+ /* res == query_length */
+
+ /* NOTE: We do not flush the input stream after sending the frame!
+ * If the process gets swapped out between the end of writing
+ * to the serial port, and the call to flush the input of the
+ * same serial port, the response to the modbus query may be
+ * sent over between those two calls. This would result in the
+ * tcflush(ttyfd, TCIFLUSH) call flushing out the response
+ * to the query we have just sent!
+ * Not a good thing at all... ;-)
+ */
+ return data_length - RTU_FRAME_CRC_LENGTH;
+ }
+ /* NOTE: The maximum inter-character delay of 1.5 character times
+ * has most probably been exceeded, so we abort the frame and
+ * retry again...
+ */
+ send_retries--;
+ } /* while() MAIN LOOP */
+
+ /* maximum retries exceeded */
+ return -1;
+}
+
+
+
+/**************************************************************/
+/**************************************************************/
+/**** ****/
+/**** ****/
+/**** Receiving Modbus RTU Frames ****/
+/**** ****/
+/**** ****/
+/**************************************************************/
+/**************************************************************/
+
+#if MIN_FRAME_LENGTH < 2
+#error Modbus RTU frames have a minimum length larger than MIN_FRAME_LENGTH.
+#endif
+
+/************************************/
+/** **/
+/** Guess length of frame **/
+/** being read. **/
+/** **/
+/************************************/
+
+/* Auxiliary function to the search_for_frame() function.
+ *
+ * NOTE: data_byte_count must be >=2 for correct operation, therefore
+ * the #error condition above.
+ *
+ * Function to determine the length of the frame currently being read,
+ * assuming it is a query/response frame.
+ *
+ * The guess is obtained by analysing the bytes that have already been
+ * read. Sometimes we cannot be sure what is the frame length, because
+ * not enough bytes of the frame have been read yet (for example, frames
+ * that have a byte_count value which has not yet been read). In these
+ * cases we return not the frame length, but an error (-1).
+ *
+ * If we find the data does not make any sense (i.e. it cannot be a valid
+ * modbus frame), we return -1.
+ */
+static int frame_length(u8 *frame_data,
+ int frame_data_length,
+ /* The array containing the lengths of frames. */
+ /* - query_frame_length[]
+ * - response_frame_length[]
+ */
+ i8 *frame_length_array) {
+
+ u8 function_code;
+ int res;
+
+ /* check consistency of input parameters... */
+ /*
+ if ((frame_data == NULL) || (frame_length_array == NULL) || (frame_data_length < 2))
+ return -1;
+ */
+
+ function_code = frame_data[L2_FRAME_FUNCTION_OFS];
+
+ /* hard code the length of response to diagnostic function 8 (0x08), with
+ * subfunction 21 (0x15), and sub-sub-function (a.k.a. operation) 3 (0x03),
+ * which contains a byte count...
+ */
+ if ((function_code == 0x08) && (frame_length_array == response_frame_lengths)) {
+ if (frame_data_length < 4) {
+ /* not enough info to determine the sub-function... */
+ return -1;
+ } else {
+ if ((frame_data[2] == 0x00) && (frame_data[3] == 0x15)) {
+ /* we need a couple more bytes to figure out the sub-sub-function... */
+ if (frame_data_length < 6) {
+ /* not enough info to determine the sub-sub-function... */
+ return -1;
+ } else {
+ if ((frame_data[4] == 0x00) && (frame_data[5] == 0x03)) {
+ /* We have found a response frame to diagnostic sub-function ... */
+ if (frame_data_length < 8) {
+ /* not enough info to determine the frame length */
+ return -1;
+ } else {
+ return /*HEADER*/ 6 + mb_ntoh(u16_v(frame_data[6])) + RTU_FRAME_CRC_LENGTH;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ res = frame_length_array[function_code];
+
+ switch(res) {
+ case BYTE_COUNT_3 :
+ if (frame_data_length >= 3)
+ return BYTE_COUNT_3_HEADER + frame_data[2] + RTU_FRAME_CRC_LENGTH;
+ break;
+ case BYTE_COUNT_34:
+ if (frame_data_length >= 4)
+ return BYTE_COUNT_34_HEADER + mb_ntoh(u16_v(frame_data[2])) + RTU_FRAME_CRC_LENGTH;
+ break;
+ case BYTE_COUNT_7 :
+ if (frame_data_length >= 7)
+ return BYTE_COUNT_7_HEADER + frame_data[6] + RTU_FRAME_CRC_LENGTH;
+ break;
+ case BYTE_COUNT_11:
+ if (frame_data_length >= 11)
+ return BYTE_COUNT_11_HEADER + frame_data[10] + RTU_FRAME_CRC_LENGTH;
+ break;
+ case BYTE_COUNT_U :
+ return -1;
+ default:
+ return res + RTU_FRAME_CRC_LENGTH;
+ } /* switch() */
+
+ /* unknown frame length */
+ return -1;
+}
+
+
+
+/************************************/
+/** **/
+/** Search for a frame **/
+/** **/
+/************************************/
+
+/* Search for a valid frame in the current data.
+ * If no valid frame is found, then we return -1.
+ *
+ * NOTE: Since frame verification is done by calculating the CRC, which is rather
+ * CPU intensive, and this function may be called several times with the same,
+ * data, we keep state regarding the result of previous invocations...
+ * That is the reason for the *search_history parameter!
+ */
+static int search_for_frame(u8 *frame_data,
+ int frame_data_length,
+ int *search_history) {
+ int query_length, resp_length;
+ u8 function_code;
+ /* *search_history flag will have or'ed of following values... */
+#define SFF_HIST_NO_QUERY_FRAME 0x01
+#define SFF_HIST_NO_RESPONSE_FRAME 0x02
+#define SFF_HIST_NO_FRAME (SFF_HIST_NO_RESPONSE_FRAME + SFF_HIST_NO_QUERY_FRAME)
+
+ if ((*search_history == SFF_HIST_NO_FRAME) ||
+ (frame_data_length < MIN_FRAME_LENGTH) ||
+ (frame_data_length > MAX_RTU_FRAME_LENGTH))
+ return -1;
+
+ function_code = frame_data[L2_FRAME_FUNCTION_OFS];
+
+ /* check for exception frame... */
+ if ((function_code && 0x80) == 0x80) {
+ if (frame_data_length >= EXCEPTION_FRAME_LENGTH + RTU_FRAME_CRC_LENGTH) {
+ /* let's check CRC for valid frame. */
+ if ( crc_calc(frame_data, EXCEPTION_FRAME_LENGTH)
+ == crc_read(frame_data, EXCEPTION_FRAME_LENGTH))
+ return EXCEPTION_FRAME_LENGTH + RTU_FRAME_CRC_LENGTH;
+ else
+ /* We have checked the CRC, and it is not a valid frame! */
+ *search_history |= SFF_HIST_NO_FRAME;
+ }
+ return -1;
+ }
+
+ /* check for valid function code */
+ if ((function_code > MAX_FUNCTION_CODE) || (function_code < 1)) {
+ /* This is an invalid frame!!! */
+ *search_history |= SFF_HIST_NO_FRAME;
+ return -1;
+ }
+
+ /* let's guess the frame length */
+ query_length = resp_length = -1;
+ if ((*search_history & SFF_HIST_NO_QUERY_FRAME) == 0)
+ query_length = frame_length(frame_data, frame_data_length, query_frame_lengths);
+ if ((*search_history & SFF_HIST_NO_RESPONSE_FRAME) == 0)
+ resp_length = frame_length(frame_data, frame_data_length, response_frame_lengths);
+
+ /* let's check whether any of the lengths are valid...*/
+ /* If any of the guesses coincides with the available data length
+ * we check that length first...
+ */
+ if ((frame_data_length == query_length) || (frame_data_length == resp_length)) {
+ if ( crc_calc(frame_data, frame_data_length - RTU_FRAME_CRC_LENGTH)
+ == crc_read(frame_data, frame_data_length - RTU_FRAME_CRC_LENGTH))
+ return frame_data_length;
+ /* nope, wrong guess...*/
+ if (frame_data_length == query_length)
+ *search_history |= SFF_HIST_NO_QUERY_FRAME;
+ if (frame_data_length == resp_length)
+ *search_history |= SFF_HIST_NO_RESPONSE_FRAME;
+ }
+
+ /* let's shoot for a query frame */
+ if ((*search_history & SFF_HIST_NO_QUERY_FRAME) == 0) {
+ if (query_length >= 0) {
+ if (frame_data_length >= query_length) {
+ /* let's check if we have a valid frame */
+ if ( crc_calc(frame_data, query_length - RTU_FRAME_CRC_LENGTH)
+ == crc_read(frame_data, query_length - RTU_FRAME_CRC_LENGTH))
+ return query_length;
+ else
+ /* We have checked the CRC, and it is not a valid frame! */
+ *search_history |= SFF_HIST_NO_QUERY_FRAME;
+ }
+ }
+ }
+
+ /* let's shoot for a response frame */
+ if ((*search_history & SFF_HIST_NO_RESPONSE_FRAME) == 0) {
+ if (resp_length >= 0) {
+ if (frame_data_length >= resp_length) {
+ /* let's check if we have a valid frame */
+ if ( crc_calc(frame_data, resp_length - RTU_FRAME_CRC_LENGTH)
+ == crc_read(frame_data, resp_length - RTU_FRAME_CRC_LENGTH))
+ return resp_length;
+ else
+ *search_history |= SFF_HIST_NO_RESPONSE_FRAME;
+ }
+ }
+ }
+
+ /* Could not find valid frame... */
+ return -1;
+}
+
+
+
+/************************************/
+/** **/
+/** Read a frame **/
+/** **/
+/************************************/
+
+/* A small auxiliary function, just to make the code easier to read... */
+static inline void next_frame_offset(recv_buf_t *buf, u8 *slave_id) {
+ buf->frame_search_history = 0;
+ lb_data_purge(&(buf->data_buf), 1 /* skip one byte */);
+
+ if (slave_id == NULL)
+ return;
+
+ /* keep ignoring bytes, until we find one == *slave_id,
+ * or no more bytes...
+ */
+ while (lb_data_count(&(buf->data_buf)) != 0) {
+ if (*lb_data(&(buf->data_buf)) == *slave_id)
+ return;
+ lb_data_purge(&(buf->data_buf), 1 /* skip one byte */);
+ }
+}
+
+/* A small auxiliary function, just to make the code easier to read... */
+static inline int return_frame(recv_buf_t *buf,
+ int frame_length,
+ u8 **recv_data_ptr) {
+#ifdef DEBUG
+ fprintf(stderr, "\n" );
+ fprintf(stderr, "returning valid frame of %d bytes.\n", frame_length);
+#endif
+ /* set the data pointer */
+ *recv_data_ptr = lb_data(&(buf->data_buf));
+ /* remove the frame bytes off the buffer */
+ lb_data_purge(&(buf->data_buf), frame_length);
+ /* reset the search_history flag */
+ buf->frame_search_history = 0;
+ /* if the buffer becomes empty, then reset boundary flag */
+ if (lb_data_count(&(buf->data_buf)) <= 0)
+ buf->found_frame_boundary = 0;
+ /* return the frame length, excluding CRC */
+ return frame_length - RTU_FRAME_CRC_LENGTH;
+}
+
+/* A function to read a valid frame off the rtu bus.
+ *
+ * NOTES:
+ * - The returned frame is guaranteed to be a valid frame.
+ * - The returned length does *not* include the CRC.
+ * - The returned frame is not guaranteed to have the same
+ * slave id as that stored in (*slave_id). This value is used
+ * merely in optimizing the search for wanted valid frames
+ * after reading an aborted frame. Only in this situation do
+ * we limit our search for frames with a slvae id == (*slave_id).
+ * Under normal circumstances, the value in (*slave_id) is
+ * simply ignored...
+ * If any valid frame is desired, then slave_id should be NULL.
+ *
+ */
+
+/* NOTE: We cannot relly on the 3.5 character interval between frames to detect
+ * end of frame. We are reading the bytes from a user process, so in
+ * essence the bytes we are reading are coming off a cache.
+ * Any inter-character delays between the arrival of the bytes are
+ * lost as soon as they were placed in the cache.
+ *
+ * Our only recourse is to analyse the frame we are reading in real-time,
+ * and check if it is a valid frame by checking it's CRC.
+ * To optimise this, we must be able to figure out the length
+ * of the frame currently being received by analysing the first bytes
+ * of that frame. Unfortunately, we have three problems with this:
+ * 1) The spec does not specify the format of every possible modbus
+ * frame. For ex.functions 9, 10, 13, 14, 18 and 19(?).
+ * 2) It is not possible to figure out whether a frame is a query
+ * or a response by just analysing the frame, and query and response
+ * frames have different sizes...
+ * 3) A frame may be aborted in the middle! We have no easy way of telling
+ * if what we are reading is a partial (aborted) frame, followed by a
+ * correct frame.
+ * Possible solutions to:
+ * 1) We could try to reverse engineer, but at the moment I have no
+ * PLCs that will generate the required frames.
+ * The chosen method is to verify the CRC if we are lucky enough to
+ * detect the 3.5 frame boundary imediately following one of these
+ * frames of unknown length.
+ * If we do not detect any frame boundary, then our only option
+ * is to consider it an aborted frame.
+ * 2) We aim for the query frame (usually the shortest), and check
+ * it's CRC. If it matches, we accept, the frame, otherwise we try
+ * a response frame.
+ * 3) The only way is to consider a frame boundary after each byte,
+ * (i.e. ignore one bye at a time) and verify if the following bytes
+ * constitue a valid frame (by checking the CRC).
+ *
+ * When reading an aborted frame followed by two or more valid frames, if
+ * we are unlucky and do not detetect any frame boundary using the 3.5
+ * character interval, then we will most likely be reading in bytes
+ * beyond the first valid frame. This means we will have to store the extra
+ * bytes we have already read, so they may be handled the next time the
+ * read_frame() function is called.
+ */
+ /*
+ * NOTE: The modbus RTU spec is inconsistent on how to handle
+ * inter-character delays larger than 1.5 characters.
+ * - On one paragraph it is stated that any delay larger than
+ * 1.5 character times aborts the current frame, and a new
+ * frame is started.
+ * - On another paragraph it is stated that a frame must begin
+ * with a silence of 3.5 character times.
+ *
+ * We will therefore consider that any delay larger than 1.5 character
+ * times terminates a valid frame. All the above references to the 3.5 character
+ * interval should therefore be read as a 1.5 character interval.
+ */
+/* NOTE: This function is only called from one place in the rest of the code,
+ * so we might just as well make it inline...
+ */
+/* RETURNS: number of bytes in received frame
+ * -1 on read file error
+ * -2 on timeout
+ */
+static inline int read_frame(nd_entry_t *nd_entry,
+ u8 **recv_data_ptr,
+ struct timespec *end_time,
+ u8 *slave_id)
+{
+ /* temporary variables... */
+ fd_set rfds;
+ struct timeval timeout;
+ int res, read_stat;
+ int frame_length;
+ recv_buf_t *recv_buf = &nd_entry->recv_buf_;
+
+ /* Flag:
+ * 1 => we are reading in an aborted frame, so we must
+ * start ignoring bytes...
+ */
+ int found_aborted_frame;
+
+ /* assume error... */
+ *recv_data_ptr = NULL;
+
+ /*===================================*
+ * Check for frame in left over data *
+ *===================================*/
+ /* If we have any data left over from previous call to read_frame()
+ * (i.e. this very same function), then we try to interpret that
+ * data, and do not wait for any extra bytes...
+ */
+ frame_length = search_for_frame(lb_data(&recv_buf->data_buf),
+ lb_data_count(&recv_buf->data_buf),
+ &recv_buf->frame_search_history);
+ if (frame_length > 0)
+ /* We found a valid frame! */
+ return return_frame(recv_buf, frame_length, recv_data_ptr);
+
+ /* If the left over data finished at a frame boundary, and since it
+ * doesn't contain any valid frame, we discard those bytes...
+ */
+ if (recv_buf->found_frame_boundary == 1)
+ recv_buf_reset(recv_buf);
+
+ /*============================*
+ * wait for data availability *
+ *============================*/
+ /* if we can't find a valid frame in the existing data, or no data
+ * was left over, then we need to read more bytes!
+ */
+ FD_ZERO(&rfds);
+ FD_SET(nd_entry->fd, &rfds);
+ {int sel_res = my_select(nd_entry->fd + 1, &rfds, NULL, end_time);
+ if (sel_res < 0)
+ return -1;
+ if (sel_res == 0)
+ return -2;
+ }
+
+ /*==============*
+ * read a frame *
+ *==============*/
+ /* The main loop that reads one frame */
+ /* (multiple calls to read() ) */
+ /* and jumps out as soon as it finds a valid frame. */
+
+ found_aborted_frame = 0;
+ FD_ZERO(&rfds);
+ FD_SET(nd_entry->fd, &rfds);
+ while (1) {
+
+ /*------------------*
+ * read frame bytes *
+ *------------------*/
+ /* Read in as many bytes as possible...
+ * But only if we have not found a frame boundary. Once we find
+ * a frame boundary, we do not want to read in any more bytes
+ * and mix them up with the current frame's bytes.
+ */
+ if (recv_buf->found_frame_boundary == 0) {
+ read_stat = read(nd_entry->fd,
+ lb_free(&recv_buf->data_buf),
+ lb_free_count(&recv_buf->data_buf));
+ if (read_stat < 0) {
+ if (errno != EINTR)
+ return -1;
+ else
+ read_stat = 0;
+ }
+#ifdef DEBUG
+ {/* display the hex code of each character received */
+ int i;
+ fprintf(stderr, "-");
+ for (i=0; i < read_stat; i++)
+ fprintf(stderr, "<0x%2X>", *(lb_free(&recv_buf->data_buf) + i));
+ }
+#endif
+ lb_data_add(&recv_buf->data_buf, read_stat);
+ }
+
+ /*-----------------------*
+ * check for valid frame *
+ *-----------------------*/
+ frame_length = search_for_frame(lb_data(&recv_buf->data_buf),
+ lb_data_count(&recv_buf->data_buf),
+ &recv_buf->frame_search_history);
+ if (frame_length > 0)
+ /* We found a valid frame! */
+ return return_frame(recv_buf, frame_length, recv_data_ptr);
+
+ /* if we reach this point, we are sure we do not have valid frame
+ * of known length in the current data with the current offset...
+ */
+
+ /*---------------------------------*
+ * Have we found an aborted frame? *
+ *---------------------------------*/
+ if (lb_data_count(&recv_buf->data_buf) >= MAX_RTU_FRAME_LENGTH)
+ found_aborted_frame = 1;
+
+ /*---------------------------------*
+ * Must we try a new frame_offset? *
+ *---------------------------------*/
+ if (found_aborted_frame == 1) {
+ /* Note that the found_aborted_frame flag is only set if:
+ * 1 - we have previously detected a frame_boundary,
+ * (i.e. found_frame_boundary is == 1 !!) so we won't be
+ * reading in more bytes;
+ * 2 - we have read more bytes than the maximum frame length
+ *
+ * Considering we have just failed finding a valid frame, and the above
+ * points (1) and (2), then there is no way we are still going to
+ * find a valid frame in the current data.
+ * We must therefore try a new first byte for the frame...
+ */
+ next_frame_offset(recv_buf, slave_id);
+ }
+
+ /*-----------------------------*
+ * check for data availability *
+ *-----------------------------*/
+ if (recv_buf->found_frame_boundary == 0) {
+ /* We need more bytes!! */
+ /*
+ * if no character at the buffer, then we wait time_15_char_
+ * before accepting end of frame
+ */
+ /* NOTES:
+ * - On Linux, timeout is modified by select() to reflect
+ * the amount of time not slept; most other implementations do
+ * not do this. On those platforms we will simply have to wait
+ * longer than we wished if select() is by any chance interrupted
+ * by a signal...
+ */
+ timeout = nd_entry->time_15_char_;
+ while ((res = select(nd_entry->fd+1, &rfds, NULL, NULL, &timeout)) < 0) {
+ if (errno != EINTR)
+ return -1;
+ /* We will be calling select() again.
+ * We need to reset the FD SET !
+ */
+ FD_ZERO(&rfds);
+ FD_SET(nd_entry->fd, &rfds);
+ }
+
+ if (res == 0) {
+ int frame_length = lb_data_count(&recv_buf->data_buf);
+ /* We have detected an end of frame using timing boundaries... */
+ recv_buf->found_frame_boundary = 1; /* => stop trying to read any more bytes! */
+
+ /* Let's check if we happen to have a correct frame... */
+ if ((frame_length <= MAX_RTU_FRAME_LENGTH) &&
+ (frame_length - RTU_FRAME_CRC_LENGTH > 0)) {
+ if ( crc_calc(lb_data(&recv_buf->data_buf), frame_length - RTU_FRAME_CRC_LENGTH)
+ == crc_read(lb_data(&recv_buf->data_buf), frame_length - RTU_FRAME_CRC_LENGTH)) {
+ /* We have found a valid frame. Let's get out of here! */
+ return return_frame(recv_buf, frame_length, recv_data_ptr);
+ }
+ }
+
+ /* We have detected a frame boundary, but the frame we read
+ * is not valid...
+ *
+ * One of the following reasons must be the cause:
+ * 1 - we are reading a single aborted frame.
+ * 2 - we are reading more than one frame. The first frame,
+ * followed by any number of valid and/or aborted frames,
+ * may be one of:
+ * a - a valid frame whose length is unknown to us,
+ * i.e. it is not specified in the public Modbus spec.
+ * b - an aborted frame.
+ *
+ * Due to the complexity of reading 2a as a correct frame, we will
+ * consider it as an aborted frame. (NOTE: it is possible, but
+ * we will ignore it until the need arises... hopefully, never!)
+ *
+ * To put it succintly, what wee now have is an 'aborted' frame
+ * followed by one or more aborted and/or valid frames. To get to
+ * any valid frames, and since we do not know where they begin,
+ * we will have to consider every byte as the possible begining
+ * of a valid frame. For this permutation, we ignore the first byte,
+ * and carry on from there...
+ */
+ found_aborted_frame = 1;
+ lb_data_purge(&recv_buf->data_buf, 1 /* skip one byte */);
+ recv_buf->frame_search_history = 0;
+ }
+ }
+
+ /*-------------------------------*
+ * check for data yet to process *
+ *-------------------------------*/
+ if ((lb_data_count(&recv_buf->data_buf) < MIN_FRAME_LENGTH) &&
+ (recv_buf->found_frame_boundary == 1)) {
+ /* We have no more data to process, and will not read anymore! */
+ recv_buf_reset(recv_buf);
+ /* Return TIMEOUT error */
+ return -2;
+ }
+ } /* while (1)*/
+
+ /* humour the compiler... */
+ return -1;
+}
+
+
+
+
+
+/************************************/
+/** **/
+/** Read a Modbus RTU frame **/
+/** **/
+/************************************/
+
+/* The public function that reads a valid modbus frame.
+ *
+ * The returned frame is guaranteed to be different to the
+ * the frame stored in send_data, and to start with the
+ * same slave address stored in send_data[0].
+ *
+ * If send_data is NULL, send_data_length = 0, or
+ * ignore_echo == 0, then the first valid frame read off
+ * the bus is returned.
+ *
+ * return value: The length (in bytes) of the valid frame,
+ * -1 on error
+ * -2 on timeout
+ */
+
+int modbus_rtu_read(int *nd,
+ u8 **recv_data_ptr,
+ u16 *transaction_id,
+ const u8 *send_data,
+ int send_length,
+ const struct timespec *recv_timeout) {
+ struct timespec end_time, *ts_ptr;
+ int res, recv_length, iter;
+ u8 *local_recv_data_ptr;
+ u8 *slave_id, local_slave_id;
+ nd_entry_t *nd_entry;
+
+ /* Check input parameters... */
+ if (nd == NULL)
+ return -1;
+
+ if (recv_data_ptr == NULL)
+ recv_data_ptr = &local_recv_data_ptr;
+
+ if ((send_data == NULL) && (send_length != 0))
+ return -1;
+
+ /* check if nd is correct... */
+ if ((nd_entry = nd_table_get_nd(&nd_table_, *nd)) == NULL)
+ return -1;
+
+ /* check if nd is initialzed... */
+ if (nd_entry->fd < 0)
+ return -1;
+
+ slave_id = NULL;
+ if (send_length > L2_FRAME_SLAVEID_OFS) {
+ local_slave_id = send_data[L2_FRAME_SLAVEID_OFS];
+ slave_id = &local_slave_id;
+ }
+
+ /* We will potentially read many frames, and we cannot reset the timeout
+ * for every frame we read. We therefore determine the absolute time_out,
+ * and use this as a parameter for each call to read_frame() instead of
+ * using a relative timeout.
+ *
+ * NOTE: see also the timeout related comment in the read_frame()= function!
+ */
+ /* get the current time... */
+ ts_ptr = NULL;
+ if (recv_timeout != NULL) {
+ ts_ptr = &end_time;
+ *ts_ptr = timespec_add_curtime(*recv_timeout);
+ }
+
+ /* NOTE: When using a half-duplex RS-485 bus, some (most ?) RS232-485
+ * converters will send back to the RS232 port whatever we write,
+ * so we will read in whatever we write out onto the bus.
+ * We will therefore have to compare
+ * the first frame we read with the one we sent. If they are
+ * identical it is because we are in fact working on a RS-485
+ * bus and must therefore read in a second frame which will be
+ * the true response to our query.
+ * If the first frame we receive is different to the query we
+ * just sent, then we are *not* working on a RS-485 bus, and
+ * that is already the real response to our query.
+ *
+ * Flushing the input cache immediately after sending the query
+ * could solve this issue, but we have no guarantee that this
+ * process would not get swapped out between the write() and
+ * flush() calls, and we could therefore be flushing the response
+ * frame!
+ */
+
+ iter = 0;
+ while ((res = recv_length = read_frame(nd_entry, recv_data_ptr, ts_ptr, slave_id)) >= 0) {
+ if (iter < INT_MAX) iter++;
+
+ if ((send_length <= 0) || (nd_entry->ignore_echo == 0))
+ /* any valid frame will do... */
+ return recv_length;
+
+ if ((send_length > L2_FRAME_SLAVEID_OFS + 1) && (iter == 1))
+ /* We have a frame in send_data,
+ * so we must make sure we are not reading in the frame just sent...
+ *
+ * We must only do this for the first frame we read. Subsequent
+ * frames are guaranteed not to be the previously sent frame
+ * since the modbus_rtu_write() resets the recv buffer.
+ * Remember too that valid modbus responses may be exactly the same
+ * as the request frame!!
+ */
+ if (recv_length == send_length)
+ if (memcmp(*recv_data_ptr, send_data, recv_length) == 0)
+ /* recv == send !!! */
+ /* read in another frame. */
+ continue;
+
+ /* The frame read is either:
+ * - different to the frame in send_data
+ * - or there is only the slave id in send_data[0]
+ * - or both of the above...
+ */
+ if (send_length > L2_FRAME_SLAVEID_OFS)
+ if (recv_length > L2_FRAME_SLAVEID_OFS)
+ /* check that frame is from/to the correct slave... */
+ if ((*recv_data_ptr)[L2_FRAME_SLAVEID_OFS] == send_data[L2_FRAME_SLAVEID_OFS])
+ /* yep, it is... */
+ return recv_length;
+
+ /* The frame we have received is not acceptable...
+ * Let's read a new frame.
+ */
+ } /* while(...) */
+
+ /* error reading response! */
+ /* Return the error returned by read_frame! */
+ return res;
+}
+
+
+
+
+
+/**************************************************************/
+/**************************************************************/
+/**** ****/
+/**** ****/
+/**** Initialising and Shutting Down Library ****/
+/**** ****/
+/**** ****/
+/**************************************************************/
+/**************************************************************/
+
+/******************************/
+/** **/
+/** Load Default Values **/
+/** **/
+/******************************/
+
+static void set_defaults(int *baud,
+ int *parity,
+ int *data_bits,
+ int *stop_bits) {
+ /* Set the default values, if required... */
+ if (*baud == 0)
+ *baud = DEF_BAUD_RATE;
+ if (*data_bits == 0)
+ *data_bits = DEF_DATA_BITS;
+ if (*stop_bits == 0) {
+ if (*parity == 0)
+ *stop_bits = DEF_STOP_BITS_NOP; /* no parity */
+ else
+ *stop_bits = DEF_STOP_BITS_PAR; /* parity used */
+ }
+}
+
+
+/******************************/
+/** **/
+/** Initialise Library **/
+/** **/
+/******************************/
+
+int modbus_rtu_init(int nd_count,
+ optimization_t opt,
+ int *extra_bytes)
+{
+#ifdef DEBUG
+ fprintf(stderr, "modbus_rtu_init(): called...\n");
+ fprintf(stderr, "creating %d node descriptors\n", nd_count);
+ if (opt == optimize_speed)
+ fprintf(stderr, "optimizing for speed\n");
+ if (opt == optimize_size)
+ fprintf(stderr, "optimizing for size\n");
+#endif
+
+ /* check input parameters...*/
+ if (0 == nd_count) {
+ if (extra_bytes != NULL)
+ // Not the corect value for this layer.
+ // What we set it to in case this layer is not used!
+ *extra_bytes = 0;
+ return 0;
+ }
+ if (nd_count <= 0)
+ goto error_exit_0;
+
+ if (extra_bytes == NULL)
+ goto error_exit_0;
+
+ if (crc_init(opt) < 0) {
+#ifdef ERRMSG
+ fprintf(stderr, ERRMSG_HEAD "Out of memory: error initializing crc buffers\n");
+#endif
+ goto error_exit_0;
+ }
+
+ /* set the extra_bytes value... */
+ /* Please see note before the modbus_rtu_write() function for a
+ * better understanding of this extremely ugly hack...
+ *
+ * The number of extra bytes that must be allocated to the data buffer
+ * before calling modbus_rtu_write()
+ */
+ *extra_bytes = RTU_FRAME_CRC_LENGTH;
+
+ /* initialise nd table... */
+ if (nd_table_init(&nd_table_, nd_count) < 0)
+ goto error_exit_0;
+
+ /* remember the optimization choice for later reference... */
+ optimization_ = opt;
+
+#ifdef DEBUG
+ fprintf(stderr, "modbus_rtu_init(): returning succesfuly...\n");
+#endif
+ return 0;
+
+error_exit_0:
+ if (extra_bytes != NULL)
+ // Not the corect value for this layer.
+ // What we set it to in case of error!
+ *extra_bytes = 0;
+ return -1;
+}
+
+
+
+/******************************/
+/** **/
+/** Open node descriptor **/
+/** **/
+/******************************/
+
+/* Open a node for master or slave operation.
+ * Returns the node descriptor, or -1 on error.
+ *
+ * This function is mapped onto both
+ * modbus_connect() and modbus_listen()
+ */
+int modbus_rtu_connect(node_addr_t node_addr) {
+ int node_descriptor;
+ nd_entry_t *nd_entry;
+
+#ifdef DEBUG
+ fprintf(stderr, "modbus_rtu_connect(): called...\n");
+ fprintf(stderr, "opening %s\n", node_addr.addr.rtu.device);
+ fprintf(stderr, "baud_rate = %d\n", node_addr.addr.rtu.baud);
+ fprintf(stderr, "parity = %d\n", node_addr.addr.rtu.parity);
+ fprintf(stderr, "data_bits = %d\n", node_addr.addr.rtu.data_bits);
+ fprintf(stderr, "stop_bits = %d\n", node_addr.addr.rtu.stop_bits);
+ fprintf(stderr, "ignore_echo = %d\n", node_addr.addr.rtu.ignore_echo);
+#endif
+
+ /* Check for valid address family */
+ if (node_addr.naf != naf_rtu)
+ /* wrong address type... */
+ goto error_exit_0;
+
+ /* find a free node descriptor */
+ if ((node_descriptor = nd_table_get_free_nd(&nd_table_)) < 0)
+ /* if no free nodes to initialize, then we are finished... */
+ goto error_exit_0;
+ if ((nd_entry = nd_table_get_nd(&nd_table_, node_descriptor)) == NULL)
+ /* strange, this should not occur... */
+ goto error_exit_0;
+
+ /* set the default values... */
+ set_defaults(&(node_addr.addr.rtu.baud),
+ &(node_addr.addr.rtu.parity),
+ &(node_addr.addr.rtu.data_bits),
+ &(node_addr.addr.rtu.stop_bits));
+
+#ifdef DEBUG
+ fprintf(stderr, "modbus_rtu_connect(): calling nd_entry_connect()\n");
+#endif
+ if (nd_entry_connect(nd_entry, &node_addr, optimization_) < 0)
+ goto error_exit_0;
+
+#ifdef DEBUG
+ fprintf(stderr, "modbus_rtu_connect(): %s open\n", node_addr.addr.rtu.device);
+ fprintf(stderr, "modbus_rtu_connect(): returning nd=%d\n", node_descriptor);
+#endif
+ return node_descriptor;
+
+ error_exit_0:
+#ifdef DEBUG
+ fprintf(stderr, "modbus_rtu_connect(): error!\n");
+#endif
+ return -1;
+}
+
+
+
+int modbus_rtu_listen(node_addr_t node_addr) {
+ return modbus_rtu_connect(node_addr);
+}
+
+
+
+/******************************/
+/** **/
+/** Close node descriptor **/
+/** **/
+/******************************/
+
+int modbus_rtu_close(int nd) {
+ return nd_table_free_nd(&nd_table_, nd);
+}
+
+
+
+/******************************/
+/** **/
+/** Shutdown Library **/
+/** **/
+/******************************/
+
+int modbus_rtu_done(void) {
+ nd_table_done(&nd_table_);
+ crc_done();
+
+ return 0;
+}
+
+
+
+
+/******************************/
+/** **/
+/** **/
+/** **/
+/******************************/
+int modbus_rtu_silence_init(void) {
+ return 0;
+}
+
+
+
+
+/******************************/
+/** **/
+/** **/
+/** **/
+/******************************/
+
+
+double modbus_rtu_get_min_timeout(int baud,
+ int parity,
+ int data_bits,
+ int stop_bits) {
+ int parity_bits, start_bits, char_bits;
+
+ set_defaults(&baud, &parity, &data_bits, &stop_bits);
+ parity_bits = (parity == 0)?0:1;
+ start_bits = 1;
+ char_bits = start_bits + data_bits + parity_bits + stop_bits;
+ return (double)((MAX_RTU_FRAME_LENGTH * char_bits) / baud);
+}
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mb_rtu_private.h Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2001,2016 Mario de Sousa (msousa@fe.up.pt)
+ *
+ * This file is part of the Modbus library for Beremiz and matiec.
+ *
+ * This Modbus library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this Modbus library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is made available on the understanding that it will not be
+ * used in safety-critical situations without a full and competent review.
+ */
+
+
+
+#ifndef MODBUS_RTU_PRIVATE_H
+#define MODBUS_RTU_PRIVATE_H
+
+#include "mb_types.h" /* get the data types */
+#include "mb_util.h"
+
+
+/* serial port default configuration... */
+#define DEF_DATA_BITS 8
+#define DEF_STOP_BITS_PAR 1 /* default stop bits if parity is used */
+#define DEF_STOP_BITS_NOP 2 /* default stop bits if parity is not used */
+#define DEF_BAUD_RATE 9600
+
+
+/* Send retries of rtu frames... */
+#define RTU_FRAME_SEND_RETRY 1
+ /* NOTES:
+ * - the above are the retries at the layer1 level,
+ * higher layers may decide to retry for themselves!
+ */
+
+
+/* Buffer sizes... */
+ /* We use double the maximum frame length for the read buffer,
+ * due to the algorithm used to work around aborted frames.
+ */
+#define RECV_BUFFER_SIZE_SMALL (MAX_RTU_FRAME_LENGTH + 10)
+#define RECV_BUFFER_SIZE_LARGE (2 * MAX_RTU_FRAME_LENGTH)
+
+
+/* Frame lengths... */
+
+ /* The number of bytes in each frame format, excluding CRC.
+ *
+ * BYTE_COUNT_3 denotes that third byte of frame contains the number of bytes;
+ * - total number of bytes in frame is
+ * BYTE_COUNT_3_HEADER + byte_count
+ * BYTE_COUNT_34 denotes that third+fourth bytes of frame contain the number of bytes;
+ * - total number of bytes in frame is
+ * BYTE_COUNT_34_HEADER + byte_count
+ * BYTE_COUNT_7 denotes that seventh byte of frame contain the number of bytes;
+ * - total number of bytes in frame is
+ * BYTE_COUNT_7_HEADER + byte_count
+ * BYTE_COUNT_11 denotes that eleventh byte of frame contain the number of bytes;
+ * - total number of bytes in frame is
+ * BYTE_COUNT_11_HEADER + byte_count
+ * BYTE_COUNT_U denotes unknown number of bytes;
+ */
+
+#define BYTE_COUNT_3_HEADER 3
+#define BYTE_COUNT_34_HEADER 4
+#define BYTE_COUNT_7_HEADER 7
+#define BYTE_COUNT_11_HEADER 11
+
+#define BYTE_COUNT_3 (-3)
+#define BYTE_COUNT_34 (-34)
+#define BYTE_COUNT_7 (-7)
+#define BYTE_COUNT_11 (-11)
+#define BYTE_COUNT_U (-128)
+
+
+#define MAX_FUNCTION_CODE 0x18
+
+#define MIN_FRAME_LENGTH 3
+#define EXCEPTION_FRAME_LENGTH 3
+
+static i8 query_frame_lengths[MAX_FUNCTION_CODE+1] = {
+ /* 0x00 */ 0, /* unused */
+ /* 0x01 */ 6, /* Read Coil Status */
+ /* 0x02 */ 6, /* Read Input Status */
+ /* 0x03 */ 6, /* Read Holding Registers */
+ /* 0x04 */ 6, /* Read Input Registers */
+ /* 0x05 */ 6, /* Force Single Coil */
+ /* 0x06 */ 6, /* Preset Single Register */
+ /* 0x07 */ 2, /* Read Exception Status */
+ /* 0x08 */ 4, /* Diagnostics */
+ /* 0x09 */ BYTE_COUNT_U, /* Program 484 */
+ /* 0x0A */ BYTE_COUNT_U, /* Poll 484 */
+ /* 0x0B */ 2, /* Fetch Comm. Event Counter */
+ /* 0x0C */ 2, /* Fetch Comm. Event Log */
+ /* 0x0D */ BYTE_COUNT_U, /* Program Controller */
+ /* 0x0E */ BYTE_COUNT_U, /* Poll Controller */
+ /* 0x0F */ BYTE_COUNT_7, /* Force Multiple Coils */
+ /* 0x10 */ BYTE_COUNT_7, /* Preset Multiple Registers */
+ /* 0x11 */ 2, /* Report Slave ID */
+ /* 0x12 */ BYTE_COUNT_U, /* Program 884/M84 */
+ /* 0x13 */ BYTE_COUNT_U, /* Reset. Comm. Link */
+ /* 0x14 */ BYTE_COUNT_3, /* Read General Reference */
+ /* 0x15 */ BYTE_COUNT_3, /* Write General Reference */
+ /* 0x16 */ 8, /* Mask Write 4X Register */
+ /* 0x17 */ BYTE_COUNT_11, /* Read/Write 4x Register */
+ /* 0x18 */ 4 /* Read FIFO Queue */
+ };
+
+static i8 response_frame_lengths[MAX_FUNCTION_CODE+1] = {
+ /* 0x00 */ 0, /* unused */
+ /* 0x01 */ BYTE_COUNT_3, /* Read Coil Status */
+ /* 0x02 */ BYTE_COUNT_3, /* Read Input Status */
+ /* 0x03 */ BYTE_COUNT_3, /* Read Holding Registers */
+ /* 0x04 */ BYTE_COUNT_3, /* Read Input Registers */
+ /* 0x05 */ 6, /* Force Single Coil */
+ /* 0x06 */ 6, /* Preset Single Register */
+ /* 0x07 */ 3, /* Read Exception Status */
+ /* 0x08 */ 6,/*see (1)*/ /* Diagnostics */
+ /* 0x09 */ BYTE_COUNT_U, /* Program 484 */
+ /* 0x0A */ BYTE_COUNT_U, /* Poll 484 */
+ /* 0x0B */ 6, /* Fetch Comm. Event Counter */
+ /* 0x0C */ BYTE_COUNT_3, /* Fetch Comm. Event Log */
+ /* 0x0D */ BYTE_COUNT_U, /* Program Controller */
+ /* 0x0E */ BYTE_COUNT_U, /* Poll Controller */
+ /* 0x0F */ 6, /* Force Multiple Coils */
+ /* 0x10 */ 6, /* Preset Multiple Registers */
+ /* 0x11 */ BYTE_COUNT_3, /* Report Slave ID */
+ /* 0x12 */ BYTE_COUNT_U, /* Program 884/M84 */
+ /* 0x13 */ BYTE_COUNT_U, /* Reset. Comm. Link */
+ /* 0x14 */ BYTE_COUNT_3, /* Read General Reference */
+ /* 0x15 */ BYTE_COUNT_3, /* Write General Reference */
+ /* 0x16 */ 8, /* Mask Write 4X Register */
+ /* 0x17 */ BYTE_COUNT_3, /* Read/Write 4x Register */
+ /* 0x18 */ BYTE_COUNT_34 /* Read FIFO Queue */
+ };
+
+/* NOTE (1):
+ * The diagnostic function (0x08) has sub-functions. In particular,
+ * sub-function 21 (0x15) has two sub-sub-functions. In the very
+ * particular case of *one* of these sub-sub-functions, the reply
+ * frame does *not* have a size of 4, but is variable in length
+ * and includes a byte counter.
+ * To take this into account in the table would require an extra two
+ * tables.
+ * The above length has been hardcoded into the frame_length() function
+ * (in file modbus_rtu.c)
+ */
+
+
+#define FALSE 0
+#define TRUE 1
+
+
+#endif /* MODBUS_RTU_PRIVATE_H */
+
+
+
+
+
+
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mb_slave.c Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,845 @@
+/*
+ * Copyright (c) 2001,2016 Mario de Sousa (msousa@fe.up.pt)
+ *
+ * This file is part of the Modbus library for Beremiz and matiec.
+ *
+ * This Modbus library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this Modbus library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is made available on the understanding that it will not be
+ * used in safety-critical situations without a full and competent review.
+ */
+
+
+/* mb_slave.c */
+
+#include <fcntl.h> /* File control definitions */
+#include <stdio.h> /* Standard input/output */
+#include <string.h>
+#include <stdlib.h>
+#include <termio.h> /* POSIX terminal control definitions */
+#include <sys/time.h> /* Time structures for select() */
+#include <unistd.h> /* POSIX Symbolic Constants */
+#include <errno.h> /* Error definitions */
+
+#include <netinet/in.h> /* required for htons() and ntohs() */
+#include "mb_layer1.h"
+#include "mb_slave.h"
+#include "mb_slave_private.h"
+
+/* #define DEBUG */ /* uncomment to see the data sent and received */
+
+
+#define modbus_write fptr_[layer1_fin].modbus_write
+#define modbus_read fptr_[layer1_fin].modbus_read
+#define modbus_init fptr_[layer1_fin].modbus_init
+#define modbus_done fptr_[layer1_fin].modbus_done
+#define modbus_connect fptr_[layer1_fin].modbus_connect
+#define modbus_listen fptr_[layer1_fin].modbus_listen
+#define modbus_close fptr_[layer1_fin].modbus_close
+#define modbus_silence_init fptr_[layer1_fin].modbus_silence_init
+#define modbus_get_min_timeout fptr_[layer1_fin].modbus_get_min_timeout
+
+/* the lower two bits of ttyfd are used to store the index to layer1 function pointers */
+/* layer1_fin index to fptr_[] is in lowest 2 bits of fd */
+#define get_ttyfd() int layer1_fin = fd & 3; int ttyfd = fd / 4;\
+ if (fd < 0) {ttyfd = fd; layer1_fin = 0; /* use modbusTCP */}
+
+
+
+
+/******************************************/
+/******************************************/
+/** **/
+/** Global Variables... **/
+/** **/
+/******************************************/
+/******************************************/
+/* The layer 1 (RTU, ASCII, TCP) implementations will be adding some
+ * header and tail bytes (e.g. CRC) to the packet we build here. Since
+ * layer1 will re-use the same buffer allocated in this slave layer
+ * (so as not to continuosly copy the same info from buffer to buffer),
+ * we need to allocate more bytes than those strictly required for this
+ * slave layer. Therefore, the extra_bytes parameter.
+ *
+ * Note that we add one more extra byte to the response buffer.
+ * This is because some response packets will not be starting off
+ * at byte 0, but rather at byte 1 of the buffer. This is in order
+ * to guarantee that the data that is sent on the buffer is aligned
+ * on even bytes (the 16 bit words!). This will allow the application
+ * (layer above the one implemented in this file - i.e. the callback
+ * functions) to reference this memory as an u16 *, without producing
+ * 'bus error' messages in some embedded devices that do not allow
+ * acessing u16 on odd numbered addresses.
+ */
+static int buff_extra_bytes_;
+#define RESP_BUFFER_SIZE (MAX_L2_FRAME_LENGTH + buff_extra_bytes_ + 1)
+
+/******************************************/
+/******************************************/
+/** **/
+/** Local Utility functions... **/
+/** **/
+/******************************************/
+/******************************************/
+
+
+/*
+ * Function to determine next transaction id.
+ *
+ * We use a library wide transaction id, which means that we
+ * use a new transaction id no matter what slave to which we will
+ * be sending the request...
+ */
+static inline u16 next_transaction_id(void) {
+ static u16 next_id = 0;
+ return next_id++;
+}
+
+
+/*
+ * Functions to convert u16 variables
+ * between network and host byte order
+ *
+ * NOTE: Modbus uses MSByte first, just like
+ * tcp/ip, so we could be tempted to use the htons() and
+ * ntohs() functions to guarantee code portability.
+ *
+ * However, on some embedded systems running Linux
+ * these functions only work if the 16 bit words are
+ * stored on even addresses. This is not always the
+ * case in our code, so we have to define our own
+ * conversion functions...
+ */
+
+/* if using gcc, use it to determine byte order... */
+#ifndef __BYTE_ORDER
+#if defined(__GNUC__)
+ /* We have GCC, which should define __LITTLE_ENDIAN__ */
+# if defined(__LITTLE_ENDIAN__)
+# define __BYTE_ORDER __LITTLE_ENDIAN
+# else
+# define __BYTE_ORDER __BIG_ENDIAN
+# endif
+#endif /* __GNUC__ */
+#endif /* __BYTE_ORDER */
+
+
+/* If we still don't know byte order, try to get it from <sys/param.h> */
+#ifndef __BYTE_ORDER
+#include <sys/param.h>
+#endif
+
+
+#ifndef __BYTE_ORDER
+# ifdef BYTE_ORDER
+# if BYTE_ORDER == LITTLE_ENDIAN
+# define __BYTE_ORDER __LITTLE_ENDIAN
+# else
+# if BYTE_ORDER == BIG_ENDIAN
+# define __BYTE_ORDER __BIG_ENDIAN
+# endif
+# endif
+# endif /* BYTE_ORDER */
+#endif /* __BYTE_ORDER */
+
+
+
+
+
+#ifdef __BYTE_ORDER
+# if __BYTE_ORDER == __LITTLE_ENDIAN
+
+/**************************************************************/
+/* u16 conversion functions to use on little endian platforms */
+/**************************************************************/
+
+static inline u16 mb_hton(u16 w) {
+ register u16 tmp;
+ tmp = (w & 0x00FF);
+ tmp = ((w & 0xFF00) >> 0x08) | (tmp << 0x08);
+ return(tmp);
+}
+#define mb_ntoh(a) mb_hton(a)
+
+static inline void mb_hton_count(u16 *w, int count) {
+ int i;
+ for (i = 0; i < count; i++) {
+ /* swap the bytes around...
+ * a = a ^ b;
+ * b = a ^ b;
+ * a = a ^ b;
+ */
+ ((u8 *)(w+i))[0] ^= ((u8 *)(w+i))[1];
+ ((u8 *)(w+i))[1] ^= ((u8 *)(w+i))[0];
+ ((u8 *)(w+i))[0] ^= ((u8 *)(w+i))[1];
+ }
+}
+#define mb_ntoh_count(w, count) mb_hton_count(w, count)
+
+
+
+# else
+# if __BYTE_ORDER == __BIG_ENDIAN
+/***********************************************************/
+/* u16 conversion functions to use on big endian platforms */
+/***********************************************************/
+
+ /* We do not need to swap the bytes around! */
+#define mb_ntoh(val) (val)
+#define mb_hton(val) (val)
+#define mb_hton_count(w, count) /* empty ! */
+#define mb_ntoh_count(w, count) /* empty ! */
+
+
+# else
+
+/********************************************************/
+/* u16 conversion functions to use on generic platforms */
+/********************************************************/
+
+ /* We don't know the byte order, so we revert to the
+ * standard htons() and ntohs() ...
+ */
+static inline u16 mb_hton(u16 h_value)
+ {return htons(h_value); /* return h_value; */}
+
+static inline u16 mb_ntoh(u16 m_value)
+ {return ntohs(m_value); /* return m_value; */}
+
+static inline void mb_hton_count(u16 *w, int count)
+ {int i; for (i = 0; i < count; i++) {w[i] = mb_hton(w[i]);}}
+
+static inline void mb_ntoh_count(u16 *w, int count)
+ {int i; for (i = 0; i < count; i++) {w[i] = mb_ntoh(w[i]);}}
+
+# endif
+# endif
+#endif /* __BYTE_ORDER */
+
+
+
+
+/* Safe versions of the conversion functions!
+ *
+ * Note that these functions always work, whatever the endiannes
+ * of the machine that executes it!
+ *
+ * It is also safe because the resulting value may be stored
+ * on an odd address even on machines that do not allow directly
+ * accessing u16 bit words on odd addresses.
+ */
+static inline int mb_hton_safe(u16 from, u16 *to_ptr) {
+ ((u8 *)to_ptr)[1] = (from & 0x00FF);
+ ((u8 *)to_ptr)[0] = ((from & 0xFF00) >> 0x08);
+ return 0;
+}
+
+#define mb_ntoh_safe(a, b) mb_hton_safe(a, b)
+
+
+/* return Most Significant Byte of value; */
+static inline u8 msb(u16 value)
+ {return (value >> 8) & 0xFF;}
+
+/* return Least Significant Byte of value; */
+static inline u8 lsb(u16 value)
+ {return value & 0xFF;}
+
+#define u16_v(char_ptr) (*((u16 *)(&(char_ptr))))
+
+
+
+
+
+
+
+
+
+/***********************************************/
+/***********************************************/
+/** **/
+/** Handle requests from master/client **/
+/** **/
+/***********************************************/
+/***********************************************/
+
+
+/* Handle functions 0x01 and 0x02 */
+typedef int (*read_bits_callback_t)(void *arg, u16 start_addr, u16 bit_count, u8 *data_bytes);
+static int handle_read_bits (u8 *query_packet,
+ u8 **resp_packet_ptr,
+ u8 *error_code,
+ read_bits_callback_t read_bits_callback,
+ void *callback_arg
+ ) {
+ u16 start_addr, count;
+ int res;
+ u8 *resp_packet;
+
+ /* If no callback, handle as if function is not supported... */
+ if (read_bits_callback == NULL)
+ {*error_code = ERR_ILLEGAL_FUNCTION; return -1;}
+
+ /* in oprder for the data in this packet to be aligned on even numbered addresses, this
+ * response packet will start off at an odd numbered byte...
+ * We therefore add 1 to the address where the packet starts.
+ */
+ (*resp_packet_ptr)++;
+ resp_packet = *resp_packet_ptr;
+
+ /* NOTE:
+ * Modbus uses high level addressing starting off from 1, but
+ * this is sent as 0 on the wire!
+ * We could expect the user to specify high level addressing
+ * starting at 1, and do the conversion to start off at 0 here.
+ * However, to do this we would then need to use an u32 data type
+ * to correctly hold the address supplied by the user (which could
+ * correctly be 65536, which does not fit in an u16), which would
+ * in turn require us to check whether the address supplied by the user
+ * is correct (i.e. <= 65536).
+ * I decided to go with the other option of using an u16, and
+ * requiring the user to use addressing starting off at 0!
+ */
+ /* start_addr = mb_ntoh(u16_v(query_packet[2])) + 1; */
+ mb_ntoh_safe(u16_v(query_packet[2]), &start_addr);
+ mb_ntoh_safe(u16_v(query_packet[4]), &count);
+
+ #ifdef DEBUG
+ printf("handle_read_input_bits() called. slave=%d, function=%d, start_addr=%d, count=%d\n",
+ query_packet[0], query_packet[1], start_addr, count);
+ #endif
+
+ if ((count > MAX_READ_BITS) || (count < 1))
+ {*error_code = ERR_ILLEGAL_DATA_VALUE; return -1;}
+
+ /* Remember, we are using addressing starting off at 0, in the start_addr variable! */
+ /* This means that he highest acceptable address is 65535, when count=1 .... */
+ /* Note the use of 65536 in the comparison will force automatic upgrade of u16 variables! */
+ /* => start_addr + count will nver overflow the u16 type! */
+ if (start_addr + count > 65536)
+ {*error_code = ERR_ILLEGAL_DATA_ADDRESS; return -1;}
+
+ /* start building response frame... */
+ resp_packet[0] = query_packet[0]; /* slave */
+ resp_packet[1] = query_packet[1]; /* function (either 0x01 or 0x02 ! */
+ resp_packet[2] = (count + 7) / 8; /* number of data bytes = ceil(count/8) */
+
+ res = read_bits_callback(callback_arg, start_addr, count, &(resp_packet[3]));
+ if (res == -2) {*error_code = ERR_ILLEGAL_DATA_ADDRESS; return -1;}
+ if (res < 0) {*error_code = ERR_SLAVE_DEVICE_FAILURE; return -1;}
+
+ return resp_packet[2] + 3; /* packet size is data length + 3 bytes -> slave, function, count */
+}
+
+
+
+/* Handle function 0x01 */
+int handle_read_output_bits (u8 *query_packet, u8 **resp_packet_ptr, u8 *error_code, mb_slave_callback_t *callbacks)
+ {return handle_read_bits(query_packet, resp_packet_ptr, error_code, callbacks->read_outbits, callbacks->arg);}
+
+/* Handle function 0x02 */
+int handle_read_input_bits (u8 *query_packet, u8 **resp_packet_ptr, u8 *error_code, mb_slave_callback_t *callbacks)
+ {return handle_read_bits(query_packet, resp_packet_ptr, error_code, callbacks->read_inbits, callbacks->arg);}
+
+
+
+
+/* Handle functions 0x03 and 0x04 */
+typedef int (*read_words_callback_t)(void *arg, u16 start_addr, u16 word_count, u16 *data_words);
+static int handle_read_words (u8 *query_packet,
+ u8 **resp_packet_ptr,
+ u8 *error_code,
+ read_words_callback_t read_words_callback,
+ void *callback_arg
+ ) {
+ u16 start_addr, count;
+ int res;
+ u8 *resp_packet;
+
+ /* If no callback, handle as if function is not supported... */
+ if (read_words_callback == NULL)
+ {*error_code = ERR_ILLEGAL_FUNCTION; return -1;}
+
+ /* See equivalent comment in handle_read_bits() */
+ (*resp_packet_ptr)++;
+ resp_packet = *resp_packet_ptr;
+
+ /* See equivalent comment in handle_read_bits() */
+ mb_ntoh_safe(u16_v(query_packet[2]), &start_addr);
+ mb_ntoh_safe(u16_v(query_packet[4]), &count);
+
+ #ifdef DEBUG
+ printf("handle_read_output_words() called. slave=%d, function=%d, start_addr=%d, count=%d\n",
+ query_packet[0], query_packet[1], start_addr, count);
+ #endif
+
+ if ((count > MAX_READ_REGS) || (count < 1))
+ {*error_code = ERR_ILLEGAL_DATA_VALUE; return -1;}
+
+ /* See equivalent comment in handle_read_bits() */
+ if (start_addr + count > 65536)
+ {*error_code = ERR_ILLEGAL_DATA_ADDRESS; return -1;}
+
+ /* start building response frame... */
+ resp_packet[0] = query_packet[0]; /* slave */
+ resp_packet[1] = query_packet[1]; /* function code, either 0x03 or 0x04 !!!*/
+ resp_packet[2] = count * 2; /* number of bytes of data... */
+
+ res = read_words_callback(callback_arg, start_addr, count, (u16 *)&(resp_packet[3]));
+ if (res == -2) {*error_code = ERR_ILLEGAL_DATA_ADDRESS; return -1;}
+ if (res < 0) {*error_code = ERR_SLAVE_DEVICE_FAILURE; return -1;}
+
+ /* convert all data from host to network byte order. */
+ mb_hton_count((u16 *)&(resp_packet[3]), count);
+
+ return resp_packet[2] + 3; /* packet size is data length + 3 bytes -> slave, function, count */
+}
+
+
+
+
+/* Handle function 0x03 */
+int handle_read_output_words (u8 *query_packet, u8 **resp_packet_ptr, u8 *error_code, mb_slave_callback_t *callbacks)
+ {return handle_read_words(query_packet, resp_packet_ptr, error_code, callbacks->read_outwords, callbacks->arg);}
+
+/* Handle function 0x04 */
+int handle_read_input_words (u8 *query_packet, u8 **resp_packet_ptr, u8 *error_code, mb_slave_callback_t *callbacks)
+ {return handle_read_words(query_packet, resp_packet_ptr, error_code, callbacks->read_inwords, callbacks->arg);}
+
+
+
+/* Handle function 0x05 */
+int handle_write_output_bit (u8 *query_packet, u8 **resp_packet_ptr, u8 *error_code, mb_slave_callback_t *callbacks) {
+ u16 start_addr;
+ int res;
+ u8 *resp_packet;
+
+ /* If no callback, handle as if function is not supported... */
+ if (callbacks->write_outbits == NULL)
+ {*error_code = ERR_ILLEGAL_FUNCTION; return -1;}
+
+ resp_packet = *resp_packet_ptr;
+
+ /* See equivalent comment in handle_read_bits() */
+ mb_ntoh_safe(u16_v(query_packet[2]), &start_addr);
+
+ #ifdef DEBUG
+ printf("handle_write_output_bit() called. slave=%d, function=%d, start_addr=%d\n",
+ query_packet[0], query_packet[1], start_addr);
+ #endif
+
+ // byte 5 Must be 0x00, byte 4 must be 0x00 or 0xFF !!
+ if ( (query_packet[5] != 0) ||
+ ((query_packet[4] != 0) && (query_packet[4] != 0xFF)))
+ {*error_code = ERR_ILLEGAL_DATA_VALUE; return -1;}
+
+ /* Address will always be valid, no need to check! */
+ // if (start_addr > 65535) {*error_code = ERR_ILLEGAL_DATA_ADDRESS; return -1;}
+
+ /* start building response frame... */
+ resp_packet[0] = query_packet[0]; /* slave */
+ resp_packet[1] = query_packet[1]; /* function */
+ resp_packet[2] = query_packet[2]; /* start address - hi byte */
+ resp_packet[3] = query_packet[3]; /* start address - lo byte */
+ resp_packet[4] = query_packet[4]; /* value: 0x00 or 0xFF */
+ resp_packet[5] = query_packet[5]; /* value: must be 0x00 */
+
+ res = (callbacks->write_outbits)(callbacks->arg, start_addr, 1, &(query_packet[4]));
+ if (res == -2) {*error_code = ERR_ILLEGAL_DATA_ADDRESS; return -1;}
+ if (res < 0) {*error_code = ERR_SLAVE_DEVICE_FAILURE; return -1;}
+
+ return 6; /* response packet size, including slave id in byte 0 */
+}
+
+
+
+/* Handle function 0x06 */
+int handle_write_output_word (u8 *query_packet, u8 **resp_packet_ptr, u8 *error_code, mb_slave_callback_t *callbacks) {
+ u16 start_addr;
+ int res;
+ u8 *resp_packet;
+
+ /* If no callback, handle as if function is not supported... */
+ if (callbacks->write_outwords == NULL)
+ {*error_code = ERR_ILLEGAL_FUNCTION; return -1;}
+
+ resp_packet = *resp_packet_ptr;
+
+ /* See equivalent comment in handle_read_bits() */
+ mb_ntoh_safe(u16_v(query_packet[2]), &start_addr);
+
+ #ifdef DEBUG
+ printf("handle_write_output_word() called. slave=%d, function=%d, start_addr=%d\n",
+ query_packet[0], query_packet[1], start_addr);
+ #endif
+
+ /* Address will always be valid, no need to check! */
+ // if (start_addr > 65535) {*error_code = ERR_ILLEGAL_DATA_ADDRESS; return -1;}
+
+ /* start building response frame... */
+ resp_packet[0] = query_packet[0]; /* slave */
+ resp_packet[1] = query_packet[1]; /* function */
+ resp_packet[2] = query_packet[2]; /* start address - hi byte */
+ resp_packet[3] = query_packet[3]; /* start address - lo byte */
+ resp_packet[4] = query_packet[4]; /* value - hi byte */
+ resp_packet[5] = query_packet[5]; /* value - lo byte */
+
+ /* convert data from network to host byte order */
+ mb_ntoh_count((u16 *)&(query_packet[4]), 1);
+
+ res = (callbacks->write_outwords)(callbacks->arg, start_addr, 1, (u16 *)&(query_packet[4]));
+ if (res == -2) {*error_code = ERR_ILLEGAL_DATA_ADDRESS; return -1;}
+ if (res < 0) {*error_code = ERR_SLAVE_DEVICE_FAILURE; return -1;}
+
+ return 6; /* packet size is 6 -> slave, function, addr(2), value(2) */
+}
+
+
+
+/* Handle function 0x0F */
+int handle_write_output_bits (u8 *query_packet, u8 **resp_packet_ptr, u8 *error_code, mb_slave_callback_t *callbacks) {
+ u16 start_addr, count;
+ int res;
+ u8 *resp_packet;
+
+ /* If no callback, handle as if function is not supported... */
+ if (callbacks->write_outbits == NULL)
+ {*error_code = ERR_ILLEGAL_FUNCTION; return -1;}
+
+ resp_packet = *resp_packet_ptr;
+
+ /* See equivalent comment in handle_read_bits() */
+ mb_ntoh_safe(u16_v(query_packet[2]), &start_addr);
+ mb_ntoh_safe(u16_v(query_packet[4]), &count);
+
+ #ifdef DEBUG
+ printf("handle_write_output_bits() called. slave=%d, function=%d, start_addr=%d, count=%d\n",
+ query_packet[0], query_packet[1], start_addr, count);
+ #endif
+
+ if ((count > MAX_WRITE_COILS) || (count < 1) || ((count+7)/8 != query_packet[6]) )
+ {*error_code = ERR_ILLEGAL_DATA_VALUE; return -1;}
+
+ /* See equivalent comment in handle_read_bits() */
+ if (start_addr + count > 65536)
+ {*error_code = ERR_ILLEGAL_DATA_ADDRESS; return -1;}
+
+ /* start building response frame... */
+ resp_packet[0] = query_packet[0]; /* slave */
+ resp_packet[1] = query_packet[1]; /* function */
+ resp_packet[2] = query_packet[2]; /* start address - hi byte */
+ resp_packet[3] = query_packet[3]; /* start address - lo byte */
+ resp_packet[4] = query_packet[4]; /* count - hi byte */
+ resp_packet[5] = query_packet[5]; /* count - lo byte */
+
+ res = (callbacks->write_outbits)(callbacks->arg, start_addr, count, &(query_packet[7]));
+ if (res == -2) {*error_code = ERR_ILLEGAL_DATA_ADDRESS; return -1;}
+ if (res < 0) {*error_code = ERR_SLAVE_DEVICE_FAILURE; return -1;}
+
+ return 6; /* packet size is 6 -> slave, function, addr(2), count(2) */
+}
+
+
+
+
+/* Handle function 0x10 */
+int handle_write_output_words(u8 *query_packet, u8 **resp_packet_ptr, u8 *error_code, mb_slave_callback_t *callbacks) {
+ u16 start_addr, count;
+ int res;
+ u8 *resp_packet;
+
+ /* If no callback, handle as if function is not supported... */
+ if (callbacks->write_outwords == NULL)
+ {*error_code = ERR_ILLEGAL_FUNCTION; return -1;}
+
+ resp_packet = *resp_packet_ptr;
+
+ /* See equivalent comment in handle_read_bits() */
+ mb_ntoh_safe(u16_v(query_packet[2]), &start_addr);
+ mb_ntoh_safe(u16_v(query_packet[4]), &count);
+
+ if ((count > MAX_WRITE_REGS) || (count < 1) || (count*2 != query_packet[6]) )
+ {*error_code = ERR_ILLEGAL_DATA_VALUE; return -1;}
+
+ /* See equivalent comment in handle_read_bits() */
+ if (start_addr + count > 65536)
+ {*error_code = ERR_ILLEGAL_DATA_ADDRESS; return -1;}
+
+ /* start building response frame... */
+ resp_packet[0] = query_packet[0]; /* slave */
+ resp_packet[1] = query_packet[1]; /* function */
+ resp_packet[2] = query_packet[2]; /* start address - hi byte */
+ resp_packet[3] = query_packet[3]; /* start address - lo byte */
+ resp_packet[4] = query_packet[4]; /* count - hi byte */
+ resp_packet[5] = query_packet[5]; /* count - lo byte */
+
+ /* convert all data from network to host byte order */
+ mb_ntoh_count((u16 *)&(query_packet[7]), count);
+
+ res = (callbacks->write_outwords)(callbacks->arg, start_addr, count, (u16 *)&(query_packet[7]));
+ if (res == -2) {*error_code = ERR_ILLEGAL_DATA_ADDRESS; return -1;}
+ if (res < 0) {*error_code = ERR_SLAVE_DEVICE_FAILURE; return -1;}
+
+ return 6; /* packet size is 6 -> slave, function, addr(2), count(2) */
+}
+
+
+
+
+
+
+
+
+/***********************************************/
+/***********************************************/
+/** **/
+/** initialise / shutdown the library **/
+/** **/
+/***********************************************/
+/***********************************************/
+
+int mb_slave_init__(int extra_bytes) {
+ buff_extra_bytes_ = extra_bytes;
+ return 0;
+}
+
+
+int mb_slave_done__(void)
+ {return 0;}
+
+
+#if 0
+int mb_slave_init(int nd_count) {
+ int extra_bytes;
+
+ #ifdef DEBUG
+ fprintf( stderr, "mb_slave_init()\n");
+ fprintf( stderr, "creating %d nodes\n", nd_count);
+ #endif
+
+ /* initialise layer 1 library */
+ if (modbus_init(nd_count, DEF_OPTIMIZATION, &extra_bytes) < 0)
+ goto error_exit_0;
+
+ /* initialise this library */
+ if (mb_slave_init__(extra_bytes) < 0)
+ goto error_exit_1;
+
+ return 0;
+
+error_exit_1:
+ modbus_done();
+error_exit_0:
+ return -1;
+}
+
+
+int mb_slave_done(void) {
+ mb_slave_done__(void)
+ return modbus_done();
+}
+#endif
+
+
+
+/***********************************************/
+/***********************************************/
+/** **/
+/** open/close slave connection **/
+/** **/
+/***********************************************/
+/***********************************************/
+
+/* Create a new slave/server */
+/* NOTE: We use the lower 2 bits of the returned node id to identify which
+ * layer1 implementation to use.
+ * 0 -> TCP
+ * 1 -> RTU
+ * 2 -> ASCII
+ * 4 -> unused
+ * The node id used by the layer1 is shifted left 2 bits
+ * before returning the node id to the caller!
+ */
+int mb_slave_new(node_addr_t node_addr) {
+ int res = -1;
+ #ifdef DEBUG
+ fprintf( stderr, "mb_slave_connect()\n");
+ #endif
+
+ /* call layer 1 library */
+ switch(node_addr.naf) {
+ case naf_tcp:
+ res = modbus_tcp_listen(node_addr);
+ if (res >= 0) res = res*4 + 0 /* offset into fptr_ with TCP functions */;
+ return res;
+ case naf_rtu:
+ res = modbus_rtu_listen(node_addr);
+ if (res >= 0) res = res*4 + 1 /* offset into fptr_ with RTU functions */;
+ return res;
+ case naf_ascii:
+ res = modbus_ascii_listen(node_addr);
+ if (res >= 0) res = res*4 + 2 /* offset into fptr_ with ASCII functions */;
+ return res;
+ }
+
+ return -1;
+}
+
+
+
+
+int mb_slave_close(int fd) {
+ #ifdef DEBUG
+ fprintf( stderr, "mb_slave_close(): nd = %d\n", fd);
+ #endif
+ get_ttyfd(); /* declare the ttyfd variable!! */
+ /* call layer 1 library */
+ /* will call one of modbus_tcp_close(), modbus_rtu_close(), modbus_ascii_close() */
+ return modbus_close(ttyfd);
+}
+
+
+
+
+
+/***********************************************/
+/***********************************************/
+/** **/
+/** Run the slave **/
+/** **/
+/***********************************************/
+/***********************************************/
+
+/* Execute infinite loop waiting and replying to requests coming from clients/master
+ * This function enters an infinite loop wating for new connection requests,
+ * and for modbus requests over previoulsy open connections...
+ *
+ * The frames are read from:
+ * - the node descriptor nd, if nd >= 0
+ * When using TCP, if the referenced node nd was created to listen for new connections
+ * [mb_slave_listen()], then this function will also reply to Modbus data requests arriving
+ * on other nodes that were created as a consequence of accepting connections requests to
+ * the referenced node nd.
+ * All other nodes are ignored!
+ *
+ * - any valid and initialised TCP node descriptor, if nd = -1
+ * In this case, will also accept connection requests arriving from a previously
+ * created node to listen for new connection requests [mb_slave_listen() ].
+ * NOTE: (only avaliable if using TCP)
+ *
+ * slaveid identifies the address (RTU and ASCII) or slaveid (TCP) that we implement.
+ * Any requests that we receive sent with a slaveid different
+ * than the one specified, and also different to 0, will be silently ignored!
+ * Whatever the slaveid specified, we always reply to requests
+ * to slaveid 0 (the modbus broadcast address).
+ * Calling this function with a slaveid of 0 means to ignore this
+ * parameter and to reply to all requests (whatever the slaveid
+ * used in the request). This should mostly be used by TCP servers...
+ */
+
+int mb_slave_run(int fd, mb_slave_callback_t callback_functions, u8 slaveid) {
+ int byte_count;
+ u16 transaction_id;
+ int nd;
+ u8 function, error_code = 0;
+ int resp_length;
+ u8 *query_packet = NULL;
+ u8 *resp_packet;
+ u8 resp_buffer_[RESP_BUFFER_SIZE];
+ u8 slave;
+
+ get_ttyfd(); /* declare the ttyfd variable!! */
+
+ #ifdef DEBUG
+ fprintf(stderr,"[%lu] mb_slave_run(): Called... fd=%d, ttyfd=%d\n", pthread_self(), fd, ttyfd);
+ #endif
+
+ while(1) {
+ nd = ttyfd;
+ /* will call one of modbus_tcp_read(), modbus_rtu_read(), modbus_ascii_read() */
+ do {
+ byte_count = modbus_read(&nd, /* node descriptor */
+ &query_packet, /* u8 **recv_data_ptr, */
+ &transaction_id, /* u16 *transaction_id, */
+ NULL, /* const u8 *send_data, */
+ 0, /* int send_length, */
+ NULL /* wait indefenitely */ /* const struct timespec *recv_timeout); */
+ );
+ } while (byte_count <= 2);
+
+ #ifdef DEBUG
+ {/* display the hex code of each character received */
+ int i;
+ printf("[%lu] mb_slave_run() received %d bytes (ptr=%p): \n", pthread_self(), byte_count, query_packet);
+ for (i=0; i < byte_count; i++)
+ printf("<0x%2X>", query_packet[i]);
+ printf("\n");
+ }
+ #endif
+
+ slave = query_packet[0];
+ function = query_packet[1];
+
+ /* We only reply if:
+ * - request was sent to broadcast address (slave == 0)
+ * OR - we were asked to reply to every request (slaveid == 0)
+ * OR - request matches the slaveid we were asked to accept (slave == slaveid)
+ *
+ * Otherwise, silently ignore the received request!!!
+ */
+ if ((slaveid == 0) || (slave == 0) || (slave == slaveid)) {
+ resp_packet = resp_buffer_;
+
+ switch(function) {
+ case 0x01: resp_length = handle_read_output_bits (query_packet, &resp_packet, &error_code, &callback_functions); break;
+ case 0x02: resp_length = handle_read_input_bits (query_packet, &resp_packet, &error_code, &callback_functions); break;
+ case 0x03: resp_length = handle_read_output_words (query_packet, &resp_packet, &error_code, &callback_functions); break;
+ case 0x04: resp_length = handle_read_input_words (query_packet, &resp_packet, &error_code, &callback_functions); break;
+ case 0x05: resp_length = handle_write_output_bit (query_packet, &resp_packet, &error_code, &callback_functions); break;
+ case 0x06: resp_length = handle_write_output_word (query_packet, &resp_packet, &error_code, &callback_functions); break;
+ case 0x0F: resp_length = handle_write_output_bits (query_packet, &resp_packet, &error_code, &callback_functions); break;
+ case 0x10: resp_length = handle_write_output_words(query_packet, &resp_packet, &error_code, &callback_functions); break;
+ /* return exception code 0x01 -> function not supported! */
+ default : resp_length = -1; error_code = 0x01; break;
+ }; /* switch(function) */
+
+ if (resp_length < 0) {
+ /* return error... */
+ /* build exception response frame... */
+ resp_packet = resp_buffer_;
+ resp_packet[0] = query_packet[0]; /* slave */
+ resp_packet[1] = query_packet[1] | 0x80; /* function code with error bit activated! */
+ resp_packet[2] = error_code;
+ resp_length = 3;
+ }
+ modbus_write(nd, resp_packet, resp_length, transaction_id, NULL /*transmit_timeout*/);
+ }; /* if not ignore request */
+ }; /* while(1) */
+
+ /* humour the compiler... */
+ return 0;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mb_slave.h Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2001,2016 Mario de Sousa (msousa@fe.up.pt)
+ *
+ * This file is part of the Modbus library for Beremiz and matiec.
+ *
+ * This Modbus library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this Modbus library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is made available on the understanding that it will not be
+ * used in safety-critical situations without a full and competent review.
+ */
+
+
+/* mb_slave.h */
+
+
+#ifndef MODBUS_SLAVE_H
+#define MODBUS_SLAVE_H
+
+#include <time.h> /* struct timespec data structure */
+
+#include "mb_types.h" /* get the data types */
+#include "mb_addr.h" /* get definition of common variable types and error codes */
+
+
+
+
+/* Initialise the Modbus Library to work as Slave/Server only */
+int mb_slave_init(int nd_count);
+/* Shut down the Modbus Library */
+int mb_slave_done(void);
+
+
+
+
+/* Create a new slave/server...
+ *
+ * This function creates a new node used to:
+ * - accept connection requests (TCP version)
+ * - receive frames from masters (RTU and ASCII versions)
+ *
+ * The type of address (naf_tcp, naf_rtu, naf_ascii) will specify the lower
+ * layer of modbus that will be used by the newly opened node.
+ */
+int mb_slave_new(node_addr_t node_addr);
+/* close a node crreated by mb_slave_new() */
+int mb_slave_close(int nd);
+
+
+
+
+/***********************************************/
+/***********************************************/
+/** **/
+/** Run the slave **/
+/** **/
+/***********************************************/
+/***********************************************/
+
+
+/* The following functions must return:
+ * -2 on attempt to read invalid address
+ * -1 on all other errors...
+ * 0 on success.
+ *
+ * Start_addr may start from 0, to 65535!
+ * In other words, we use 0 based addressing!
+ */
+typedef struct {
+ int (*read_inbits) (void *arg, u16 start_addr, u16 bit_count, u8 *data_bytes); /* bits are packed into bytes... */
+ int (*read_outbits) (void *arg, u16 start_addr, u16 bit_count, u8 *data_bytes); /* bits are packed into bytes... */
+ int (*write_outbits) (void *arg, u16 start_addr, u16 bit_count, u8 *data_bytes); /* bits are packed into bytes... */
+ int (*read_inwords) (void *arg, u16 start_addr, u16 word_count, u16 *data_words);
+ int (*read_outwords) (void *arg, u16 start_addr, u16 word_count, u16 *data_words);
+ int (*write_outwords)(void *arg, u16 start_addr, u16 word_count, u16 *data_words);
+ void *arg;
+ } mb_slave_callback_t;
+
+/* Execute the Slave and process requests coming from masters...
+ * This function enters an infinite loop wating for new connection requests,
+ * and for modbus requests over previoulsy open connections...
+ *
+ * The frames are read from:
+ * - the node descriptor nd, if nd >= 0
+ * When using TCP, if the referenced node nd was created to listen for new connections
+ * [mb_slave_listen()], then this function will also reply to Modbus data requests arriving
+ * on other nodes that were created as a consequence of accepting connections requests to
+ * the referenced node nd.
+ * All other nodes are ignored!
+ *
+ * - any valid and initialised TCP node descriptor, if nd = -1
+ * In this case, will also accept connection requests arriving from a previously
+ * created node to listen for new connection requests [mb_slave_listen() ].
+ * NOTE: (only avaliable if using TCP)
+ *
+ * slaveid identifies the address (RTU and ASCII) or slaveid (TCP) that we implement.
+ * Any requests that we receive sent with a slaveid different
+ * than the one specified, and also different to 0, will be silently ignored!
+ * Whatever the slaveid specified, we always reply to requests
+ * to slaveid 0 (the modbus broadcast address).
+ * Calling this function with a slaveid of 0 means to ignore this
+ * parameter and to reply to all requests (whatever the slaveid
+ * used in the request). This should mostly be used by TCP servers...
+ */
+int mb_slave_run(int nd, mb_slave_callback_t callback_functions, u8 slaveid);
+
+
+
+
+#endif /* MODBUS_SLAVE_H */
+
+
+
+
+
+
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mb_slave_and_master.c Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2002,2016 Mario de Sousa (msousa@fe.up.pt)
+ *
+ * This file is part of the Modbus library for Beremiz and matiec.
+ *
+ * This Modbus library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this Modbus library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is made available on the understanding that it will not be
+ * used in safety-critical situations without a full and competent review.
+ */
+
+
+
+#include <fcntl.h> /* File control definitions */
+#include <stdio.h> /* Standard input/output */
+#include <string.h>
+#include <stdlib.h>
+#include <termio.h> /* POSIX terminal control definitions */
+#include <sys/time.h> /* Time structures for select() */
+#include <unistd.h> /* POSIX Symbolic Constants */
+#include <errno.h> /* Error definitions */
+
+#include "mb_layer1.h"
+#include "mb_slave_private.h"
+#include "mb_master_private.h"
+#include "mb_slave.h"
+#include "mb_master.h"
+
+//#define DEBUG /* uncomment to see the data sent and received */
+
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+
+
+
+layer1_funct_ptr_t fptr_[4] = {
+ { /* WARNING: TCP functions MUST be the first, as we have this hardcoded in the code! */
+ /* more specifically, in the get_ttyfd() macro in mb_slave.c */
+ /* in the mb_slave_new() function in mb_slave.c */
+ /* in the mb_master_connect() function in mb_master.c */
+ &modbus_tcp_write
+ ,&modbus_tcp_read
+ ,&modbus_tcp_init
+ ,&modbus_tcp_done
+ ,&modbus_tcp_connect
+ ,&modbus_tcp_listen
+ ,&modbus_tcp_close
+ ,&modbus_tcp_silence_init
+ ,&modbus_tcp_get_min_timeout
+ },{
+ &modbus_rtu_write
+ ,&modbus_rtu_read
+ ,&modbus_rtu_init
+ ,&modbus_rtu_done
+ ,&modbus_rtu_connect
+ ,&modbus_rtu_listen
+ ,&modbus_rtu_close
+ ,&modbus_rtu_silence_init
+ ,&modbus_rtu_get_min_timeout
+ },{
+ &modbus_ascii_write
+ ,&modbus_ascii_read
+ ,&modbus_ascii_init
+ ,&modbus_ascii_done
+ ,&modbus_ascii_connect
+ ,&modbus_ascii_listen
+ ,&modbus_ascii_close
+ ,&modbus_ascii_silence_init
+ ,&modbus_ascii_get_min_timeout
+ },{
+ NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL
+ }
+};
+
+
+
+
+
+
+/************************************************************************
+
+ initialise / shutdown the library
+
+ These functions sets up/shut down the library state
+ (allocate memory for buffers, initialise data strcutures, etc)
+
+**************************************************************************/
+#define max(a,b) (((a)>(b))?(a):(b))
+
+int mb_slave_and_master_init(int nd_count_tcp, int nd_count_rtu, int nd_count_ascii) {
+ int extra_bytes, extra_bytes_tcp, extra_bytes_rtu, extra_bytes_ascii;
+
+#ifdef DEBUG
+ fprintf( stderr, "mb_slave_and_master_init()\n");
+ fprintf( stderr, "creating %d nodes\n", nd_count);
+#endif
+
+ /* initialise layer 1 library */
+ if (modbus_tcp_init (nd_count_tcp, DEF_OPTIMIZATION, &extra_bytes_tcp ) < 0)
+ goto error_exit_0;
+ if (modbus_rtu_init (nd_count_rtu, DEF_OPTIMIZATION, &extra_bytes_rtu ) < 0)
+ goto error_exit_1;
+ if (modbus_ascii_init(nd_count_ascii, DEF_OPTIMIZATION, &extra_bytes_ascii) < 0)
+ goto error_exit_2;
+ extra_bytes= max(extra_bytes_tcp, extra_bytes_rtu);
+ extra_bytes= max(extra_bytes , extra_bytes_ascii);
+
+ /* initialise master and slave libraries... */
+ if (mb_slave_init__(extra_bytes) < 0)
+ goto error_exit_3;
+ if (mb_master_init__(extra_bytes) < 0)
+ goto error_exit_4;
+ return 0;
+
+/*
+error_exit_3:
+ modbus_master_done();
+*/
+error_exit_4:
+ mb_slave_done__();
+error_exit_3:
+ modbus_ascii_done();
+error_exit_2:
+ modbus_rtu_done();
+error_exit_1:
+ modbus_tcp_done();
+error_exit_0:
+ return -1;
+}
+
+
+
+
+int mb_slave_and_master_done(void) {
+ int res = 0;
+ res |= mb_slave_done__ ();
+ res |= mb_master_done__ ();
+ res |= modbus_ascii_done();
+ res |= modbus_rtu_done ();
+ res |= modbus_tcp_done ();
+ return res;
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mb_slave_and_master.h Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2002,2016 Mario de Sousa (msousa@fe.up.pt)
+ *
+ * This file is part of the Modbus library for Beremiz and matiec.
+ *
+ * This Modbus library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this Modbus library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is made available on the understanding that it will not be
+ * used in safety-critical situations without a full and competent review.
+ */
+
+
+
+
+#include "mb_slave.h"
+#include "mb_master.h"
+
+
+/************************************************************************
+
+ initialise / shutdown the library
+
+ These functions sets up/shut down the library state
+ (allocate memory for buffers, initialise data strcutures, etc)
+
+**************************************************************************/
+
+
+int mb_slave_and_master_init(int nd_count_tcp, int nd_count_rtu, int nd_count_ascii);
+
+int mb_slave_and_master_done(void);
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mb_slave_private.h Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2002,2016 Mario de Sousa (msousa@fe.up.pt)
+ *
+ * This file is part of the Modbus library for Beremiz and matiec.
+ *
+ * This Modbus library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this Modbus library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is made available on the understanding that it will not be
+ * used in safety-critical situations without a full and competent review.
+ */
+
+
+
+#ifndef MODBUS_SLAVE_PRIVATE_H
+#define MODBUS_SLAVE_PRIVATE_H
+
+#include "mb_slave.h"
+#include "mb_util.h"
+
+
+#define DEF_LAYER2_SEND_RETRIES 1
+
+#define DEF_IGNORE_ECHO 0
+
+#define DEF_OPTIMIZATION optimize_speed
+
+
+
+
+int mb_slave_init__(int extra_bytes);
+int mb_slave_done__(void);
+
+
+#endif /* MODBUS_SLAVE_PRIVATE_H */
+
+
+
+
+
+
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mb_tcp.c Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,1705 @@
+/*
+ * Copyright (c) 2002,2016 Mario de Sousa (msousa@fe.up.pt)
+ *
+ * This file is part of the Modbus library for Beremiz and matiec.
+ *
+ * This Modbus library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this Modbus library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is made available on the understanding that it will not be
+ * used in safety-critical situations without a full and competent review.
+ */
+
+
+
+
+
+#include <fcntl.h> /* File control definitions */
+#include <stdio.h> /* Standard input/output */
+#include <string.h>
+#include <stdlib.h>
+#include <termio.h> /* POSIX terminal control definitions */
+#include <sys/time.h> /* Time structures for select() */
+#include <unistd.h> /* POSIX Symbolic Constants */
+#include <assert.h>
+#include <errno.h> /* Error definitions */
+#include <time.h> /* clock_gettime() */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h> /* required for htons() and ntohs() */
+#include <netinet/tcp.h> /* TCP level socket options */
+#include <netinet/ip.h> /* IP level socket options */
+
+#include <pthread.h>
+#include <sched.h> /* sched_yield() */
+
+
+
+#include "sin_util.h" /* internet socket utility functions... */
+#include "mb_layer1.h" /* The public interface this file implements... */
+#include "mb_tcp_private.h"
+
+
+
+/************************************/
+/** **/
+/** Include common code... **/
+/** **/
+/************************************/
+
+#include "mb_time_util.h"
+
+
+//#define ERRMSG
+#define ERRMSG_HEAD "Modbus/TCP: "
+
+
+// #define DEBUG /* uncomment to see the data sent and received */
+
+
+#ifdef DEBUG
+#ifndef ERRMSG
+#define ERRMSG
+#endif
+#endif
+
+
+
+/**************************************************************/
+/**************************************************************/
+/**** ****/
+/**** ****/
+/**** Forward Declarations ****/
+/**** and Defaults ****/
+/**** ****/
+/**************************************************************/
+/**************************************************************/
+
+
+ /* A Node Descriptor metadata,
+ * Due to the fact that modbus TCP is connection oriented,
+ * and that if the client detects an error the connection
+ * must be shut down and re-established automatically,
+ * the modbus TCP layer needs to keep the address of the remote server.
+ *
+ * We do this by implementing a node descriptor table, in which each
+ * entry will have the remote address, and the file descriptor
+ * of the socket currently in use.
+ *
+ * We do not pass the file descriptor up to the next higher layer. We
+ * send them the node descriptor instead...
+ */
+#define MB_MASTER_NODE 12
+#define MB_LISTEN_NODE 14
+#define MB_SLAVE_NODE 16
+#define MB_FREE_NODE 18
+typedef sa_family_t nd_type_t;
+
+typedef struct {
+ int fd; /* socket descriptor == file descriptor */
+ /* NOTE:
+ * Modbus TCP says that on error, we should close
+ * a connection and retry with a new connection.
+ * Since it takes time for a socket to close
+ * a connection if the remote server is down,
+ * we close the connection on the socket, close the
+ * socket itself, and create a new one for the new
+ * connection. There will be times when the node will
+ * not have any valid socket, and it will have to
+ * be created on the fly.
+ * When the node does not have a valid socket,
+ * fd will be set to -1
+ */
+ int node_type; /* What kind of use we are giving to this node...
+ * If node_type == MB_MASTER_NODE
+ * The node descriptor was initialised by the
+ * modbus_connect() function.
+ * The node descriptor is being used by a master
+ * device, and the addr contains the address of the slave.
+ * Remember that in this case fd may be >= 0 while
+ * we have a valid connection, or it may be < 0 when
+ * the connection needs to be reset.
+ * If node_type == MB_LISTEN_NODE
+ * The node descriptor was initialised by the
+ * modbus_listen() function.
+ * The node is merely used to accept() new connection
+ * requests. The new slave connections will use another
+ * node to transfer data.
+ * In this case fd must be >= 0.
+ * fd < 0 is an ilegal state and should never occur.
+ * If node_type == MB_SLAVE_NODE
+ * The node descriptor was initialised when a new
+ * connection request arrived on a MB_LISTEN type node.
+ * The node descriptor is being used by a slave device,
+ * and is currently being used to connect to a master.
+ * In this case fd must be >= 0.
+ * fd < 0 is an ilegal state and should never occur.
+ * If node_type == FREE_ND
+ * The node descriptor is currently not being used.
+ * In this case fd is set to -1, but is really irrelevant.
+ */
+ struct sockaddr_in addr; /* The internet adress we are using.
+ * If node_type == MB_MASTER_NODE
+ * addr will be the address of the remote slave
+ * If node_type == MB_LISTEN_NODE
+ * addr will be the address of the local listening port and network interface
+ * If node_type == MB_SLAVE_NODE
+ * addr will be the address of the local port and network interface
+ * of the connection to the specific client.
+ */
+ int listen_node; /* When a slave accepts a connection through a MB_LISTEN_NODE, it will
+ * will use an empty node for the new connection, and configure this new node
+ * to use the type MB_SLAVE_NODE.
+ * The listen_node entry is only used by nodes of type MB_SLAVE_NODE.
+ * In this case, listen_node will be the node of type MB_LISTEN_NODE through
+ * which the connection request came through...
+ */
+ int close_on_silence; /* A flag used only by Master Nodes.
+ * When (close_on_silence > 0), then the connection to the
+ * slave device will be shut down whenever the
+ * modbus_tcp_silence_init() function is called.
+ * Remember that the connection will be automatically
+ * re-established the next time the user wishes to communicate
+ * with the same slave (using this same node descripto).
+ * If the user wishes to comply with the sugestion
+ * in the OpenModbus Spec, (s)he should set this flag
+ * if a silence interval longer than 1 second is expected.
+ */
+ int print_connect_error; /* flag to guarantee we only print an error the first time we
+ * attempt to connect to a emote server.
+ * Stops us from generting a cascade of errors while the slave
+ * is down.
+ * Flag will get reset every time we successfully
+ * establish a connection, so a message is once again generated
+ * on the next error.
+ */
+ u8 *recv_buf; /* This node's receive buffer
+ * The library supports multiple simultaneous connections,
+ * and may need to receive multiple frames through mutiple nodes concurrently.
+ * To make the library thread-safe, we use one buffer for each node.
+ */
+} nd_entry_t;
+
+
+/* please make sure to lock the node table mutex before calling this function */
+static int nd_entry_init(nd_entry_t *nde) {
+ nde->addr.sin_family = AF_INET ;
+ nde->node_type = MB_FREE_NODE;
+ nde->fd = -1; /* not currently connected... */
+ /* initialise recv buffer */
+ nde->recv_buf = malloc(sizeof(u8) * RECV_BUFFER_SIZE);
+ if (nde->recv_buf == NULL)
+ return -1;
+ return 0;
+}
+
+/* please make sure to lock the node table mutex before calling this function */
+static int nd_entry_done(nd_entry_t *nde) {
+ free(nde->recv_buf);
+ return 0;
+}
+
+
+
+typedef struct {
+ /* the array of node descriptors, and current size... */
+ nd_entry_t *node; /* array of node entries. if NULL => node table not initialized */
+ int node_count; /* total number of nodes in the node[] array */
+ int free_node_count; /* number of free nodes in the node[] array */
+ pthread_mutex_t mutex;
+} nd_table_t;
+
+
+
+static int nd_table_done(nd_table_t *ndt) {
+ int count;
+
+ if (ndt->node == NULL)
+ return 0;
+
+ /* lock the mutex */
+ while (pthread_mutex_lock(&ndt->mutex) != 0) sched_yield();
+
+ /* initialise the state of each node in the array... */
+ for (count = 0; count < ndt->node_count; count++) {
+ nd_entry_done(&ndt->node[count]);
+ } /* for() */
+
+ free(ndt->node);
+ pthread_mutex_unlock (&ndt->mutex);
+ pthread_mutex_destroy(&ndt->mutex);
+ *ndt = (nd_table_t){.node=NULL, .node_count=0, .free_node_count=0};
+
+ return 0;
+}
+
+
+
+
+#if 1
+/* nd_table_init()
+ * Version 1 of the nd_table_init() function.
+ * If called more than once, 2nd and any subsequent calls will
+ * be interpreted as a request to confirm that it was already correctly
+ * initialized with the requested number of nodes.
+ */
+static int nd_table_init(nd_table_t *ndt, int nd_count) {
+ int count;
+
+ if (ndt->node != NULL) {
+ /* this function has already been called, and the node table is already initialised */
+ return (ndt->node_count == nd_count)?0:-1;
+ }
+
+ /* initialise the node table mutex... */
+ pthread_mutex_init(&ndt->mutex, NULL);
+ if (pthread_mutex_lock(&ndt->mutex) != 0) {
+#ifdef DEBUG
+ perror("pthread_mutex_lock()");
+ fprintf(stderr, "[%lu] Unable to lock newly crated mutex while creating new node table!\n", pthread_self());
+#endif
+ pthread_mutex_destroy(&ndt->mutex);
+ return -1;
+ }
+
+ /* initialise the node descriptor metadata array... */
+ ndt->node = malloc(sizeof(nd_entry_t) * nd_count);
+ if (ndt->node == NULL) {
+#ifdef DEBUG
+ perror("malloc()");
+ fprintf(stderr, "[%lu] Out of memory: error initializing node address buffer\n", pthread_self());
+#endif
+#ifdef ERRMSG
+ perror("malloc()");
+ fprintf(stderr, ERRMSG_HEAD "Out of memory. Error initializing node address buffer\n");
+#endif
+ pthread_mutex_unlock (&ndt->mutex);
+ pthread_mutex_destroy(&ndt->mutex);
+ return -1;
+ }
+
+ /* initialise the state of each node in the array... */
+ for (count = 0; count < nd_count; count++) {
+ if (nd_entry_init(&ndt->node[count]) < 0) {
+ pthread_mutex_unlock(&ndt->mutex);
+ nd_table_done(ndt);
+ return -1;
+ }
+ ndt->node_count = count+1;
+ ndt->free_node_count = count+1;
+ } /* for() */
+
+ ndt->node_count = nd_count;
+ ndt->free_node_count = nd_count;
+
+ pthread_mutex_unlock(&ndt->mutex);
+ return nd_count; /* number of succesfully created nodes! */
+}
+
+
+#else
+/* nd_table_init()
+ * Version 2 of the nd_table_init() function.
+ * If called more than once, 2nd and any subsequent calls will
+ * be interpreted as a request to reserve an extra new_nd_count
+ * number of nodes. This will be done using realloc().
+ */
+static int nd_table_init(nd_table_t *ndt, int new_nd_count) {
+ int count;
+
+ if (ndt->node == NULL) {
+ /* Node table nt yet initialized => we must initialise the node table mutex... */
+ pthread_mutex_init(&ndt->mutex, NULL);
+ }
+
+ /* lock the mutex */
+ while (pthread_mutex_lock(&ndt->mutex) != 0) sched_yield();
+
+ /* initialise the node descriptor metadata array... */
+ ndt->node = realloc(ndt->node, sizeof(nd_entry_t) * (ndt->node_count + new_nd_count));
+ if (ndt->node == NULL) {
+#ifdef DEBUG
+ perror("malloc()");
+ fprintf(stderr, "[%lu] Out of memory: error initializing node address buffer\n", pthread_self());
+#endif
+#ifdef ERRMSG
+ perror("malloc()");
+ fprintf(stderr, ERRMSG_HEAD "Out of memory. Error initializing node address buffer\n");
+#endif
+ pthread_mutex_unlock (&ndt->mutex);
+ pthread_mutex_destroy(&ndt->mutex);
+ return -1;
+ }
+
+ /* initialise the state of each newly added node in the array... */
+ for (count = ndt->node_count; count < ndt->node_count + new_nd_count; count++) {
+ if (nd_entry_init(&ndt->node[count]) < 0) {
+ pthread_mutex_unlock(&ndt->mutex);
+ return -1;
+ }
+ } /* for() */
+ ndt->node_count += new_nd_count;
+ ndt->free_node_count += new_nd_count;
+
+ pthread_mutex_unlock(&ndt->mutex);
+ return new_nd_count; /* number of succesfully created nodes! */
+}
+#endif
+
+
+static int nd_table_get_free_node(nd_table_t *ndt, nd_type_t nd_type) {
+ int count;
+
+ /* lock the mutex */
+ while (pthread_mutex_lock(&ndt->mutex) != 0) sched_yield();
+
+ /* check for free nodes... */
+ if (ndt->free_node_count <= 0) {
+ /* no free nodes... */
+ pthread_mutex_unlock(&ndt->mutex);
+ return -1;
+ }
+
+ /* Decrement the free node counter...*/
+ ndt->free_node_count--;
+
+ /* search for a free node... */
+ for (count = 0; count < ndt->node_count; count++) {
+ if(ndt->node[count].node_type == MB_FREE_NODE) {
+ /* found one!! Allocate it to the new type! */
+ ndt->node[count].node_type = nd_type;
+ pthread_mutex_unlock(&ndt->mutex);
+ return count;
+ }
+ } /* for() */
+
+ /* Strange... We should have free nodes, but we didn't finda any! */
+ /* Let's try to get into a consistent state, and return an error! */
+ ndt->free_node_count = 0;
+ pthread_mutex_unlock(&ndt->mutex);
+ return -1;
+}
+
+
+
+static void nd_table_close_node(nd_table_t *ndt, int nd) {
+
+ /* lock the mutex */
+ while (pthread_mutex_lock(&ndt->mutex) != 0) sched_yield();
+
+ if(ndt->node[nd].node_type == MB_FREE_NODE) {
+ /* Node already free... */
+ pthread_mutex_unlock(&ndt->mutex);
+ return;
+ }
+
+ /* Increment the free node counter...*/
+ ndt->free_node_count++;
+ /* Mark the node as being free. */
+ ndt->node[nd].node_type = MB_FREE_NODE;
+
+ pthread_mutex_unlock(&ndt->mutex);
+ return;
+}
+
+
+
+/**************************************************************/
+/**************************************************************/
+/**** ****/
+/**** ****/
+/**** Global Library State ****/
+/**** ****/
+/**** ****/
+/**************************************************************/
+/**************************************************************/
+
+
+ /* The node descriptor table... */
+ /* NOTE: The node_table_ Must be initialized correctly here! */
+static nd_table_t nd_table_ = {.node=NULL, .node_count=0, .free_node_count=0};
+
+
+/**************************************************************/
+/**************************************************************/
+/**** ****/
+/**** ****/
+/**** Local Utility functions... ****/
+/**** ****/
+/**** ****/
+/**************************************************************/
+/**************************************************************/
+
+
+#define min(a,b) ((a<b)?a:b)
+#define max(a,b) ((a>b)?a:b)
+
+/************************************/
+/** **/
+/** Configure socket for Modbus **/
+/** **/
+/************************************/
+
+
+static int configure_socket(int socket_id) {
+
+ /* configure the socket */
+ /* Set it to be non-blocking. This is safe because we always use select() before reading from it!
+ * It is also required for the connect() call. The default timeout in the TCP stack is much too long
+ * (typically blocks for 128 s ??) when the connect does not succedd imediately!
+ */
+ if (fcntl(socket_id, F_SETFL, O_NONBLOCK) < 0) {
+#ifdef ERRMSG
+ perror("fcntl()");
+ fprintf(stderr, ERRMSG_HEAD "Error configuring socket 'non-blocking' option.\n");
+#endif
+ return -1;
+ }
+
+ /* configure the socket */
+ /* set the TCP no delay flag. */
+ {int bool_opt = 1;
+ if (setsockopt(socket_id, SOL_TCP, TCP_NODELAY,
+ (const void *)&bool_opt, sizeof(bool_opt))
+ < 0) {
+#ifdef ERRMSG
+ perror("setsockopt()");
+ fprintf(stderr, ERRMSG_HEAD "Error configuring socket 'TCP no delay' option.\n");
+#endif
+ return -1;
+ }
+ }
+
+ /* set the IP low delay option. */
+ {int priority_opt = IPTOS_LOWDELAY;
+ if (setsockopt(socket_id, SOL_IP, IP_TOS,
+ (const void *)&priority_opt, sizeof(priority_opt))
+ < 0) {
+#ifdef ERRMSG
+ perror("setsockopt()");
+ fprintf(stderr, ERRMSG_HEAD "Error configuring socket 'IP low delay' option.\n");
+#endif
+ return -1;
+ }
+ }
+
+#if 0
+ /* send buffer */
+ /* NOTE: For slave devices, that may be receiving multiple
+ * requests before they have a chance to reply to the first,
+ * it probably is a good idea to have a large receive buffer.
+ * So it is best to leave it with the default configuration, as it is
+ * larger than the largest Modbus TCP frame.
+ *
+ * For the send buffer, a smaller buffer should suffice.
+ * However, it probably does not make sense to
+ * waste time asking for a smaller buffer, since the larger
+ * default buffer has already been allocated (the socket has already
+ * been created!)
+ *
+ * We might just as well leave out the configuration of the socket
+ * buffer size...
+ */
+#define SOCK_BUF_SIZE 300 /* The size proposed in the Modbus TCP spec. */
+ {int sock_buf_size;
+ sock_buf_size = SOCK_BUF_SIZE;
+ if (setsockopt(socket_id, SOL_SOCKET, SO_SNDBUF,
+ (const void *)&sock_buf_size, sizeof(sock_buf_size))
+ < 0)
+ return -1;
+ /* recv buffer */
+ sock_buf_size = SOCK_BUF_SIZE;
+ if (setsockopt(socket_id, SOL_SOCKET, SO_RCVBUF,
+ (const void *)&sock_buf_size, sizeof(sock_buf_size))
+ < 0)
+ return -1;
+ }
+#endif
+
+ return 0;
+}
+
+
+/************************************/
+/** **/
+/** Connect socket to remote host **/
+/** **/
+/************************************/
+
+/* This function will create a new socket, and connect it to a remote host... */
+static inline int open_connection(int nd, const struct timespec *timeout) {
+ int socket_id, con_res;
+
+#ifdef DEBUG
+ printf("[%lu] open_connection(): called, nd = %d\n", pthread_self(), nd);
+#endif
+
+ if (nd_table_.node[nd].fd >= 0)
+ /* nd already connected) */
+ return nd_table_.node[nd].fd;
+
+ if (nd_table_.node[nd].addr.sin_family != AF_INET)
+ /* invalid remote address, or invalid nd */
+ return -1;
+
+ /* lets try to connect... */
+ /* create the socket */
+ if ((socket_id = socket(PF_INET, DEF_TYPE, 0 /* protocol_num */)) < 0) {
+#ifdef DEBUG
+ perror("socket()");
+ fprintf(stderr, "[%lu] Error creating socket\n", pthread_self());
+#endif
+#ifdef ERRMSG
+ perror("socket()");
+ fprintf(stderr, ERRMSG_HEAD "Error creating socket\n");
+#endif
+ return -1;
+ }
+
+ /* configure the socket - includes setting non-blocking option! */
+ if (configure_socket(socket_id) < 0) {
+ close(socket_id);
+ return -1;
+ };
+
+ /* establish the connection to remote host */
+ con_res = connect(socket_id,
+ (struct sockaddr *)&(nd_table_.node[nd].addr),
+ sizeof(nd_table_.node[nd].addr));
+
+ /* The following condition is not strictly necessary
+ * (we could let the code fall through)
+ * but it does make the code easier to read/understand...
+ */
+ if (con_res >= 0)
+ goto success_exit; /* connected succesfully on first try! */
+
+ if (con_res < 0) {
+ if ((errno != EINPROGRESS) && (errno != EALREADY))
+ goto error_exit; /* error in connection request! */
+
+ /* connection request is ongoing */
+ /* EINPROGRESS -> first call to connect, EALREADY -> subsequent calls to connect */
+ /* Must wait for connect to complete at most 'timeout' seconds */
+ {fd_set fdset;
+ int res, so_error;
+ socklen_t len;
+ struct timespec end_time, *et_ptr;
+
+ et_ptr = NULL;
+ if (timeout != NULL) {
+ et_ptr = &end_time;
+ *et_ptr = timespec_add_curtime(*timeout);
+ }
+
+ FD_ZERO(&fdset);
+ FD_SET(socket_id, &fdset);
+
+ res = my_select(socket_id+1, NULL, &fdset, et_ptr);
+ if (res < 0) goto error_exit; /* error on call to select */
+ if (res == 0) goto error_exit; /* timeout */
+ /* (res > 0) -> connection attemt completed. May have been success or failure! */
+
+ len = sizeof(so_error);
+ res = getsockopt(socket_id, SOL_SOCKET, SO_ERROR, &so_error, &len);
+ if (res < 0) goto error_exit; /* error on call to getsockopt */
+ if (so_error != 0) goto error_exit; /* error on connection attempt */
+ goto success_exit; /* succesfully completed connection attempt! */
+ /* goto sucess_exit is not strcitly necessary - we could let the code fall through! */
+ }
+ }
+
+success_exit:
+ nd_table_.node[nd].fd = socket_id;
+ /* Succesfully established connection => print a message next time we have error. */
+ nd_table_.node[nd].print_connect_error = 1;
+
+#ifdef DEBUG
+ printf("[%lu] open_connection(): returning...\n", pthread_self());
+#endif
+ return socket_id;
+
+error_exit:
+#ifdef ERRMSG
+ if (nd_table_.node[nd].print_connect_error > 0) {
+ perror("connect()");
+ fprintf(stderr, ERRMSG_HEAD "Error establishing socket connection.\n");
+ /* do not print more error messages for this node... */
+ nd_table_.node[nd].print_connect_error = 0;
+ }
+#endif
+ close(socket_id);
+ return -1;
+}
+
+
+/* This function will accept a new connection request, and attribute it to a new node... */
+static inline int accept_connection(int nd) {
+ int socket_id, new_nd;
+
+#ifdef DEBUG
+ printf("[%lu] accept_connection(): called, nd = %d\n", pthread_self(), nd);
+#endif
+
+ /* NOTE: We MUST accccept8) all connection requests, even if no new node is available.
+ * => We first accept the connection request, and only later look for a node.
+ * If no node is free/available for this new connections request, the
+ * connection will be accepted and immediately closed.
+ * Reason:
+ * When the library is used for a Modbus/TCP server and no free node is
+ * available, if we do not accept() all newly arrived connection requests
+ * we would enter an infinite loop calling
+ * - select() (in modbus_tcp_read())
+ * - and accept_connection().
+ * Note that select() will continue to return immediately if the
+ * connection request is not accept()ted!
+ */
+ /* lets accept new connection request... */
+ if ((socket_id = accept(nd_table_.node[nd].fd, NULL, NULL)) < 0) {
+#ifdef ERRMSG
+ perror("accept()");
+ fprintf(stderr, ERRMSG_HEAD "Error while waiting for connection request from new client\n");
+#endif
+ /* error establishing new connection... */
+ return -1;
+ }
+
+ /* find a free node */
+ if ((new_nd = nd_table_get_free_node(&nd_table_, MB_SLAVE_NODE)) < 0) {
+ /* no available free nodes for the new connection... */
+ close(socket_id);
+ return -1;
+ }
+
+ /* configure the socket - includes setting the non-blocking option! */
+ if (configure_socket(socket_id) < 0) {
+ nd_table_close_node(&nd_table_, new_nd); /* first free up the un-used node. */
+ close(socket_id);
+ return -1;
+ }
+
+ /* set up the node entry and update the fd sets */
+ nd_table_.node[new_nd].fd = socket_id;
+ nd_table_.node[new_nd].listen_node = nd;
+
+#ifdef DEBUG
+ printf("[%lu] accept_connection(): returning new_nd = %d\n", pthread_self(), new_nd);
+#endif
+ return new_nd;
+}
+
+
+static inline void close_connection(int nd) {
+ if (nd_table_.node[nd].fd >= 0) {
+ /* disconnect the tcp connection */
+ shutdown(nd_table_.node[nd].fd, SHUT_RDWR);
+#ifdef ERRMSG
+ int res =
+#endif
+ close(nd_table_.node[nd].fd);
+#ifdef ERRMSG
+ if (res < 0) {
+ perror("close()");
+ fprintf(stderr, ERRMSG_HEAD "Error closing socket\n");
+ }
+#endif
+ nd_table_.node[nd].fd = -1;
+ }
+
+ if (nd_table_.node[nd].node_type == MB_SLAVE_NODE) {
+ /* If it is a slave node, we will not be receiving any more data over this disconnected node,
+ * (MB_SLAVE_NODE do not get re-connected!), so we free the node...
+ */
+ nd_table_close_node(&nd_table_, nd);
+ }
+}
+
+
+
+/************************************/
+/** **/
+/** Data format conversion **/
+/** **/
+/************************************/
+
+/*
+ * Functions to convert u16 variables
+ * between network and host byte order
+ *
+ * NOTE: Modbus uses MSByte first, just like
+ * tcp/ip, so we use the htons() and
+ * ntoh() functions to guarantee
+ * code portability.
+ */
+
+static inline u16 mb_hton(u16 h_value) {
+/* return h_value; */
+ return htons(h_value);
+}
+
+static inline u16 mb_ntoh(u16 m_value) {
+/* return m_value; */
+ return ntohs(m_value);
+}
+
+static inline u8 msb(u16 value) {
+/* return Most Significant Byte of value; */
+ return (value >> 8) & 0xFF;
+}
+
+static inline u8 lsb(u16 value) {
+/* return Least Significant Byte of value; */
+ return value & 0xFF;
+}
+
+#define u16_v(char_ptr) (*((u16 *)(&(char_ptr))))
+
+
+/************************************/
+/** **/
+/** Build/Check a frame header **/
+/** **/
+/************************************/
+
+/* A modbus TCP frame header has 6 bytes...
+ * header[0-1] -> transaction id
+ * header[2-3] -> must be 0
+ * header[4-5] -> frame data length (must be <= 255)
+ */
+#if TCP_HEADER_LENGTH < 6
+#error This code assumes a header size of 6 bytes, but TCP_HEADER_LENGTH < 6
+#endif
+
+static inline void build_header(u8 *header,
+ u16 transaction_id,
+ u16 byte_count)
+{
+ u16_v(header[0]) = mb_hton(transaction_id);
+ header[2] = 0;
+ header[3] = 0;
+ u16_v(header[4]) = mb_hton(byte_count);
+}
+
+
+static inline int check_header(u8 *header,
+ u16 *transaction_id,
+ u16 *byte_count)
+{
+ if ((header[2] != 0) || (header[3] != 0))
+ return -1;
+
+ *transaction_id = mb_ntoh(*(u16 *)(header + 0));
+ *byte_count = mb_ntoh(*(u16 *)(header + 4));
+
+ if (*byte_count > MAX_L2_FRAME_LENGTH)
+ return -1;
+
+ return 0;
+}
+
+
+
+
+
+/**************************************************************/
+/**************************************************************/
+/**** ****/
+/**** ****/
+/**** Sending of Modbus TCP Frames ****/
+/**** ****/
+/**** ****/
+/**************************************************************/
+/**************************************************************/
+
+// pthread_mutex_t sendmsg_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+/* NOTE: this function MUST be thread safe!! */
+int modbus_tcp_write(int nd, /* node descriptor */
+ u8 *data,
+ size_t data_length,
+ u16 transaction_id,
+ const struct timespec *transmit_timeout
+ )
+{
+#define data_vector_size 2
+
+ u8 header[TCP_HEADER_LENGTH];
+ struct iovec data_vector[data_vector_size] = {
+ {(void *)header, TCP_HEADER_LENGTH},
+ {NULL, 0}};
+ struct msghdr msg = {NULL, 0, data_vector, data_vector_size, NULL, 0, 0};
+ int res, bytes_sent;
+
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_write(): called... nd=%d\n", pthread_self(), nd);
+#endif
+
+ if ((nd >= nd_table_.node_count) || (nd < 0))
+ /* invalid node descriptor... */
+ return -1;
+
+#ifdef DEBUG
+// printf("[%lu] locking mutex...\n", pthread_self());
+#endif
+// while (pthread_mutex_lock(&sendmsg_mutex) != 0);
+
+ /*************************
+ * prepare the header... *
+ *************************/
+ build_header(header, transaction_id, data_length);
+#ifdef DEBUG
+/* Print the hex value of each character that is about to be
+ * sent over the bus.
+ */
+ { int i;
+ printf("modbus_tcp_write(): sending data...\n");
+ for(i = 0; i < TCP_HEADER_LENGTH; i++)
+ printf("[0x%2X]", header[i]);
+ for(i = 0; i < data_length; i++)
+ printf("[0x%2X]", data[i]);
+ printf("\n");
+ }
+#endif
+
+ /******************************************
+ * do we need to re-establish connection? *
+ ******************************************/
+ if (open_connection(nd, transmit_timeout) < 0) {
+#ifdef DEBUG
+ fprintf(stderr, "[%lu] modbus_tcp_write(): could not establish connection...\n", pthread_self());
+#endif
+#ifdef ERRMSG
+ fprintf(stderr, ERRMSG_HEAD "could not establish connection...\n");
+#endif
+ return -1;
+ }
+
+ /**********************
+ * write to output... *
+ **********************/
+ /* TWO ALTERNATIVE IMPLEMENTATIONS !!! */
+#if 0
+ /* write header */
+ bytes_sent = 0;
+ while (1) {
+ res = write(nd_table_.node[nd].fd, header+bytes_sent, TCP_HEADER_LENGTH-bytes_sent);
+ if (res < 0) {
+ if ((errno != EAGAIN ) && (errno != EINTR )) {
+ /* error sending message... */
+ close_connection(nd);
+ return -1;
+ } else {
+ continue;
+ }
+ } else {
+ /* res >= 0 */
+ bytes_sent += res;
+ if (bytes_sent >= TCP_HEADER_LENGTH) {
+ break;
+ }
+ }
+ }
+
+ /* write data */
+ bytes_sent = 0;
+ while (1) {
+ res = write(nd_table_.node[nd].fd, data+bytes_sent, data_length-bytes_sent);
+ if (res < 0) {
+ if ((errno != EAGAIN ) && (errno != EINTR )) {
+ /* error sending message... */
+ close_connection(nd);
+ return -1;
+ } else {
+ continue;
+ }
+ } else {
+ /* res >= 0 */
+ bytes_sent += res;
+ if (bytes_sent >= data_length) {
+ /* query succesfully sent! */
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_write(): sent %d bytes\n", pthread_self(), TCP_HEADER_LENGTH+data_length);
+#endif
+ return data_length;
+ }
+ }
+ }
+
+ /**********************
+ * write to output... *
+ **********************/
+#else
+ /* We are optimising for the most likely case, and in doing that
+ * we are making the least likely case have worse behaviour!
+ * Read on for an explanation...
+ *
+ * - The optimised behaviour for the most likely case:
+ * We have set the NO_DELAY flag on the socket, so the IP datagram
+ * is not delayed and is therefore sent as soon as any data is written to
+ * the socket.
+ * In order to send the whole message in a single IP datagram, we have to
+ * write both the the header and the data with a single call to write()
+ * In order to not to have to copy the data around just to add the
+ * message header, we use sendmsg() instead of write()!
+ *
+ * - The worse behaviour for the least likely case:
+ * If for some reason only part of the data is sent with the first call to
+ * write(), a datagram is sent right away, and the subsequent data will
+ * be sent in another datagram. :-(
+ */
+ /* NOTE: since snedmsg() is not thread safe, we use a mutex to protect access to this function... */
+
+ data_vector[data_vector_size - 1].iov_base = data;
+ data_vector[data_vector_size - 1].iov_len = data_length;
+ data_vector[ 0].iov_base = header;
+ data_vector[ 0].iov_len = TCP_HEADER_LENGTH;
+ bytes_sent = 0;
+ while (1) {
+ int sendmsg_errno;
+ /* Please see the comment just above the main loop!! */
+ res = sendmsg(nd_table_.node[nd].fd, &msg, 0);
+ sendmsg_errno = errno;
+ if (res < 0) {
+ if ((sendmsg_errno != EAGAIN ) && (sendmsg_errno != EINTR )) {
+ /* error sending message... */
+ close_connection(nd);
+ return -1;
+ } else {
+ continue;
+ }
+ } else {
+ /* res >= 0 */
+ bytes_sent += res;
+ if (bytes_sent >= data_length + TCP_HEADER_LENGTH) {
+ /* query succesfully sent! */
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_write(): sent %d bytes\n", pthread_self(), bytes_sent);
+#endif
+// pthread_mutex_unlock(&sendmsg_mutex);
+#ifdef DEBUG
+// printf("[%lu] unlocked mutex...\n", pthread_self());
+#endif
+ return data_length;
+ }
+
+ /* adjust the data_vector... */
+ if (res < data_vector[0].iov_len) {
+ u8* tmp = data_vector[0].iov_base;
+ tmp += res;
+ data_vector[0].iov_len -= res;
+ data_vector[0].iov_base = tmp;
+ } else {
+ u8* tmp = data_vector[1].iov_base;
+ tmp += res;
+ res -= data_vector[0].iov_len;
+ data_vector[0].iov_len = 0;
+ data_vector[1].iov_len -= res;
+ data_vector[1].iov_base = tmp;
+ }
+ }
+ } /* while (1) */
+#endif
+
+ /* humour the compiler... */
+// pthread_mutex_unlock(&sendmsg_mutex);
+#ifdef DEBUG
+// printf("[%lu] unlocked mutex...\n", pthread_self());
+#endif
+ return -1;
+}
+
+
+
+/**************************************************************/
+/**************************************************************/
+/**** ****/
+/**** ****/
+/**** Receiving Modbus TCP Frames ****/
+/**** ****/
+/**** ****/
+/**************************************************************/
+/**************************************************************/
+
+
+/* A helper function to modbus_tcp_read()
+ *
+ * WARNING: The semantics of this function are not what you would expect!
+ *
+ * if (data_already_available != 0)
+ * It assumes that select() has already been called before
+ * this function got called, and we are therefore guaranteed
+ * to have at least one byte to read off the socket (the fd).
+ *
+ * if (data_already_available == 0)
+ * it starts off by calling select()!
+ *
+ *
+ * NOTE: Ususal select semantics for (a: end_time == NULL) and
+ * (b: *end_time == 0) also apply.
+ *
+ * (a) Indefinite timeout
+ * (b) Try once, and and quit if no data available.
+ */
+/* RETURNS: number of bytes read
+ * -1 read error!
+ * -2 timeout
+ */
+static int read_bytes(int fd,
+ u8 *data,
+ int max_data_count,
+ const struct timespec *end_time,
+ int data_already_available)
+{
+ fd_set rfds;
+ int res, data_count;
+
+ data_count = 0;
+
+ while (data_count < max_data_count) {
+ /*============================*
+ * wait for data availability *
+ *============================*/
+ if (data_already_available == 0) {
+ int sel_res;
+ FD_ZERO(&rfds);
+ FD_SET(fd, &rfds);
+ sel_res = my_select(fd + 1, &rfds, NULL, end_time);
+ if (sel_res < 0)
+ return -1;
+ if (sel_res == 0)
+ /* timeout! */
+ return -2;
+ }
+
+ /*============================*
+ * read the available data... *
+ *============================*/
+ res = read(fd, data + data_count, max_data_count - data_count);
+ if (res == 0) {
+ /* We are guaranteed to have data to read off the fd since we called
+ * select(), but read() returned 0 bytes.
+ * This means that the remote process has closed down the connection,
+ * so we return 0.
+ */
+ return 0;
+ }
+
+ if (res < 0) {
+ if (errno != EINTR)
+ return -1;
+ else
+ res = 0;
+ }
+#ifdef DEBUG
+ {/* display the hex code of each character received */
+ int i;
+ for (i=0; i < res; i++)
+ printf("<0x%2X>", *(data + data_count + i));
+ }
+#endif
+ data_count += res;
+ data_already_available = 0;
+ } /* while ()*/
+
+ /* data read succesfully... */
+ return data_count;
+}
+
+
+
+/***************************************/
+/** **/
+/** Read a Modbus TCP frame **/
+/** off a single identified node. **/
+/** **/
+/***************************************/
+
+/* This private function will read a Modbus TCP frame off a single identified node
+ * that we know before hand that has data ready to be read off it. The data may or may not be
+ * a valid Modbus TCP frame. It is up to this function to figure that out.
+ */
+/* NOTES:
+ * - We re-use the recv_buf_ to load the frame header, so we have to make
+ * sure that the buffer is large enough to take it...
+ */
+ /* RETURNS: number of bytes read
+ * -1 on read from file/node error
+ * -2 on timeout
+ */
+#if RECV_BUFFER_SIZE < TCP_HEADER_LENGTH
+#error The receive buffer is smaller than the frame header length.
+#endif
+
+static int modbus_tcp_read_frame(int nd,
+ u16 *transaction_id,
+ struct timespec *ts_ptr) {
+ int fd, res;
+ u16 frame_length;
+
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_read_frame(): reading off nd=%d\n", pthread_self(), nd);
+#endif
+ /*=========================*
+ * read a Modbus TCP frame *
+ *=========================*/
+ /* assume error... */
+ fd = nd_table_.node[nd].fd;
+
+ /*-------------*
+ * read header *
+ *-------------*/
+ if ((res = read_bytes(fd, nd_table_.node[nd].recv_buf, TCP_HEADER_LENGTH, ts_ptr, 1)) != TCP_HEADER_LENGTH) {
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_read_frame(): frame with insuficient bytes for a valid header...\n", pthread_self());
+#endif
+ if (res < 0) return res;
+ return -1;
+ }
+
+ /* let's check for header consistency... */
+ if (check_header(nd_table_.node[nd].recv_buf, transaction_id, &frame_length) < 0) {
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_read_frame(): frame with non valid header...\n", pthread_self());
+#endif
+ return -1;
+ }
+
+ /*-----------*
+ * read data *
+ *-----------*/
+ if ((res = read_bytes(fd, nd_table_.node[nd].recv_buf, frame_length, ts_ptr, 0)) != frame_length) {
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_read_frame(): frame with non valid frame length...\n", pthread_self());
+#endif
+ if (res < 0) return res;
+ return -1;
+ }
+
+ /* frame received succesfully... */
+#ifdef DEBUG
+ printf("\n");
+#endif
+ return frame_length;
+}
+
+
+
+
+/***************************************/
+/** **/
+/** Read a Modbus TCP frame **/
+/** OR Accept connection requests **/
+/** off possibly multiple node... **/
+/** **/
+/***************************************/
+
+/* The public function that reads a valid modbus frame.
+ * The frame is read from...:
+ * - if (nd >= 0) and (nd is of type MB_MASTER_NODE or MB_SLAVE_NODE)
+ * The frame is read from the node descriptor nd
+ * - if (nd >= 0) and (nd is of type MB_LISTEN_NODE)
+ * The frame is read from the all node descriptors of type MB_SLAVE_NODE that were
+ * opened as a consequence of a connection request to the nd slave.
+ * In this case, new connection requests to nd will also be accepted!
+ * - if (nd == -1)
+ * The frame is read from any valid and initialised node descriptor.
+ * In this case, new connection requests to any nd of type MB_LISTEN_NODE will also be accepted!
+ * In this case, the node where the data is eventually read from is returned in *nd.
+ *
+ * The send_data and send_length parameters are ignored...
+ * (However, these parameters must stay in order to keep the function
+ * interface identical to the ASCII and RTU versons!)
+ *
+ * return value: The length (in bytes) of the valid frame,
+ * -1 on error
+ *
+ * NOTE: Ususal select semantics for (a: recv_timeout == NULL) and
+ * (b: *recv_timeout == 0) also apply.
+ *
+ * (a) Indefinite timeout
+ * (b) Try once, and and quit if no data available.
+ */
+
+ /* RETURNS: number of bytes read
+ * -1 on read from file/node error
+ * -2 on timeout
+ */
+int modbus_tcp_read(int *nd, /* node descriptor */
+ u8 **recv_data_ptr,
+ u16 *transaction_id,
+ const u8 *send_data, /* ignored ! */
+ int send_length, /* ignored ! */
+ const struct timespec *recv_timeout) {
+
+ struct timespec end_time, *ts_ptr;
+ u8 *local_recv_data_ptr;
+ u16 local_transaction_id = 0;
+
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_read(): called... nd=%d\n", pthread_self(), *nd);
+#endif
+
+ if (nd == NULL)
+ return -1;
+
+ if (*nd >= nd_table_.node_count)
+ /* invalid *nd */
+ /* remember that *nd < 0 is valid!! */
+ return -1;
+
+ if (recv_data_ptr == NULL)
+ recv_data_ptr = &local_recv_data_ptr;
+ if (transaction_id == NULL)
+ transaction_id = &local_transaction_id;
+
+ /* We will potentially call read() multiple times to read in a single frame.
+ * We therefore determine the absolute time_out, and use this as a parameter
+ * for each call to read_bytes() instead of using a relative timeout.
+ *
+ * NOTE: see also the timeout related comment in the read_bytes() function!
+ */
+ ts_ptr = NULL;
+ if (recv_timeout != NULL) {
+ ts_ptr = &end_time;
+ *ts_ptr = timespec_add_curtime(*recv_timeout);
+ }
+
+ /* If we must read off a single node... */
+ if (*nd >= 0)
+ /* but the node does not have a valid fd */
+ if ((nd_table_.node[*nd].node_type == MB_FREE_NODE) ||
+ (nd_table_.node[*nd].fd < 0))
+ /* then we return an error... */
+ return -1;
+
+ /* We will loop forever...
+ * We jump out of the loop and return from the function as soon as:
+ * - we receive a valid modbus message;
+ * OR
+ * - we time out.
+ *
+ * NOTE: This loop will close connections through which we receive invalid frames.
+ * This means that the set of nodes through which we may receive data may change with each
+ * loop iteration. => We need to re-calculate the fds in each loop iteration!
+ */
+
+ while (1) {
+ int nd_count, fd_high;
+ fd_set rfds;
+
+ /* We prepare our fd sets here so we can later call select() */
+ FD_ZERO(&rfds);
+ fd_high = -1;
+
+ for (nd_count = 0; nd_count < nd_table_.node_count; nd_count++) {
+ if (nd_table_.node[nd_count].node_type != MB_FREE_NODE)
+ {
+ if ((*nd < 0) // we select from all nodes
+ || (*nd == nd_count) // we select from this specific node
+ // we are listening on a MB_LISTEN_NODE, so we must also receive requests sent to slave nodes
+ // whose connection requests arrived through this MB_LISTEN_NDODE
+ || ((nd_table_.node[nd_count].node_type == MB_SLAVE_NODE) && (nd_table_.node[nd_count].listen_node == *nd)))
+ {
+ /* check if valid fd */
+ if (nd_table_.node[nd_count].fd >= 0) {
+ /* Add the descriptor to the fd set... */
+ FD_SET(nd_table_.node[nd_count].fd, &rfds);
+ fd_high = max(fd_high, nd_table_.node[nd_count].fd);
+ }
+ }
+ }
+ } /* for(;;) */
+
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_read(): while(1) looping. fd_high = %d, nd=%d\n", pthread_self(), fd_high, *nd);
+#endif
+
+ if (fd_high == -1)
+ /* we will not be reading from any node! */
+ return -1;
+
+ /* We now call select and wait for activity on the nodes we are listening to */
+ { int sel_res = my_select(fd_high + 1, &rfds, NULL, ts_ptr);
+ if (sel_res < 0)
+ return -1;
+ if (sel_res == 0)
+ /* timeout! */
+ return -2;
+ }
+
+ /* figure out which nd is ready to be read... */
+ for (nd_count = 0; nd_count < nd_table_.node_count; nd_count++) {
+ if ((nd_table_.node[nd_count].node_type != MB_FREE_NODE) &&
+ (nd_table_.node[nd_count].fd >= 0)) {
+ if (FD_ISSET(nd_table_.node[nd_count].fd, &rfds)) {
+ /* Found the node descriptor... */
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_read(): my_select() returned due to activity on node nd=%d\n", pthread_self(), nd_count);
+#endif
+ if (nd_table_.node[nd_count].node_type == MB_LISTEN_NODE) {
+ /* We must accept a new connection...
+ * No need to check for errors.
+ * If one occurs, there is nothing we can do...
+ */
+ accept_connection(nd_count);
+ } else {
+ /* it is a MB_SLAVE_NODE or a MB_MASTER_NODE */
+ /* We will read a frame off this nd */
+ int res;
+ res = modbus_tcp_read_frame(nd_count, transaction_id, ts_ptr);
+ if (res > 0) {
+ *nd = nd_count;
+ *recv_data_ptr = nd_table_.node[nd_count].recv_buf;
+ return res;
+ }
+ if (res < 0) {
+ /* We had an error reading the frame...
+ * We handle it by closing the connection, as specified by
+ * the modbus TCP protocol!
+ *
+ * NOTE: The error may have been a timeout, which means this function should return immediately.
+ * However, in this case we let the execution loop once again
+ * in the while(1) loop. My_select() will be called again
+ * and the timeout detected. The timeout error code (-2)
+ * will then be returned correctly!
+ */
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_read(): error reading frame. Closing connection...\n", pthread_self());
+#endif
+ /* We close the socket... */
+ close_connection(nd_count);
+ }
+ }
+ /* we have found the node descriptor, so let's jump out of the for(;;) loop */
+ break;
+ }
+ }
+ } /* for(;;) */
+
+ /* We were unsuccesfull reading a frame, so we try again... */
+ } /* while (1) */
+
+ /* humour the compiler... */
+ return -1;
+}
+
+
+
+
+
+/**************************************************************/
+/**************************************************************/
+/**** ****/
+/**** ****/
+/**** Initialising and Shutting Down Library ****/
+/**** ****/
+/**** ****/
+/**************************************************************/
+/**************************************************************/
+
+
+/* Ugly hack...
+ * Beremiz will be calling modbus_tcp_init() multiple times (through modbus_init() )
+ * (once for each plugin instance)
+ * It will also be calling modbus_tcp_done() the same number of times
+ * We only want to really shutdown the library the last time it is called.
+ * We therefore keep a counter of how many times modbus_tcp_init() is called,
+ * and decrement it in modbus_tcp_done()
+ */
+int modbus_tcp_init_counter = 0;
+
+/******************************/
+/** **/
+/** Load Default Values **/
+/** **/
+/******************************/
+
+static void set_defaults(const char **service) {
+ /* Set the default values, if required... */
+ if (*service == NULL)
+ *service = DEF_SERVICE;
+}
+
+
+/******************************/
+/** **/
+/** Initialise Library **/
+/** **/
+/******************************/
+/* returns the number of nodes succesfully initialised...
+ * returns -1 on error.
+ */
+int modbus_tcp_init(int nd_count,
+ optimization_t opt /* ignored... */,
+ int *extra_bytes) {
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_init(): called...\n", pthread_self());
+ printf("[%lu] creating %d nodes:\n", pthread_self(), nd_count);
+#endif
+
+ modbus_tcp_init_counter++;
+
+ /* set the extra_bytes value... */
+ /* Please see note before the modbus_rtu_write() function for a
+ * better understanding of this extremely ugly hack... This will be
+ * in the mb_rtu.c file!!
+ *
+ * The number of extra bytes that must be allocated to the data buffer
+ * before calling modbus_tcp_write()
+ */
+ if (extra_bytes != NULL)
+ *extra_bytes = 0;
+
+ if (0 == nd_count)
+ /* no need to initialise this layer! */
+ return 0;
+ if (nd_count <= 0)
+ /* invalid node count... */
+ goto error_exit_1;
+
+ /* initialise the node table... */
+ if (nd_table_init(&nd_table_, nd_count) < 0)
+ goto error_exit_1;
+
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_init(): %d node(s) opened succesfully\n", pthread_self(), nd_count);
+#endif
+ return nd_count; /* number of succesfully created nodes! */
+
+/*
+error_exit_2:
+ nd_table_done(&nd_table_);
+*/
+error_exit_1:
+ if (extra_bytes != NULL)
+ *extra_bytes = 0;
+ return -1;
+}
+
+
+
+
+
+
+/******************************/
+/** **/
+/** Open a Master Node **/
+/** **/
+/******************************/
+int modbus_tcp_connect(node_addr_t node_addr) {
+ int node_descriptor;
+ struct sockaddr_in tmp_addr;
+
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_connect(): called...\n", pthread_self());
+ printf("[%lu] %s:%s\n", pthread_self(),
+ node_addr.addr.tcp.host,
+ node_addr.addr.tcp.service);
+#endif
+
+ /* Check for valid address family */
+ if (node_addr.naf != naf_tcp)
+ /* wrong address type... */
+ return -1;
+
+ /* set the default values... */
+ set_defaults(&(node_addr.addr.tcp.service));
+
+ /* Check the parameters we were passed... */
+ if(sin_initaddr(&tmp_addr,
+ node_addr.addr.tcp.host, 0,
+ node_addr.addr.tcp.service, 0,
+ DEF_PROTOCOL)
+ < 0) {
+#ifdef ERRMSG
+ fprintf(stderr, ERRMSG_HEAD "Error parsing/resolving address %s:%s\n",
+ node_addr.addr.tcp.host,
+ node_addr.addr.tcp.service);
+#endif
+ return -1;
+ }
+
+ /* find a free node descriptor */
+ if ((node_descriptor = nd_table_get_free_node(&nd_table_, MB_MASTER_NODE)) < 0)
+ /* if no free nodes to initialize, then we are finished... */
+ return -1;
+
+ nd_table_.node[node_descriptor].addr = tmp_addr;
+ nd_table_.node[node_descriptor].fd = -1; /* not currently connected... */
+ nd_table_.node[node_descriptor].close_on_silence = node_addr.addr.tcp.close_on_silence;
+
+ if (nd_table_.node[node_descriptor].close_on_silence < 0)
+ nd_table_.node[node_descriptor].close_on_silence = DEF_CLOSE_ON_SILENCE;
+
+ /* WE have never tried to connect, so print an error the next time we try to connect */
+ nd_table_.node[node_descriptor].print_connect_error = 1;
+
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_connect(): returning nd=%d\n", pthread_self(), node_descriptor);
+#endif
+ return node_descriptor;
+}
+
+
+
+/******************************/
+/** **/
+/** Open a Slave Node **/
+/** **/
+/******************************/
+
+int modbus_tcp_listen(node_addr_t node_addr) {
+ int fd, nd;
+
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_listen(): called...\n", pthread_self());
+ printf("[%lu] %s:%s\n", pthread_self(),
+ node_addr.addr.tcp.host,
+ node_addr.addr.tcp.service);
+#endif
+
+ /* Check for valid address family */
+ if (node_addr.naf != naf_tcp)
+ /* wrong address type... */
+ goto error_exit_0;
+
+ /* set the default values... */
+ set_defaults(&(node_addr.addr.tcp.service));
+
+ /* create a socket and bind it to the appropriate port... */
+ fd = sin_bindsock(node_addr.addr.tcp.host,
+ node_addr.addr.tcp.service,
+ DEF_PROTOCOL);
+ if (fd < 0) {
+#ifdef ERRMSG
+ fprintf(stderr, ERRMSG_HEAD "Could not bind to socket %s:%s\n",
+ ((node_addr.addr.tcp.host==NULL)?"#ANY#":node_addr.addr.tcp.host),
+ node_addr.addr.tcp.service);
+#endif
+ goto error_exit_0;
+ }
+ if (listen(fd, DEF_MAX_PENDING_CONNECTION_REQUESTS) < 0)
+ goto error_exit_0;
+
+ /* find a free node descriptor */
+ if ((nd = nd_table_get_free_node(&nd_table_, MB_LISTEN_NODE)) < 0) {
+ /* if no free nodes to initialize, then we are finished... */
+ goto error_exit_1;
+ }
+
+ /* nd_table_.node[nd].addr = tmp_addr; */ /* does not apply for MB_LISTEN_NODE */
+ nd_table_.node[nd].fd = fd; /* not currently connected... */
+
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_listen(): returning nd=%d\n", pthread_self(), nd);
+#endif
+ return nd;
+
+error_exit_1:
+ close(fd);
+error_exit_0:
+ return -1;
+}
+
+
+
+/******************************/
+/** **/
+/** Close a node **/
+/** **/
+/******************************/
+
+int modbus_tcp_close(int nd) {
+#ifdef DEBUG
+ fprintf(stderr, "[%lu] modbus_tcp_close(): called... nd=%d\n", pthread_self(), nd);
+#endif
+
+ if ((nd < 0) || (nd >= nd_table_.node_count)) {
+ /* invalid nd */
+#ifdef DEBUG
+ fprintf(stderr, "[%lu] modbus_tcp_close(): invalid node %d. Should be < %d\n", pthread_self(), nd, nd_table_.node_count);
+#endif
+ return -1;
+ }
+
+ if (nd_table_.node[nd].node_type == MB_FREE_NODE)
+ /* already free node */
+ return 0;
+
+ close_connection(nd);
+
+ nd_table_close_node(&nd_table_, nd);
+
+ return 0;
+}
+
+
+
+/**********************************/
+/** **/
+/** Close all open connections **/
+/** **/
+/**********************************/
+
+int modbus_tcp_silence_init(void) {
+ int nd;
+
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_silence_init(): called...\n", pthread_self());
+#endif
+
+ /* close all master connections that remain open... */
+ for (nd = 0; nd < nd_table_.node_count; nd++)
+ if (nd_table_.node[nd].node_type == MB_MASTER_NODE)
+ if (nd_table_.node[nd].close_on_silence > 0)
+ /* node is is being used for a master device,
+ * and wishes to be closed... ...so we close it!
+ */
+ close_connection(nd);
+
+ return 0;
+}
+
+
+
+/******************************/
+/** **/
+/** Shutdown the Library **/
+/** **/
+/******************************/
+
+int modbus_tcp_done(void) {
+ int i;
+
+ modbus_tcp_init_counter--;
+ if (modbus_tcp_init_counter != 0) return 0; /* ignore this request */
+
+ /* close all the connections... */
+ for (i = 0; i < nd_table_.node_count; i++)
+ modbus_tcp_close(i);
+
+ /* Free memory... */
+ nd_table_done(&nd_table_);
+
+ return 0;
+}
+
+
+
+
+double modbus_tcp_get_min_timeout(int baud,
+ int parity,
+ int data_bits,
+ int stop_bits) {
+ return 0;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mb_tcp_private.h Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2002,2016 Mario de Sousa (msousa@fe.up.pt)
+ *
+ * This file is part of the Modbus library for Beremiz and matiec.
+ *
+ * This Modbus library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this Modbus library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is made available on the understanding that it will not be
+ * used in safety-critical situations without a full and competent review.
+ */
+
+
+
+#ifndef MODBUS_TCP_PRIVATE_H
+#define MODBUS_TCP_PRIVATE_H
+
+
+#include "mb_util.h"
+
+
+/* tcp port default configuration... */
+#define DEF_SERVICE "502" /* port used by modbus */
+#define DEF_PROTOCOL "tcp" /* protocol used by modbus tcp */
+#define DEF_TYPE SOCK_STREAM /* Quality of service required of the socket... */
+#define DEF_MAX_PENDING_CONNECTION_REQUESTS 5
+ /* maximum number of pending connection requests
+ * that have not yet been accept()'ed
+ */
+#define DEF_CLOSE_ON_SILENCE 1 /* Used only by master nodes.
+ * Flag indicating whether, by default, the connection
+ * to the slave device should be closed whenever the
+ * modbus_tcp_silence_init() function is called.
+ *
+ * 0 -> do not close connection
+ * >0 -> close connection
+ *
+ * The spec sugests that connections that will not
+ * be used for longer than 1 second should be closed.
+ * Even though we expect most connections to have
+ * silence intervals much shorted than 1 second, we
+ * decide to use the default of shuting down the
+ * connections because it is safer, and most other
+ * implementations seem to do the same.
+ * If we do not close we risk using up all the possible
+ * connections that the slave can simultaneouly handle,
+ * effectively locking out every other master that
+ * wishes to communicate with that same slave.
+ */
+
+ /* Since the receive buffer is also re-used to store the frame header,
+ * we set it to the larger of the two.
+ */
+#if TCP_HEADER_LENGTH > MAX_L2_FRAME_LENGTH
+#define RECV_BUFFER_SIZE TCP_HEADER_LENGTH
+#else
+#define RECV_BUFFER_SIZE MAX_L2_FRAME_LENGTH
+#endif
+
+
+
+
+#endif /* MODBUS_TCP_PRIVATE_H */
+
+
+
+
+
+
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mb_time_util.h Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2002,2016 Mario de Sousa (msousa@fe.up.pt)
+ *
+ * This file is part of the Modbus library for Beremiz and matiec.
+ *
+ * This Modbus library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this Modbus library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is made available on the understanding that it will not be
+ * used in safety-critical situations without a full and competent review.
+ */
+
+
+
+ /* Time handling functions used by the modbus protocols... */
+
+
+#ifndef __MODBUS_TIME_UTIL_H
+#define __MODBUS_TIME_UTIL_H
+
+
+/************************************/
+/** **/
+/** Time format conversion **/
+/** **/
+/************************************/
+
+/* Function to load a struct timeval correctly from a double. */
+static inline struct timeval d_to_timeval(double time) {
+ struct timeval tmp;
+
+ tmp.tv_sec = time;
+ tmp.tv_usec = 1e6*(time - tmp.tv_sec);
+ return tmp;
+}
+
+
+/* Function to load a struct timespec correctly from a double. */
+static inline struct timespec d_to_timespec(double time) {
+ struct timespec tmp;
+
+ tmp.tv_sec = time;
+ tmp.tv_nsec = 1e9*(time - tmp.tv_sec);
+ return tmp;
+}
+
+
+
+/* Function to ... */
+static inline struct timespec timespec_dif(struct timespec ts1, struct timespec ts2) {
+ struct timespec ts;
+
+ ts.tv_sec = ts1.tv_sec - ts2.tv_sec;
+ if(ts1.tv_nsec > ts2.tv_nsec) {
+ ts.tv_nsec = ts1.tv_nsec - ts2.tv_nsec;
+ } else {
+ ts.tv_nsec = 1000000000 + ts1.tv_nsec - ts2.tv_nsec;
+ ts.tv_sec--;
+ }
+
+ if (ts.tv_sec < 0)
+ ts.tv_sec = ts.tv_nsec = 0;
+
+ return ts;
+}
+
+/* Function to ... */
+static inline struct timespec timespec_add(struct timespec ts1, struct timespec ts2) {
+ struct timespec ts;
+
+ ts.tv_sec = ts1.tv_sec + ts2.tv_sec;
+ ts.tv_nsec = ts1.tv_nsec + ts2.tv_nsec;
+ ts.tv_sec += ts.tv_nsec / 1000000000;
+ ts.tv_nsec = ts.tv_nsec % 1000000000;
+ return ts;
+}
+
+/* Function to convert a struct timespec to a struct timeval. */
+static inline struct timeval timespec_to_timeval(struct timespec ts) {
+ struct timeval tv;
+
+ tv.tv_sec = ts.tv_sec;
+ tv.tv_usec = ts.tv_nsec/1000;
+ return tv;
+}
+
+
+/*
+ * NOTE: clock_gettime() is rather expensive, between 7000 and 7500 clock
+ * cycles (measured with rdtsc on an Intel Pentium)
+ * gettimeofday() is half as expensive (3000 to 3500 clock cycles),
+ * but is not POSIX compliant... :-(
+ * Nevertheless this is peanuts (20 us on a 350 MHz cpu) compared to
+ * the timescales required to read a modbus frame over a serial bus
+ * (aprox. 10 ms for a 10 byte frame on a 9600 baud bus!)
+ */
+static inline struct timespec timespec_add_curtime(struct timespec ts) {
+ struct timespec res = {.tv_sec = 0, .tv_nsec = 0};
+
+ /* is ts = 0 also return 0 !! */
+ if ((ts.tv_sec != 0) || (ts.tv_nsec != 0))
+ if (clock_gettime(CLOCK_MONOTONIC, &res) >= 0)
+ res = timespec_add(res, ts);
+ return res;
+}
+
+/************************************/
+/** **/
+/** select() with absolute timeout **/
+/** **/
+/************************************/
+
+
+
+
+/* My private version of select using an absolute timeout, instead of the
+ * usual relative timeout.
+ *
+ * NOTE: Ususal select semantics for (a: end_time == NULL) and
+ * (b: *end_time == 0) also apply.
+ *
+ * (a) Indefinite timeout
+ * (b) Try once, and and quit if no data available.
+ */
+/* Returns: -1 on error
+ * 0 on timeout
+ * >0 on success
+ */
+static int my_select(int fd, fd_set *rfds, fd_set *wfds, const struct timespec *end_time) {
+
+ int res;
+ struct timespec cur_time;
+ struct timeval timeout, *tv_ptr;
+ fd_set tmp_rfds, *tmp_rfds_ptr, tmp_wfds, *tmp_wfds_ptr;
+
+ tmp_rfds_ptr = NULL;
+ tmp_wfds_ptr = NULL;
+ if (rfds != NULL) tmp_rfds_ptr = &tmp_rfds;
+ if (wfds != NULL) tmp_wfds_ptr = &tmp_wfds;
+
+ /*============================*
+ * wait for data availability *
+ *============================*/
+ do {
+ if (rfds != NULL) tmp_rfds = *rfds;
+ if (wfds != NULL) tmp_wfds = *wfds;
+ /* NOTE: To do the timeout correctly we would have to revert to timers
+ * and asociated signals. That is not very thread friendly, and is
+ * probably too much of a hassle trying to figure out which signal
+ * to use. What if we don't have any free signals?
+ *
+ * The following solution is not correct, as it includes a race
+ * condition. The following five lines of code should really
+ * be atomic!
+ *
+ * NOTE: see also the timeout related comment in the
+ * modbus_tcp_read() function!
+ */
+ if (end_time == NULL) {
+ tv_ptr = NULL;
+ } else {
+ tv_ptr = &timeout;
+ if ((end_time->tv_sec == 0) && (end_time->tv_nsec == 0)) {
+ timeout.tv_sec = timeout.tv_usec = 0;
+ } else {
+ /* ATOMIC - start */
+ if (clock_gettime(CLOCK_MONOTONIC, &cur_time) < 0)
+ return -1;
+ timeout = timespec_to_timeval(timespec_dif(*end_time, cur_time));
+ }
+ }
+
+ res = select(fd, tmp_rfds_ptr, tmp_wfds_ptr, NULL, tv_ptr);
+ /* ATOMIC - end */
+
+#ifdef DEBUG
+ {int i;
+ if (tmp_rfds_ptr != NULL)
+ for (i = 0; i < fd; i++)
+ if (FD_ISSET(i, tmp_rfds_ptr))
+ fprintf(stderr,"fd=%d is ready for reading\n", i);
+ if (tmp_wfds_ptr != NULL)
+ for (i = 0; i < fd; i++)
+ if (FD_ISSET(i, tmp_wfds_ptr))
+ fprintf(stderr,"fd=%d is ready for writing\n", i);
+ }
+#endif
+ if (res == 0) {
+#ifdef DEBUG
+ printf("Comms time out\n");
+#endif
+ return 0;
+ }
+ if ((res < 0) && (errno != EINTR)) {
+ return -1;
+ }
+ } while (res <= 0);
+
+ if (rfds != NULL) *rfds = tmp_rfds;
+ if (wfds != NULL) *wfds = tmp_wfds;
+ return res;
+}
+
+
+
+
+
+
+#endif /* __MODBUS_TIME_UTIL_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mb_types.h Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2002,2016 Mario de Sousa (msousa@fe.up.pt)
+ *
+ * This file is part of the Modbus library for Beremiz and matiec.
+ *
+ * This Modbus library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this Modbus library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is made available on the understanding that it will not be
+ * used in safety-critical situations without a full and competent review.
+ */
+
+
+
+#ifndef __MB_TYPES_H
+#define __MB_TYPES_H
+
+#ifndef __PLC_TYPES_H
+ /* if we have already included the MatPLC's type definitions, we don't need to delare the types ourselves... */
+
+
+/* Tell the stdint.h file we want the limits of the data types defined. */
+/* If being compiled by a C++ compiler, this is required! */
+/* If being compiled by a C compiler, this is ignored! */
+#define __STDC_LIMIT_MACROS
+#include <stdint.h>
+
+
+
+/* We use the _leastX_t versions of the data types as these are guaranteed
+ * to be the exact size we want.
+ * The int8_t, etc..., may have been defined to be the same as the
+ * _fastX_t version, which may take up more space than what is really wanted
+ * in order as to speed up memory access.
+ */
+typedef uint_least64_t u64; /* 64-bit unsigned integer */
+typedef int_least64_t i64; /* 64-bit signed integer */
+
+typedef uint_least32_t u32; /* 32-bit unsigned integer */
+typedef int_least32_t i32; /* 32-bit signed integer */
+
+typedef uint_least16_t u16; /* 16-bit unsigned integer */
+typedef int_least16_t i16; /* 16-bit signed integer */
+
+typedef uint_least8_t u8; /* 8-bit unsigned integer */
+typedef int_least8_t i8; /* 8-bit signed integer */
+
+
+
+
+#define u64_MAX UINT_LEAST64_MAX
+#define u64_MIN UINT_LEAST64_MIN
+#define i64_MAX INT_LEAST64_MAX
+#define i64_MIN INT_LEAST64_MIN
+
+#define u32_MAX UINT_LEAST32_MAX
+#define u32_MIN UINT_LEAST32_MIN
+#define i32_MAX INT_LEAST32_MAX
+#define i32_MIN INT_LEAST32_MIN
+
+#define u16_MAX UINT_LEAST16_MAX
+#define u16_MIN UINT_LEAST16_MIN
+#define i16_MAX INT_LEAST16_MAX
+#define i16_MIN INT_LEAST16_MIN
+
+#define u8_MAX UINT_LEAST8_MAX
+#define u8_MIN UINT_LEAST8_MIN
+#define i8_MAX INT_LEAST8_MAX
+#define i8_MIN INT_LEAST8_MIN
+
+
+#endif /* __PLC_TYPES_H */
+
+#endif /* __MB_TYPES_H */
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mb_util.h Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2002,2016 Mario de Sousa (msousa@fe.up.pt)
+ *
+ * This file is part of the Modbus library for Beremiz and matiec.
+ *
+ * This Modbus library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this Modbus library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is made available on the understanding that it will not be
+ * used in safety-critical situations without a full and competent review.
+ */
+
+
+
+#ifndef MB_UTIL_H
+#define MB_UTIL_H
+
+/* This file has constants related to the modbus protocol */
+/*
+ * Some of these constants are specific to the layer two protocols
+ * (i.e. master and slave), while others are specific of the
+ * layer one protocols (i.e. rtu, ascii, tcp).
+ *
+ * a) Unfortunately, due to the nature of the modbus protocol, that does not
+ * include a frame size field in the layer 1 frame (see note 1), and the
+ * fact that we are implementing it at the user level, the implementation
+ * of some layer 1 protocols need to know the content of the layer 2 protocol
+ * in order to determine the size of the frame.
+ *
+ * b) The layer two message formats are in fact the same, just reversing the role
+ * being played (master or slave).
+ *
+ * Bothe a) and b) mean we need the same modbus protocol constants in several files.
+ * It ends up making more sense to put them all together in a single file, which
+ * makes updating easier, even though we are trying to strictly seperate the layer 1
+ * and layer 2 protocols.
+ *
+ *
+ *
+ * Notes:
+ * (1) There is no layer 1 field with the frame size, nevertheless this
+ * size can be determined indirectly due to timing restrictions on the rtu
+ * protocol. Unfortunately, due to the fact that we are implementing
+ * it at the user level, we are not guaranteed to detect these timings
+ * correctly, and therefore have to rely on layer 2 protocol info to
+ * determine the frame size.
+ * For the ascii protocol, the frame size is determined indirectly by
+ * a frame header and tail, so we do not use layer 2 protocol info.
+ */
+
+
+ /* Layer 2 Frame Structure... */
+ /* Valid for both master and slave protocols */
+#define L2_FRAME_HEADER_LENGTH 6
+#define L2_FRAME_BYTECOUNT_LENGTH 1
+#define L2_FRAME_DATABYTES_LENGTH 255
+#define MAX_L2_FRAME_LENGTH (L2_FRAME_HEADER_LENGTH + L2_FRAME_BYTECOUNT_LENGTH + \
+ L2_FRAME_DATABYTES_LENGTH)
+
+#define L2_FRAME_SLAVEID_OFS 0
+#define L2_FRAME_FUNCTION_OFS 1
+
+ /* Layer 1 - Ascii Frame sizes... */
+#define L2_TO_ASC_CODING 2 /* number of ascii bytes used to code a Layer 2 frame byte */
+#define ASC_FRAME_HEADER_LENGTH 1
+#define ASC_FRAME_HEADER ':'
+#define ASC_FRAME_TAIL_LENGTH 2
+#define ASC_FRAME_TAIL_0 '\13' /* 'CR' */
+#define ASC_FRAME_TAIL_1 '\10' /* 'LF' */
+#define ASC_FRAME_LRC_LENGTH 2
+
+ /* Layer 1 - RTU Frame sizes... */
+#define RTU_FRAME_CRC_LENGTH 2
+
+ /* Layer 1 - TCP Frame sizes... */
+#define TCP_HEADER_LENGTH 6
+
+ /* Global Frame sizes */
+#define MAX_RTU_FRAME_LENGTH MAX_L2_FRAME_LENGTH + RTU_FRAME_CRC_LENGTH
+#define MAX_ASC_FRAME_LENGTH ((MAX_L2_FRAME_LENGTH * L2_TO_ASC_CODING) + \
+ ASC_FRAME_HEADER_LENGTH + ASC_FRAME_TAIL_LENGTH + \
+ ASC_FRAME_LRC_LENGTH)
+
+
+/* Modbus Exception codes */
+#define ERR_ILLEGAL_FUNCTION 0x01
+#define ERR_ILLEGAL_DATA_ADDRESS 0x02
+#define ERR_ILLEGAL_DATA_VALUE 0x03
+#define ERR_SLAVE_DEVICE_FAILURE 0x04
+#define ERR_ACKNOWLEDGE 0x05
+#define ERR_SLAVE_DEVICE_BUSY 0x06
+#define ERR_NEGATIVE_ACKNOWLEDGE 0x07
+#define ERR_MEMORY_PARITY_ERROR 0x08
+#define ERR_GATEWAY_PATH_UNAVAILABLE 0x0A
+#define ERR_GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND 0x0B
+
+
+
+#endif /* MB_UTIL_H */
+
+
+
+
+
+
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/sin_util.c Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2016 Mario de Sousa (msousa@fe.up.pt)
+ *
+ * This file is part of the Modbus library for Beremiz and matiec.
+ *
+ * This Modbus library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this Modbus library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is made available on the understanding that it will not be
+ * used in safety-critical situations without a full and competent review.
+ */
+
+
+/* sin_util.c */
+
+#include "sin_util.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h> // gethostbyname(), ...
+#include <errno.h> // errno
+#include <stdlib.h> // strtoll()
+#include <ctype.h> // isspace()
+#include <string.h> // memcpy(), memset()
+#include <strings.h> // strcasecmp()
+#include <stdio.h> // perror()
+
+#ifndef INADDR_NONE
+#define INADDR_NONE 0xffffffff
+#endif
+
+/* Last time I (msousa) checked, this was not being defined in QNX */
+#ifndef socklen_t
+typedef unsigned int socklen_t;
+#endif
+
+
+static inline int str_is_whitespace(const char *str) {
+ const char *s;
+ for (s = str; *s; s++) if (!isspace(*s)) return 0; // non whitespace char found
+ return 1; // all whitespace, or empty ""
+}
+
+/* Convert a string to a number, allowing for leading and trailing whitespace */
+/* Number may be in decimal, hexadecimal (leading '0x') or octal (leading '0') format */
+static long long str_to_portnum(const char *str) {
+ long long port = 0;
+ char *errstr;
+ #define PORT_MIN 0
+ #define PORT_MAX 65535
+ errno = 0; // strtoll() does not set errno to 0 on success!!
+ port = strtoll(str, &errstr, 0 /* accept base 8, 10 and 16 */);
+ if (str == errstr) return -1; // not a number
+ if (errno == ERANGE) return -2; // out of range
+ if (!str_is_whitespace(errstr)) return -1; // not a number (has trailing characters)
+ if ((port < PORT_MIN) || (port > PORT_MAX)) return -2; // out of range
+ return (port);
+}
+
+
+
+static int gettypebyname(const char *protocol) {
+ if (strcasecmp(protocol, "tcp") == 0) return SOCK_STREAM;
+ if (strcasecmp(protocol, "udp") == 0) return SOCK_DGRAM;
+ if (strcasecmp(protocol, "raw") == 0) return SOCK_RAW;
+ return SOCK_PACKET; // a deprecated service type, we use here as error.
+}
+
+
+static int getportbyname(in_port_t *port, const char *service, const char *protocol) {
+ struct servent *se;
+ int32_t tmp;
+ // if service is NULL, "" or string of whitespace, then set port to 0, and return -1.
+ // Used when binding to a random port on the local host...
+ if ( port == NULL) { return -1;}
+ if ( service == NULL) {*port = 0; return -1;}
+ if (str_is_whitespace(service)) {*port = 0; return -1;}
+ if ((se = getservbyname(service, protocol)) != NULL) {*port = se->s_port; return 0;}
+ if ((tmp = str_to_portnum(service)) >= 0) {*port = htons((uint16_t)tmp); return 0;}
+ return -2;
+}
+
+
+static int getipbyname(struct in_addr *ip_addr, const char *host) {
+ struct hostent *he;
+ // if host is NULL, "", or "*", then set ip_addr to INADDR_ANY, and return -1.
+ // Used when binding to all interfaces on the local host...
+ if ( host == NULL) {ip_addr->s_addr = INADDR_ANY; return -1;}
+ if (str_is_whitespace(host)) {ip_addr->s_addr = INADDR_ANY; return -1;}
+ if (strcmp(host, "*") == 0) {ip_addr->s_addr = INADDR_ANY; return -1;}
+ if ((he = gethostbyname(host)) != NULL) {memcpy((char *)ip_addr, he->h_addr, he->h_length); return 0;}
+ if ((ip_addr->s_addr = inet_addr(host)) != INADDR_NONE) {return 0;}
+ return -2;
+}
+
+
+
+
+
+int sin_initaddr(struct sockaddr_in *sin,
+ const char *host, int allow_null_host, // 1 => allow host NULL, "" or "*" -> INADDR_ANY
+ const char *service, int allow_null_serv, // 1 => allow serivce NULL or "" -> port = 0
+ const char *protocol) {
+ int he = allow_null_host?-1:0;
+ int se = allow_null_serv?-1:0;
+
+ memset((void *)sin, 0, sizeof(sin));
+ sin->sin_family = AF_INET;
+
+ if (getportbyname(&(sin->sin_port), service, protocol) < se) return -1;
+ if (getipbyname (&(sin->sin_addr), host) < he) return -1;
+ return 0;
+}
+
+
+
+/* Create a socket for the IP protocol family, and connect to remote host. */
+int sin_connsock(const char *host, const char *service, const char *protocol) {
+ struct sockaddr_in sin;
+ int s, type;
+
+ if (sin_initaddr(&sin, host, 0, service, 0, protocol) < 0) return -1;
+ if ((type = gettypebyname(protocol)) == SOCK_PACKET) return -1;
+ /* create the socket */
+ if ((s = socket(PF_INET, type, 0)) < 0) {perror("socket()"); return -1;}
+ /* connect the socket */
+ if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) {perror("connect()"); return -1;}
+
+ return(s);
+}
+
+
+
+
+/* Create a socket for the IP protocol family, and bind to local host's port. */
+int sin_bindsock(const char *host, const char *service, const char *protocol) {
+ struct sockaddr_in sin;
+ int s, type;
+
+ // TODO allow random port... Needs new input parameter to function interface!
+ if (sin_initaddr(&sin, host, 1, service, 0, protocol) < 0) return -1;
+ if ((type = gettypebyname(protocol)) == SOCK_PACKET) return -1;
+ /* create the socket */
+ if ((s = socket(PF_INET, type, 0)) < 0) {perror("socket()"); return -1;}
+ /* bind the socket */
+ if (bind(s, (struct sockaddr *)&sin, sizeof (sin)) < 0) {perror("bind()"); return -1;}
+
+ return(s);
+}
+
+
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/sin_util.h Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2016 Mario de Sousa (msousa@fe.up.pt)
+ *
+ * This file is part of the Modbus library for Beremiz and matiec.
+ *
+ * This Modbus library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this Modbus library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is made available on the understanding that it will not be
+ * used in safety-critical situations without a full and competent review.
+ */
+
+
+#ifndef SIN_UTIL_H
+#define SIN_UTIL_H
+
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+
+
+/* Create a socket for the IP protocol family, and connect to remote host. */
+int sin_connsock(const char *host, const char *service, const char *protocol);
+
+/* Create a socket for the IP protocol family, and bind to local host's port. */
+int sin_bindsock(const char *host, const char *service, const char *protocol);
+
+/* Initialize a in_addr structure */
+int sin_initaddr(struct sockaddr_in *sin,
+ const char *host, int allow_null_host, // 1 => allow host NULL, "" or "*" -> INADDR_ANY
+ const char *service, int allow_null_serv, // 1 => allow serivce NULL or "" -> port = 0
+ const char *protocol);
+#endif