Modbus

Initial commit.

2017-03-05, Mario de Sousa
ae252e0fd9b8
Parents
Children 59783e8ee3d2
Initial commit.
  • +674 -0
    COPYING
  • +165 -0
    COPYING.LESSER
  • +44 -0
    Makefile
  • +65 -0
    README
  • +91 -0
    mb_addr.h
  • +1784 -0
    mb_ascii.c
  • +88 -0
    mb_ascii_private.h
  • +169 -0
    mb_ds_util.h
  • +155 -0
    mb_layer1.h
  • +135 -0
    mb_layer1_prototypes.h
  • +1277 -0
    mb_master.c
  • +359 -0
    mb_master.h
  • +53 -0
    mb_master_private.h
  • +2116 -0
    mb_rtu.c
  • +172 -0
    mb_rtu_private.h
  • +845 -0
    mb_slave.c
  • +128 -0
    mb_slave.h
  • +161 -0
    mb_slave_and_master.c
  • +43 -0
    mb_slave_and_master.h
  • +53 -0
    mb_slave_private.h
  • +1705 -0
    mb_tcp.c
  • +81 -0
    mb_tcp_private.h
  • +219 -0
    mb_time_util.h
  • +85 -0
    mb_types.h
  • +117 -0
    mb_util.h
  • +164 -0
    sin_util.c
  • +46 -0
    sin_util.h
  • --- /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