--- /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. + 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 + 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 + 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 + "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 + 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. + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source + 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 + The Corresponding Source for a work in source code form is that + 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 + 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 + 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 + 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 + 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, + 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 + 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 + 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. + "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. + 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. + 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 + 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 + 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 + 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 + 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 + "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 + 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 + 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 + 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 + 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 + 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 + 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 + 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.) + 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 \ 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 @@
+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 + ar cr libmb.a $(OBJ_FILES) + gcc -shared -fPIC -o libmb.so $(OBJ_FILES) + -rm -rf *.o libmb.a libmb.so +#get warnings, debugging information and optimization +CFLAGS = -Wall -Wpointer-arith -Wstrict-prototypes -Wwrite-strings +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 +#how to make things from other directories if they are missing +# gcc -MM -MG -I$(LLIB) *.c \ + | perl -pe 's/:/ 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 + 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 + 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_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. +#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 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; + int baud; /* plain baud rate, eg 2400; zero for the default 9600 */ + int parity; /* 0 for none, 1 for odd, 2 for even */ + int ignore_echo; /* 1 => ignore echo; 0 => do not ignore echo */ +typedef node_addr_rtu_t node_addr_ascii_t; + node_addr_ascii_t ascii; + node_addr_family_t naf; + node_addr_common_t addr; +#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 <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 <time.h> /* clock_gettime() */ +#include <limits.h> /* required for INT_MAX */ +#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: + value: ':' (i.e. '\0x3A') + 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' + value: Longitudinal Redundancy Check of data, excluding any headers, tails, + 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... ****/ +/**************************************************************/ +/**************************************************************/ +/*****************************************/ +/*****************************************/ +static inline void lrc_init(u8 *lrc) { +static inline void lrc_add_single(u8 *lrc, u8 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) { +/**************************************/ +/** Initialise a struct termios **/ +/**************************************/ +static int termios_init(struct termios *tios, + /* reset all the values... */ + /* NOTE: the following are initialised later on... + /* The minimum number of characters that should be received + * to satisfy a call to read(). + /* The maximum inter-arrival interval between two characters, + * 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. + /* 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 + * 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; + if (stop_bits == 1) tios->c_cflag &=~ CSTOPB; + else if (stop_bits == 2) tios->c_cflag |= CSTOPB; + 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; + /* 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! */ + 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; + if ((cfsetispeed(tios, baud_rate) < 0) || + (cfsetospeed(tios, baud_rate) < 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 *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'; + /* return number of bytes converted... */ +/* 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'; +/* 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)) + 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); +/************************************/ +/** 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 + * 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 + u8 lrc; /* the current value of the lrc, in binary format */ +/* 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 + if (size < SEND_BUF_MIN_LENGTH) + 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) { + 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); +/* 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 + if (lb_free_count(&buf->data_buf) < ASC_FRAME_LRC_LENGTH) + 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); +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 + if (lb_free_count(&buf->data_buf) < ASC_FRAME_HEADER_LENGTH) + /* add the ':' frame header */ + *lb_free(&buf->data_buf) = ASC_FRAME_HEADER; + lb_data_add(&buf->data_buf, ASC_FRAME_HEADER_LENGTH); +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 + if (lb_free_count(&buf->data_buf) < ASC_FRAME_TAIL_LENGTH) + /* 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); +/************************************/ +/** 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) + u8 bin_data_buf[RECV_BUF_BIN_BUF_SIZE]; + 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 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. +/* 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 + if (size < RECV_BUF_MIN_LENGTH) + 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) { + 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) { + 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 + 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; + buf->bin_data_free_ofs = 0; + /* 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 + 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! + /* 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 + 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; + /* we have found a frame with an lrc 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 + 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! + 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... + /* 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! + buf->bin_data_free_ofs = 0; + buf->frame_part = fp_header; + 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; +/* 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; +/* 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 */ + /* 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 + 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. + /* 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. + /* The old settings of the serial port, to be reset when the library is closed... */ + struct termios old_tty_settings_; + * 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... +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, + int parity_bits, start_bits, char_bits; + struct termios settings; + /* 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) + 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); + /* 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) + fprintf(stderr, "Out of memory: error initializing send buffer"); + /* 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) + fprintf(stderr, "Out of memory: error initializing receive buffer"); + /* open the serial port */ + if((nde->fd = open(node_addr->addr.ascii.device, O_RDWR | O_NOCTTY | O_NDELAY)) + fprintf(stderr, "Error opening device %s (errno=%d)", + node_addr->addr.ascii.device, errno); + if(tcgetattr(nde->fd, &nde->old_tty_settings_) < 0) { + fprintf(stderr, "Error reading device's %s original settings.", + node_addr->addr.ascii.device); + if(tcsetattr(nde->fd, TCSANOW, &settings) < 0) { + 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); + parity_bits = (node_addr->addr.ascii.parity == 0)?0: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); + printf("nd_entry_connect(): %s open\n", node_addr->addr.ascii.device ); + printf("nd_entry_connect(): returning fd=%d\n", nde->fd); + recv_buf_done(&nde->recv_buf_); + send_buf_done(&nde->send_buf_); + nde->fd = -1; /* set the node as free... */ +static int nd_entry_free(nd_entry_t *nde) { + /* 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_); +static inline int nd_entry_is_free(nd_entry_t *nde) { +/************************************/ +/** A data structure - nd table **/ +/************************************/ + /* the array of node descriptors, and current size... */ + int node_count; /* total number of nodes in the node[] array */ + * 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) { + 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) { + fprintf(stderr, "Out of memory: error initializing node address buffer"); + 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]); + return nd_count; /* number of succesfully created nodes! */ + * 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) { + /* 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) { + fprintf(stderr, ERRMSG_HEAD "Out of memory: error initializing node address buffer\n"); + /* 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]); + ndt->node_count += new_nd_count; + return new_nd_count; /* number of succesfully created nodes! */ +static inline nd_entry_t *nd_table_get_nd(nd_table_t *ndt, int nd) { + if ((nd < 0) || (nd >= ndt->node_count)) +static inline void nd_table_done(nd_table_t *ndt) { + /* close all the connections... */ + for (i = 0; i < ndt->node_count; i++) + nd_entry_free(&ndt->node[i]); + *ndt = (nd_table_t){.node=NULL, .node_count=0}; +static inline int nd_table_get_free_nd(nd_table_t *ndt) { + for (count = 0; count < ndt->node_count; count++) { + if (nd_entry_is_free(&ndt->node[count])) +static inline int nd_table_free_nd(nd_table_t *ndt, int nd) { + if ((nd < 0) || (nd >= ndt->node_count)) + 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, + const struct timespec *transmit_timeout + struct timeval timeout; + int res, bin_data_conv, send_retries; + frame_part_t frame_part; + /* check if nd is correct... */ + if ((nd_entry = nd_table_get_nd(&nd_table_, nd)) == NULL) + /* check if nd is initialzed... */ + 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 + * - 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 + * - 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_SET(nd_entry->fd, &rfds); + timeout = nd_entry->time_15_char_; + while ((res = select(nd_entry->fd+1, &rfds, NULL, NULL, &timeout)) != 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_; + /* some kind of error ocurred */ + /* we were not interrupted by a signal */ + /* We will be callind select() again. + * We need to reset the FD SET ! + 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 + tcflush(nd_entry->fd, TCIOFLUSH); /* flush the input & output streams */ + recv_buf_reset(&nd_entry->recv_buf_); /* reset the recv buffer */ + /********************** + **********************/ + 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) + if (frame_part == fp_body) { + res = send_buf_data_add(&nd_entry->send_buf_, data + bin_data_conv, data_length - bin_data_conv); + if (bin_data_conv == data_length) + if (frame_part == fp_lrc) { + if (send_buf_lrc_append(&nd_entry->send_buf_) >= 0) + if (frame_part == fp_tail) { + if (send_buf_tail_append(&nd_entry->send_buf_) >= 0) + /* 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 )) { + /* 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... +/* Print each character that has just been sent on the bus */ + printf("bytes sent -> ["); + for(i = 0; i < res; i++) + printf("%c", send_buf_data(&nd_entry->send_buf_)[i]); + 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! */ + } /* 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... + } /* while() MAIN LOOP */ + /* maximum retries exceeded */ +/**************************************************************/ +/**************************************************************/ +/**** Receiving Modbus ASCII Frames ****/ +/**************************************************************/ +/**************************************************************/ +/************************************/ +/************************************/ +/* A function to read a valid frame off the modbus ascii bus. + * - 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 +static inline int read_frame(nd_entry_t *nd_entry, + struct timespec *end_time) + /* temporary variables... */ + /* start by deleting any previously converted frames... */ + recv_buf_frame_purge(&nd_entry->recv_buf_); + printf("bytes read -> <"); + /* 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, + 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_SET(nd_entry->fd, &rfds); + {int sel_res = my_select(nd_entry->fd + 1, &rfds, NULL, end_time); + /* 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_)); + {/* display the hex code of each character received */ + for (i=0; i < res; i++) + printf("%c", recv_buf_free(&nd_entry->recv_buf_)[i]); + recv_buf_data_add(&nd_entry->recv_buf_, res); + 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 + * return value: The length (in bytes) of the valid frame, +int modbus_ascii_read(int *nd, + const struct timespec *recv_timeout) { + struct timespec end_time, *ts_ptr; + 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; + /* Check input parameters... */ + if (recv_data_ptr == NULL) + recv_data_ptr = &local_recv_data_ptr; + if ((send_data == NULL) && (send_length != 0)) + /* check if nd is correct... */ + if ((nd_entry = nd_table_get_nd(&nd_table_, *nd)) == NULL) + /* check if nd is initialzed... */ + /* 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! + if (recv_timeout != NULL) { + *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! + 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... */ + 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) + /* read in another frame. */ + /* 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]) + /* The frame we have received is not acceptable... + * Let's read a new frame. + /* error reading response! */ + /* Return the error returned by read_frame! */ +/**************************************************************/ +/**************************************************************/ +/**** Initialising and Shutting Down Library ****/ +/**************************************************************/ +/**************************************************************/ +/******************************/ +/** Load Default Values **/ +/******************************/ +static void set_defaults(int *baud, + /* Set the default values, if required... */ + *data_bits = DEF_DATA_BITS; + *stop_bits = DEF_STOP_BITS_NOP; /* no parity */ + *stop_bits = DEF_STOP_BITS_PAR; /* parity used */ +/******************************/ +/** Initialise Library **/ +/******************************/ +int modbus_ascii_init(int nd_count, + 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"); + /* check input parameters...*/ + if (extra_bytes != NULL) + // Not the corect value for this layer. + // What we set it to in case this layer is not used! + if (extra_bytes == NULL) + /* initialise nd table... */ + if (nd_table_init(&nd_table_, nd_count) < 0) + /* remember the optimization choice for later reference... */ + printf("modbus_asc_init(): returning succesfuly...\n"); + if (extra_bytes != NULL) + *extra_bytes = 0; // The value we set this to in case of error. +/******************************/ +/** 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) { + 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); + /* Check for valid address family */ + if (node_addr.naf != naf_ascii) + /* wrong address type... */ + /* 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... */ + if ((nd_entry = nd_table_get_nd(&nd_table_, node_descriptor)) == NULL) + /* strange, this should not occur... */ + /* 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) + printf("modbus_ascii_connect(): %s open\n", node_addr.addr.ascii.device ); + printf("modbus_ascii_connect(): returning nd=%d\n", node_descriptor); + return node_descriptor; +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_); +/******************************/ +/******************************/ +int modbus_ascii_silence_init(void) { +/******************************/ +/******************************/ +double modbus_ascii_get_min_timeout(int baud, + int parity_bits, start_bits, char_bits; + set_defaults(&baud, &parity, &data_bits, &stop_bits); + parity_bits = (parity == 0)?0: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 +/* serial port default configuration... */ +#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 + * - 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... +#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) +/* 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 +#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 +#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 /* 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 + * 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 + * 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 + * to remove the data from the data structure + 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? */ +/* NOTE: lb = Linear Buffer */ +static inline u8 *lb_init(lb_buf_t *buf, int size, int max_data_start) { + if (max_data_start >= size) + max_data_start = size - 1; + buf->max_data_start = max_data_start; + buf->data = (u8 *)malloc(size); +static inline void lb_done(lb_buf_t *buf) { +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. +#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_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_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_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) + #include "mb_layer1_prototypes.h" +#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, + 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 + * 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 +int modbus_read(int *nd, /* node descriptor */ + const struct timespec *recv_timeout); +int modbus_init(int nd_count, /* maximum number of nodes... */ + /* shutdown the library...*/ +/* 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 + * 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, --- /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. +#include <fcntl.h> /* File control definitions */ +#include <stdio.h> /* Standard input/output */ +#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_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; + * 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 +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, + * 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); + tmp.u16 = mb_hton(count); +/* 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, + const struct timespec *response_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--) { + 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'. + 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]; + /* success! Let's get out of the send retry loop... */ + return response_length; + /* reached the end of the retries... */ +/**************************************/ +/**************************************/ +/** 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() +static int read_bits(u8 function, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex) { + u8 packet[QUERY_BUFFER_SIZE]; + 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++ ) { + for( bit = 0x01; (bit & 0xff) && (coils_processed < count); ) { + dest[dest_pos] = (temp & bit)?1:0; + 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, + u16 count, /* number of bits !! */ + const struct timespec *response_timeout) { + u8 packet[QUERY_BUFFER_SIZE]; + 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; + /* 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, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex) { + if( count > MAX_READ_BITS ) { + fprintf( stderr, "Too many coils requested.\n" ); + 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, + const struct timespec *response_timeout) { + if( count > MAX_READ_BITS ) { + fprintf( stderr, "Too many coils requested.\n" ); + 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, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex) { + if( count > MAX_READ_BITS ) { + fprintf( stderr, "Too many coils requested.\n" ); + 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, + const struct timespec *response_timeout) { + if( count > MAX_READ_BITS ) { + fprintf( stderr, "Too many coils requested.\n" ); + 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() +static int read_registers(u8 function, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex) { + u8 packet[QUERY_BUFFER_SIZE]; + 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*/ + 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, + const struct timespec *response_timeout) { + u8 packet[QUERY_BUFFER_SIZE]; + 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; + 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]; +/* 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, + const struct timespec *response_timeout) { + u8 packet[QUERY_BUFFER_SIZE]; + 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; + 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); + /* 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, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex) { + if( count > MAX_READ_REGS ) { + fprintf( stderr, "Too many registers requested.\n" ); + 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, + const struct timespec *response_timeout) { + if( count > MAX_READ_REGS ) { + fprintf( stderr, "Too many registers requested.\n" ); + 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, + const struct timespec *response_timeout) { + if( count > MAX_READ_REGS ) { + fprintf( stderr, "Too many registers requested.\n" ); + 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, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex) { + if( count > MAX_READ_REGS ) { + fprintf( stderr, "Too many input registers requested.\n" ); + 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, + const struct timespec *response_timeout) { + if( count > MAX_READ_REGS ) { + fprintf( stderr, "Too many input registers requested.\n" ); + 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, + const struct timespec *response_timeout) { + if( count > MAX_READ_REGS ) { + fprintf( stderr, "Too many input registers requested.\n" ); + 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() +static int set_single(u8 function, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex) { + u8 packet[QUERY_BUFFER_SIZE]; + 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 response_length; +/* FUNCTION 0x05 - Force Single Coil */ +inline int write_output_bit(u8 slave, + 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, + 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, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex) { + int data_array_pos = 0; + int query_length, response_length; + u8 packet[QUERY_BUFFER_SIZE]; + if( coil_count > MAX_WRITE_COILS ) { + coil_count = MAX_WRITE_COILS; + fprintf( stderr, "Writing to too many coils.\n" ); + 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); + 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;} + 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, + const struct timespec *response_timeout) { + int org_pos, byte_count, i; + int query_length, response_length; + u8 packet[QUERY_BUFFER_SIZE]; + if( coil_count > MAX_WRITE_COILS ) { + coil_count = MAX_WRITE_COILS; + fprintf( stderr, "Writing to too many coils.\n" ); + 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; + 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, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex) { + int i, query_length, response_length; + u8 packet[QUERY_BUFFER_SIZE]; + if( reg_count > MAX_WRITE_REGS ) { + reg_count = MAX_WRITE_REGS; + fprintf( stderr, "Trying to write to too many registers.\n" ); + 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, + /* number of 16 bit registers packed in the u32 array! */ + const struct timespec *response_timeout) { + int i, query_length, response_length; + u8 packet_[QUERY_BUFFER_SIZE]; + u8 *packet = packet_; /* remove the const'ness of packet_ */ + if( reg_count > MAX_WRITE_REGS ) { + reg_count = MAX_WRITE_REGS; + fprintf( stderr, "Trying to write to too many registers.\n" ); + /* 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. + 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) { + fprintf(stderr, "mb_master_init__(extra_bytes=%d), QUERY_BUFFER_SIZE=%d\n", extra_bytes, QUERY_BUFFER_SIZE); + buff_extra_bytes_ = extra_bytes; +/* Shut down the Modbus Master Layer */ +int mb_master_done__(void) { +int mb_master_init(int nd_count) { + fprintf( stderr, "mb_master_init()\n"); + fprintf( stderr, "creating %d nodes\n", nd_count); + /* initialise layer 1 library */ + if (modbus_init(nd_count, DEF_OPTIMIZATION, &extra_bytes) < 0) + /* initialise this library */ + if (mb_master_init__(extra_bytes) < 0) +int mb_master_done(void) { +/* 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. + * 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) { + fprintf( stderr, "mb_master_tcp connect()\n"); + /* call layer 1 library */ + switch(node_addr.naf) { + res = modbus_tcp_connect(node_addr); + if (res >= 0) res = res*4 + 0 /* offset into fptr_ with TCP functions */; + res = modbus_rtu_connect(node_addr); + if (res >= 0) res = res*4 + 1 /* offset into fptr_ with RTU functions */; + res = modbus_ascii_connect(node_addr); + if (res >= 0) res = res*4 + 2 /* offset into fptr_ with ASCII functions */; +/* Shut down a connection to a remote server/slave */ +int mb_master_close(int fd) { + fprintf( stderr, "mb_master_close(): nd = %d\n", fd); + 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) { + fprintf( stderr, "mb_master_silence_init():\n"); + /* 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. +#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 + -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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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 +#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 <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 <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_HEAD "ModbusRTU: " +// #define DEBUG /* uncomment to see the data sent and received */ +#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 ****/ +/**************************************************************/ +/**************************************************************/ +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 +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) +#define u16_v(char_ptr) (*((u16 *)(&(char_ptr)))) +/**************************************/ +/** Initialise a termios struct **/ +/**************************************/ +static int termios_init(struct termios *tios, + /* reset all the values... */ + /* NOTE: the following are initialised later on... + /* The minimum number of characters that should be received + * to satisfy a call to read(). + /* The maximum inter-arrival interval between two characters, + * 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. + /* 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 + * 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; + if (stop_bits == 1) tios->c_cflag &=~ CSTOPB; + else if (stop_bits == 2) tios->c_cflag |= CSTOPB; + 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; + /* 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! */ + 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; + if ((cfsetispeed(tios, baud_rate) < 0) || + (cfsetospeed(tios, baud_rate) < 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 + * - 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 + * - 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! + * 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. */ + * Used in the call to search_for_frame() as the history parameter! + int frame_search_history; +/* 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 */ + /* 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 + /* 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(). + /* The old settings of the serial port, to be reset when the library is closed... */ + struct termios old_tty_settings_; + * 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... +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, + int parity_bits, start_bits, char_bits; + struct termios settings; + /* 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) + 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); + /* 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) + fprintf(stderr, ERRMSG_HEAD "Out of memory: error initializing receive buffer\n"); + /* open the serial port */ + if((nde->fd = open(node_addr->addr.rtu.device, O_RDWR | O_NOCTTY | O_NDELAY)) + fprintf(stderr, ERRMSG_HEAD "Error opening device %s\n", + node_addr->addr.rtu.device); + if(tcgetattr(nde->fd, &nde->old_tty_settings_) < 0) { + fprintf(stderr, ERRMSG_HEAD "Error reading device's %s original settings.\n", + node_addr->addr.rtu.device); + if(tcsetattr(nde->fd, TCSANOW, &settings) < 0) { + 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); + parity_bits = (node_addr->addr.rtu.parity == 0)?0: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); + 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); + recv_buf_done(&nde->recv_buf_); + nde->fd = -1; /* set the node as free... */ +static int nd_entry_free(nd_entry_t *nde) { + /* reset the tty device old settings... */ + tcsetattr(nde->fd, TCSANOW, &nde->old_tty_settings_); + fprintf(stderr, ERRMSG_HEAD "Error reconfiguring serial port to it's original settings.\n"); + recv_buf_done(&nde->recv_buf_); +static inline int nd_entry_is_free(nd_entry_t *nde) { +/************************************/ +/** A data structure - nd table **/ +/************************************/ + /* the array of node descriptors, and current size... */ + int node_count; /* total number of nodes in the node[] array */ + * 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) { + 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) { + fprintf(stderr, ERRMSG_HEAD "Out of memory: error initializing node address buffer\n"); + 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]); + return nd_count; /* number of succesfully created nodes! */ + * 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) { + /* 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) { + fprintf(stderr, ERRMSG_HEAD "Out of memory: error initializing node address buffer\n"); + /* 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]); + ndt->node_count += new_nd_count; + return new_nd_count; /* number of succesfully created nodes! */ +static inline nd_entry_t *nd_table_get_nd(nd_table_t *ndt, int nd) { + if ((nd < 0) || (nd >= ndt->node_count)) +static inline void nd_table_done(nd_table_t *ndt) { + /* close all the connections... */ + for (i = 0; i < ndt->node_count; i++) + nd_entry_free(&ndt->node[i]); + *ndt = (nd_table_t){.node=NULL, .node_count=0}; +static inline int nd_table_get_free_nd(nd_table_t *ndt) { + for (count = 0; count < ndt->node_count; count++) { + if (nd_entry_is_free(&ndt->node[count])) +static inline int nd_table_free_nd(nd_table_t *ndt, int nd) { + if ((nd < 0) || (nd >= ndt->node_count)) + 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. +/************************************/ +/** 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); +/************************************/ +/** A slow version of the **/ +/************************************/ +/* crc optimized for smallest memory footprint */ +static u16 crc_slow(u8 *buf, int cnt) + for (bit=1; bit<=8; bit++) { + * - since temp is unsigned, we are guaranteed a zero in MSbit; + * - if it were signed, the value placed in the MSbit would be +/************************************/ +/** A fast version of the **/ +/************************************/ +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 + fprintf(stderr, "\nInternal program error in file %s at line %d\n\n\n", __FILE__, __LINE__); + 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) { + if ((crc_fast_buf = (u8 *)malloc(256 * 2)) == NULL) + for (i = 0x00; i < 0x100; i++) { + tmp_crc = crc_slow(data, 2); + crc_fast_buf[2*i ] = lsb(tmp_crc); + crc_fast_buf[2*i + 1] = msb(tmp_crc); +static inline void crc_fast_done(void) { +/************************************/ +/** init() and done() functions **/ +/************************************/ +static inline int crc_init(optimization_t opt) { + if (crc_fast_init() < 0) + /* humour the compiler */ +static inline int crc_done(void) { + if (crc_calc == crc_fast) + crc_calc = DEF_CRC_FUNCTION; +/**************************************************************/ +/**************************************************************/ +/**** Sending of Modbus RTU Frames ****/ +/**************************************************************/ +/**************************************************************/ + * 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 + * 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, + const struct timespec *transmit_timeout + struct timeval timeout; + fprintf(stderr, "modbus_rtu_write(fd=%d) called...\n", nd); + /* check if nd is correct... */ + if ((nd_entry = nd_table_get_nd(&nd_table_, nd)) == NULL) + /* check if nd is initialzed... */ + /************************** + * append crc to frame... * + **************************/ + * The crc_write() function assumes that we have an extra + * RTU_FRAME_CRC_LENGTH free bytes at the end of the *data + * The caller of this function had better make sure he has + * allocated those extra bytes, or a segmentation fault will + * Please read on why we leave this as it is... + * 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 + * The only option that seems left over is to have the caller + * of this function allocate a few extra bytes. Let's hope he + crc_write(data, data_length); + data_length += RTU_FRAME_CRC_LENGTH; +/* Print the hex value of each character that is about to be + for(i = 0; i < data_length; i++) + fprintf(stderr, "[0x%2X]", data[i]); + /* 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 + * 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. + * - 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 + * - 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_SET(nd_entry->fd, &rfds); + timeout = nd_entry->time_35_char_; + while ((res = select(nd_entry->fd+1, &rfds, NULL, NULL, &timeout)) != 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! */ + /* some kind of error ocurred */ + /* we were not interrupted by a signal */ + /* We will be calling select() again. + * We need to reset the FD SET ! + 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 + tcflush(nd_entry->fd, TCIOFLUSH); /* flush the input & output streams */ + recv_buf_reset(&nd_entry->recv_buf_); /* reset the recv buffer */ + /********************** + **********************/ + /* 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 )) + /* 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 + } /* while() MAIN LOOP */ + /* maximum retries exceeded */ +/**************************************************************/ +/**************************************************************/ +/**** Receiving Modbus RTU Frames ****/ +/**************************************************************/ +/**************************************************************/ +#if MIN_FRAME_LENGTH < 2 +#error Modbus RTU frames have a minimum length larger than MIN_FRAME_LENGTH. +/************************************/ +/** Guess length of frame **/ +/************************************/ +/* 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, + /* The array containing the lengths of frames. */ + /* - query_frame_length[] + * - response_frame_length[] + i8 *frame_length_array) { + /* check consistency of input parameters... */ + if ((frame_data == NULL) || (frame_length_array == NULL) || (frame_data_length < 2)) + 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... */ + 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... */ + 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 /*HEADER*/ 6 + mb_ntoh(u16_v(frame_data[6])) + RTU_FRAME_CRC_LENGTH; + res = frame_length_array[function_code]; + if (frame_data_length >= 3) + return BYTE_COUNT_3_HEADER + frame_data[2] + RTU_FRAME_CRC_LENGTH; + if (frame_data_length >= 4) + return BYTE_COUNT_34_HEADER + mb_ntoh(u16_v(frame_data[2])) + RTU_FRAME_CRC_LENGTH; + if (frame_data_length >= 7) + return BYTE_COUNT_7_HEADER + frame_data[6] + RTU_FRAME_CRC_LENGTH; + if (frame_data_length >= 11) + return BYTE_COUNT_11_HEADER + frame_data[10] + RTU_FRAME_CRC_LENGTH; + return res + RTU_FRAME_CRC_LENGTH; + /* unknown frame length */ +/************************************/ +/** 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 query_length, resp_length; + /* *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)) + 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; + /* We have checked the CRC, and it is not a valid frame! */ + *search_history |= SFF_HIST_NO_FRAME; + /* 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; + /* 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)) + /* 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)) + *search_history |= SFF_HIST_NO_RESPONSE_FRAME; + /* Could not find valid 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 */); + /* keep ignoring bytes, until we find one == *slave_id, + while (lb_data_count(&(buf->data_buf)) != 0) { + if (*lb_data(&(buf->data_buf)) == *slave_id) + 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, + fprintf(stderr, "\n" ); + fprintf(stderr, "returning valid frame of %d bytes.\n", frame_length); + /* 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. + * - 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 + * 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 + * 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 + * 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 + * - 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 +static inline int read_frame(nd_entry_t *nd_entry, + struct timespec *end_time, + /* temporary variables... */ + struct timeval timeout; + recv_buf_t *recv_buf = &nd_entry->recv_buf_; + * 1 => we are reading in an aborted frame, so we must + * start ignoring bytes... + int found_aborted_frame; + /*===================================* + * 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); + /* 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_SET(nd_entry->fd, &rfds); + {int sel_res = my_select(nd_entry->fd + 1, &rfds, NULL, end_time); + /* 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_SET(nd_entry->fd, &rfds); + /* 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)); + {/* display the hex code of each character received */ + for (i=0; i < read_stat; i++) + fprintf(stderr, "<0x%2X>", *(lb_free(&recv_buf->data_buf) + i)); + 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); + /* 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 + * - 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 + timeout = nd_entry->time_15_char_; + while ((res = select(nd_entry->fd+1, &rfds, NULL, NULL, &timeout)) < 0) { + /* We will be calling select() again. + * We need to reset the FD SET ! + FD_SET(nd_entry->fd, &rfds); + 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 + * 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, + * 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 */ + /* humour the compiler... */ +/************************************/ +/** 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 + * return value: The length (in bytes) of the valid frame, +int modbus_rtu_read(int *nd, + 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; + /* Check input parameters... */ + if (recv_data_ptr == NULL) + recv_data_ptr = &local_recv_data_ptr; + if ((send_data == NULL) && (send_length != 0)) + /* check if nd is correct... */ + if ((nd_entry = nd_table_get_nd(&nd_table_, *nd)) == NULL) + /* check if nd is initialzed... */ + 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... */ + if (recv_timeout != NULL) { + *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 + 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... */ + 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) + /* read in another frame. */ + /* 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]) + /* The frame we have received is not acceptable... + * Let's read a new frame. + /* error reading response! */ + /* Return the error returned by read_frame! */ +/**************************************************************/ +/**************************************************************/ +/**** Initialising and Shutting Down Library ****/ +/**************************************************************/ +/**************************************************************/ +/******************************/ +/** Load Default Values **/ +/******************************/ +static void set_defaults(int *baud, + /* Set the default values, if required... */ + *data_bits = DEF_DATA_BITS; + *stop_bits = DEF_STOP_BITS_NOP; /* no parity */ + *stop_bits = DEF_STOP_BITS_PAR; /* parity used */ +/******************************/ +/** Initialise Library **/ +/******************************/ +int modbus_rtu_init(int nd_count, + 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"); + /* check input parameters...*/ + if (extra_bytes != NULL) + // Not the corect value for this layer. + // What we set it to in case this layer is not used! + if (extra_bytes == NULL) + if (crc_init(opt) < 0) { + fprintf(stderr, ERRMSG_HEAD "Out of memory: error initializing crc buffers\n"); + /* 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) + /* remember the optimization choice for later reference... */ + fprintf(stderr, "modbus_rtu_init(): returning succesfuly...\n"); + if (extra_bytes != NULL) + // Not the corect value for this layer. + // What we set it to in case of error! +/******************************/ +/** 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) { + 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); + /* Check for valid address family */ + if (node_addr.naf != naf_rtu) + /* wrong address type... */ + /* 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... */ + if ((nd_entry = nd_table_get_nd(&nd_table_, node_descriptor)) == NULL) + /* strange, this should not occur... */ + /* 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)); + fprintf(stderr, "modbus_rtu_connect(): calling nd_entry_connect()\n"); + if (nd_entry_connect(nd_entry, &node_addr, optimization_) < 0) + fprintf(stderr, "modbus_rtu_connect(): %s open\n", node_addr.addr.rtu.device); + fprintf(stderr, "modbus_rtu_connect(): returning nd=%d\n", node_descriptor); + return node_descriptor; + fprintf(stderr, "modbus_rtu_connect(): error!\n"); +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_); +/******************************/ +/******************************/ +int modbus_rtu_silence_init(void) { +/******************************/ +/******************************/ +double modbus_rtu_get_min_timeout(int baud, + int parity_bits, start_bits, char_bits; + set_defaults(&baud, &parity, &data_bits, &stop_bits); + parity_bits = (parity == 0)?0: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 */ +/* serial port default configuration... */ +#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 + * - the above are the retries at the layer1 level, + * higher layers may decide to retry for themselves! + /* 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) + /* 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 */ + * 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 + * The above length has been hardcoded into the frame_length() function + * (in file modbus_rtu.c) +#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. +#include <fcntl.h> /* File control definitions */ +#include <stdio.h> /* Standard input/output */ +#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_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; + * 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... */ + /* We have GCC, which should define __LITTLE_ENDIAN__ */ +# if defined(__LITTLE_ENDIAN__) +# define __BYTE_ORDER __LITTLE_ENDIAN +# define __BYTE_ORDER __BIG_ENDIAN +#endif /* __BYTE_ORDER */ +/* If we still don't know byte order, try to get it from <sys/param.h> */ +# if BYTE_ORDER == LITTLE_ENDIAN +# define __BYTE_ORDER __LITTLE_ENDIAN +# if BYTE_ORDER == BIG_ENDIAN +# define __BYTE_ORDER __BIG_ENDIAN +# endif /* BYTE_ORDER */ +#endif /* __BYTE_ORDER */ +# if __BYTE_ORDER == __LITTLE_ENDIAN +/**************************************************************/ +/* u16 conversion functions to use on little endian platforms */ +/**************************************************************/ +static inline u16 mb_hton(u16 w) { + tmp = ((w & 0xFF00) >> 0x08) | (tmp << 0x08); +#define mb_ntoh(a) mb_hton(a) +static inline void mb_hton_count(u16 *w, int count) { + for (i = 0; i < count; i++) { + /* swap the bytes around... + ((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) +# 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 ! */ +/********************************************************/ +/* 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 /* __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); +#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) +#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, + read_bits_callback_t read_bits_callback, + /* 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 = *resp_packet_ptr; + * 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); + 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); + 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, + read_words_callback_t read_words_callback, + /* 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 = *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); + 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); + 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) { + /* 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); + printf("handle_write_output_bit() called. slave=%d, function=%d, start_addr=%d\n", + query_packet[0], query_packet[1], start_addr); + // 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) { + /* 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); + printf("handle_write_output_word() called. slave=%d, function=%d, start_addr=%d\n", + query_packet[0], query_packet[1], start_addr); + /* 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) { + /* 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); + 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); + 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) { + /* 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; +int mb_slave_done__(void) +int mb_slave_init(int nd_count) { + fprintf( stderr, "mb_slave_init()\n"); + fprintf( stderr, "creating %d nodes\n", nd_count); + /* initialise layer 1 library */ + if (modbus_init(nd_count, DEF_OPTIMIZATION, &extra_bytes) < 0) + /* initialise this library */ + if (mb_slave_init__(extra_bytes) < 0) +int mb_slave_done(void) { +/***********************************************/ +/***********************************************/ +/** 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. + * 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) { + fprintf( stderr, "mb_slave_connect()\n"); + /* call layer 1 library */ + switch(node_addr.naf) { + res = modbus_tcp_listen(node_addr); + if (res >= 0) res = res*4 + 0 /* offset into fptr_ with TCP functions */; + res = modbus_rtu_listen(node_addr); + if (res >= 0) res = res*4 + 1 /* offset into fptr_ with RTU functions */; + res = modbus_ascii_listen(node_addr); + if (res >= 0) res = res*4 + 2 /* offset into fptr_ with ASCII functions */; +int mb_slave_close(int fd) { + fprintf( stderr, "mb_slave_close(): nd = %d\n", fd); + 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); +/***********************************************/ +/***********************************************/ +/***********************************************/ +/***********************************************/ +/* 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) { + u8 function, error_code = 0; + u8 *query_packet = NULL; + u8 resp_buffer_[RESP_BUFFER_SIZE]; + get_ttyfd(); /* declare the ttyfd variable!! */ + fprintf(stderr,"[%lu] mb_slave_run(): Called... fd=%d, ttyfd=%d\n", pthread_self(), fd, ttyfd); + /* will call one of modbus_tcp_read(), modbus_rtu_read(), modbus_ascii_read() */ + 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); + {/* display the hex code of each character received */ + 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]); + slave = query_packet[0]; + function = query_packet[1]; + * - 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_; + 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) */ + /* 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; + modbus_write(nd, resp_packet, resp_length, transaction_id, NULL /*transmit_timeout*/); + }; /* if not ignore request */ + /* humour the compiler... */ --- /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. +#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); +/***********************************************/ +/***********************************************/ +/***********************************************/ +/***********************************************/ +/* The following functions must return: + * -2 on attempt to read invalid address + * -1 on all other errors... + * Start_addr may start from 0, to 65535! + * In other words, we use 0 based addressing! + 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); +/* 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 <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_slave_private.h" +#include "mb_master_private.h" +//#define DEBUG /* uncomment to see the data sent and received */ +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_silence_init + ,&modbus_tcp_get_min_timeout + ,&modbus_rtu_silence_init + ,&modbus_rtu_get_min_timeout + ,&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; + fprintf( stderr, "mb_slave_and_master_init()\n"); + fprintf( stderr, "creating %d nodes\n", nd_count); + /* initialise layer 1 library */ + if (modbus_tcp_init (nd_count_tcp, DEF_OPTIMIZATION, &extra_bytes_tcp ) < 0) + if (modbus_rtu_init (nd_count_rtu, DEF_OPTIMIZATION, &extra_bytes_rtu ) < 0) + if (modbus_ascii_init(nd_count_ascii, DEF_OPTIMIZATION, &extra_bytes_ascii) < 0) + 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) + if (mb_master_init__(extra_bytes) < 0) +int mb_slave_and_master_done(void) { + res |= mb_slave_done__ (); + res |= mb_master_done__ (); + res |= modbus_ascii_done(); + res |= modbus_rtu_done (); + res |= modbus_tcp_done (); --- /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. +/************************************************************************ + 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 +#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 <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 <time.h> /* clock_gettime() */ +#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 <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_HEAD "Modbus/TCP: " +// #define DEBUG /* uncomment to see the data sent and received */ +/**************************************************************/ +/**************************************************************/ +/**** 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 +typedef sa_family_t nd_type_t; + int fd; /* socket descriptor == file descriptor */ + * 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, + 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 + * Flag will get reset every time we successfully + * establish a connection, so a message is once again generated + 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. +/* 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) +/* please make sure to lock the node table mutex before calling this function */ +static int nd_entry_done(nd_entry_t *nde) { + /* 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 */ +static int nd_table_done(nd_table_t *ndt) { + 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]); + pthread_mutex_unlock (&ndt->mutex); + pthread_mutex_destroy(&ndt->mutex); + *ndt = (nd_table_t){.node=NULL, .node_count=0, .free_node_count=0}; + * 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) { + 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) { + perror("pthread_mutex_lock()"); + fprintf(stderr, "[%lu] Unable to lock newly crated mutex while creating new node table!\n", pthread_self()); + pthread_mutex_destroy(&ndt->mutex); + /* initialise the node descriptor metadata array... */ + ndt->node = malloc(sizeof(nd_entry_t) * nd_count); + if (ndt->node == NULL) { + fprintf(stderr, "[%lu] Out of memory: error initializing node address buffer\n", pthread_self()); + fprintf(stderr, ERRMSG_HEAD "Out of memory. Error initializing node address buffer\n"); + pthread_mutex_unlock (&ndt->mutex); + pthread_mutex_destroy(&ndt->mutex); + /* 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); + ndt->node_count = count+1; + ndt->free_node_count = count+1; + ndt->node_count = nd_count; + ndt->free_node_count = nd_count; + pthread_mutex_unlock(&ndt->mutex); + return nd_count; /* number of succesfully created nodes! */ + * 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) { + if (ndt->node == NULL) { + /* Node table nt yet initialized => we must initialise the node table mutex... */ + pthread_mutex_init(&ndt->mutex, NULL); + 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) { + fprintf(stderr, "[%lu] Out of memory: error initializing node address buffer\n", pthread_self()); + fprintf(stderr, ERRMSG_HEAD "Out of memory. Error initializing node address buffer\n"); + pthread_mutex_unlock (&ndt->mutex); + pthread_mutex_destroy(&ndt->mutex); + /* 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); + 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! */ +static int nd_table_get_free_node(nd_table_t *ndt, nd_type_t nd_type) { + while (pthread_mutex_lock(&ndt->mutex) != 0) sched_yield(); + /* check for free nodes... */ + if (ndt->free_node_count <= 0) { + pthread_mutex_unlock(&ndt->mutex); + /* 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); + /* 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); +static void nd_table_close_node(nd_table_t *ndt, int nd) { + 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); + /* 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); +/**************************************************************/ +/**************************************************************/ +/**** 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) { + fprintf(stderr, ERRMSG_HEAD "Error configuring socket 'non-blocking' option.\n"); + /* configure the socket */ + /* set the TCP no delay flag. */ + if (setsockopt(socket_id, SOL_TCP, TCP_NODELAY, + (const void *)&bool_opt, sizeof(bool_opt)) + perror("setsockopt()"); + fprintf(stderr, ERRMSG_HEAD "Error configuring socket 'TCP no delay' option.\n"); + /* 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)) + perror("setsockopt()"); + fprintf(stderr, ERRMSG_HEAD "Error configuring socket 'IP low delay' option.\n"); + /* 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 + * We might just as well leave out the configuration of the socket +#define SOCK_BUF_SIZE 300 /* The size proposed in the Modbus TCP spec. */ + sock_buf_size = SOCK_BUF_SIZE; + if (setsockopt(socket_id, SOL_SOCKET, SO_SNDBUF, + (const void *)&sock_buf_size, sizeof(sock_buf_size)) + sock_buf_size = SOCK_BUF_SIZE; + if (setsockopt(socket_id, SOL_SOCKET, SO_RCVBUF, + (const void *)&sock_buf_size, sizeof(sock_buf_size)) +/************************************/ +/** 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; + printf("[%lu] open_connection(): called, nd = %d\n", pthread_self(), nd); + 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 */ + /* lets try to connect... */ + /* create the socket */ + if ((socket_id = socket(PF_INET, DEF_TYPE, 0 /* protocol_num */)) < 0) { + fprintf(stderr, "[%lu] Error creating socket\n", pthread_self()); + fprintf(stderr, ERRMSG_HEAD "Error creating socket\n"); + /* configure the socket - includes setting non-blocking option! */ + if (configure_socket(socket_id) < 0) { + /* 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... + goto success_exit; /* connected succesfully on first try! */ + 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 */ + struct timespec end_time, *et_ptr; + *et_ptr = timespec_add_curtime(*timeout); + 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! */ + 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; + printf("[%lu] open_connection(): returning...\n", pthread_self()); + if (nd_table_.node[nd].print_connect_error > 0) { + 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; +/* This function will accept a new connection request, and attribute it to a new node... */ +static inline int accept_connection(int nd) { + printf("[%lu] accept_connection(): called, nd = %d\n", pthread_self(), nd); + /* 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. + * 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) { + fprintf(stderr, ERRMSG_HEAD "Error while waiting for connection request from new client\n"); + /* error establishing new connection... */ + if ((new_nd = nd_table_get_free_node(&nd_table_, MB_SLAVE_NODE)) < 0) { + /* no available free nodes for the new connection... */ + /* 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. */ + /* 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; + printf("[%lu] accept_connection(): returning new_nd = %d\n", pthread_self(), 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); + close(nd_table_.node[nd].fd); + fprintf(stderr, ERRMSG_HEAD "Error closing socket\n"); + 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 +static inline u16 mb_hton(u16 h_value) { +static inline u16 mb_ntoh(u16 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; */ +#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 +static inline void build_header(u8 *header, + u16_v(header[0]) = mb_hton(transaction_id); + u16_v(header[4]) = mb_hton(byte_count); +static inline int check_header(u8 *header, + if ((header[2] != 0) || (header[3] != 0)) + *transaction_id = mb_ntoh(*(u16 *)(header + 0)); + *byte_count = mb_ntoh(*(u16 *)(header + 4)); + if (*byte_count > MAX_L2_FRAME_LENGTH) +/**************************************************************/ +/**************************************************************/ +/**** 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 */ + 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}, + struct msghdr msg = {NULL, 0, data_vector, data_vector_size, NULL, 0, 0}; + printf("[%lu] modbus_tcp_write(): called... nd=%d\n", pthread_self(), nd); + if ((nd >= nd_table_.node_count) || (nd < 0)) + /* invalid node descriptor... */ +// printf("[%lu] locking mutex...\n", pthread_self()); +// while (pthread_mutex_lock(&sendmsg_mutex) != 0); + /************************* + * prepare the header... * + *************************/ + build_header(header, transaction_id, data_length); +/* Print the hex value of each character that is about to be + 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]); + /****************************************** + * do we need to re-establish connection? * + ******************************************/ + if (open_connection(nd, transmit_timeout) < 0) { + fprintf(stderr, "[%lu] modbus_tcp_write(): could not establish connection...\n", pthread_self()); + fprintf(stderr, ERRMSG_HEAD "could not establish connection...\n"); + /********************** + **********************/ + /* TWO ALTERNATIVE IMPLEMENTATIONS !!! */ + res = write(nd_table_.node[nd].fd, header+bytes_sent, TCP_HEADER_LENGTH-bytes_sent); + if ((errno != EAGAIN ) && (errno != EINTR )) { + /* error sending message... */ + if (bytes_sent >= TCP_HEADER_LENGTH) { + res = write(nd_table_.node[nd].fd, data+bytes_sent, data_length-bytes_sent); + if ((errno != EAGAIN ) && (errno != EINTR )) { + /* error sending message... */ + if (bytes_sent >= data_length) { + /* query succesfully sent! */ + printf("[%lu] modbus_tcp_write(): sent %d bytes\n", pthread_self(), TCP_HEADER_LENGTH+data_length); + /********************** + **********************/ + /* 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 + * 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; + /* Please see the comment just above the main loop!! */ + res = sendmsg(nd_table_.node[nd].fd, &msg, 0); + if ((sendmsg_errno != EAGAIN ) && (sendmsg_errno != EINTR )) { + /* error sending message... */ + if (bytes_sent >= data_length + TCP_HEADER_LENGTH) { + /* query succesfully sent! */ + printf("[%lu] modbus_tcp_write(): sent %d bytes\n", pthread_self(), bytes_sent); +// pthread_mutex_unlock(&sendmsg_mutex); +// printf("[%lu] unlocked mutex...\n", pthread_self()); + /* adjust the data_vector... */ + if (res < data_vector[0].iov_len) { + u8* tmp = data_vector[0].iov_base; + data_vector[0].iov_len -= res; + data_vector[0].iov_base = tmp; + u8* tmp = data_vector[1].iov_base; + res -= data_vector[0].iov_len; + data_vector[0].iov_len = 0; + data_vector[1].iov_len -= res; + data_vector[1].iov_base = tmp; + /* humour the compiler... */ +// pthread_mutex_unlock(&sendmsg_mutex); +// printf("[%lu] unlocked mutex...\n", pthread_self()); +/**************************************************************/ +/**************************************************************/ +/**** 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 +static int read_bytes(int fd, + const struct timespec *end_time, + int data_already_available) + while (data_count < max_data_count) { + /*============================* + * wait for data availability * + *============================*/ + if (data_already_available == 0) { + sel_res = my_select(fd + 1, &rfds, NULL, end_time); + /*============================* + * read the available data... * + *============================*/ + res = read(fd, data + data_count, max_data_count - data_count); + /* 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, + {/* display the hex code of each character received */ + for (i=0; i < res; i++) + printf("<0x%2X>", *(data + data_count + i)); + data_already_available = 0; + /* data read succesfully... */ +/***************************************/ +/** 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. + * - 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 +#if RECV_BUFFER_SIZE < TCP_HEADER_LENGTH +#error The receive buffer is smaller than the frame header length. +static int modbus_tcp_read_frame(int nd, + struct timespec *ts_ptr) { + printf("[%lu] modbus_tcp_read_frame(): reading off nd=%d\n", pthread_self(), nd); + /*=========================* + * read a Modbus TCP frame * + *=========================*/ + fd = nd_table_.node[nd].fd; + if ((res = read_bytes(fd, nd_table_.node[nd].recv_buf, TCP_HEADER_LENGTH, ts_ptr, 1)) != TCP_HEADER_LENGTH) { + printf("[%lu] modbus_tcp_read_frame(): frame with insuficient bytes for a valid header...\n", pthread_self()); + if (res < 0) return res; + /* let's check for header consistency... */ + if (check_header(nd_table_.node[nd].recv_buf, transaction_id, &frame_length) < 0) { + printf("[%lu] modbus_tcp_read_frame(): frame with non valid header...\n", pthread_self()); + if ((res = read_bytes(fd, nd_table_.node[nd].recv_buf, frame_length, ts_ptr, 0)) != frame_length) { + printf("[%lu] modbus_tcp_read_frame(): frame with non valid frame length...\n", pthread_self()); + if (res < 0) return res; + /* frame received succesfully... */ +/***************************************/ +/** 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! + * 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, + * 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 +int modbus_tcp_read(int *nd, /* node descriptor */ + 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; + printf("[%lu] modbus_tcp_read(): called... nd=%d\n", pthread_self(), *nd); + if (*nd >= nd_table_.node_count) + /* remember that *nd < 0 is valid!! */ + 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! + if (recv_timeout != NULL) { + *ts_ptr = timespec_add_curtime(*recv_timeout); + /* If we must read off a single node... */ + /* 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... */ + /* We will loop forever... + * We jump out of the loop and return from the function as soon as: + * - we receive a valid modbus message; + * 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! + /* We prepare our fd sets here so we can later call select() */ + 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); + printf("[%lu] modbus_tcp_read(): while(1) looping. fd_high = %d, nd=%d\n", pthread_self(), fd_high, *nd); + /* we will not be reading from any node! */ + /* 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); + /* 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... */ + printf("[%lu] modbus_tcp_read(): my_select() returned due to activity on node nd=%d\n", pthread_self(), nd_count); + 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); + /* it is a MB_SLAVE_NODE or a MB_MASTER_NODE */ + /* We will read a frame off this nd */ + res = modbus_tcp_read_frame(nd_count, transaction_id, ts_ptr); + *recv_data_ptr = nd_table_.node[nd_count].recv_buf; + /* 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! + printf("[%lu] modbus_tcp_read(): error reading frame. Closing connection...\n", pthread_self()); + /* We close the socket... */ + close_connection(nd_count); + /* we have found the node descriptor, so let's jump out of the for(;;) loop */ + /* We were unsuccesfull reading a frame, so we try again... */ + /* humour the compiler... */ +/**************************************************************/ +/**************************************************************/ +/**** Initialising and Shutting Down Library ****/ +/**************************************************************/ +/**************************************************************/ + * 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... */ + *service = DEF_SERVICE; +/******************************/ +/** Initialise Library **/ +/******************************/ +/* returns the number of nodes succesfully initialised... +int modbus_tcp_init(int nd_count, + optimization_t opt /* ignored... */, + printf("[%lu] modbus_tcp_init(): called...\n", pthread_self()); + printf("[%lu] creating %d nodes:\n", pthread_self(), nd_count); + 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) + /* no need to initialise this layer! */ + /* invalid node count... */ + /* initialise the node table... */ + if (nd_table_init(&nd_table_, nd_count) < 0) + printf("[%lu] modbus_tcp_init(): %d node(s) opened succesfully\n", pthread_self(), nd_count); + return nd_count; /* number of succesfully created nodes! */ + nd_table_done(&nd_table_); + if (extra_bytes != NULL) +/******************************/ +/** Open a Master Node **/ +/******************************/ +int modbus_tcp_connect(node_addr_t node_addr) { + struct sockaddr_in tmp_addr; + 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); + /* Check for valid address family */ + if (node_addr.naf != naf_tcp) + /* wrong address type... */ + /* 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, + fprintf(stderr, ERRMSG_HEAD "Error parsing/resolving address %s:%s\n", + node_addr.addr.tcp.host, + node_addr.addr.tcp.service); + /* 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... */ + 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; + printf("[%lu] modbus_tcp_connect(): returning nd=%d\n", pthread_self(), node_descriptor); + return node_descriptor; +/******************************/ +/** Open a Slave Node **/ +/******************************/ +int modbus_tcp_listen(node_addr_t node_addr) { + 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); + /* Check for valid address family */ + if (node_addr.naf != naf_tcp) + /* wrong address type... */ + /* 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, + 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); + if (listen(fd, DEF_MAX_PENDING_CONNECTION_REQUESTS) < 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... */ + /* nd_table_.node[nd].addr = tmp_addr; */ /* does not apply for MB_LISTEN_NODE */ + nd_table_.node[nd].fd = fd; /* not currently connected... */ + printf("[%lu] modbus_tcp_listen(): returning nd=%d\n", pthread_self(), nd); +/******************************/ +/******************************/ +int modbus_tcp_close(int nd) { + fprintf(stderr, "[%lu] modbus_tcp_close(): called... nd=%d\n", pthread_self(), nd); + if ((nd < 0) || (nd >= nd_table_.node_count)) { + fprintf(stderr, "[%lu] modbus_tcp_close(): invalid node %d. Should be < %d\n", pthread_self(), nd, nd_table_.node_count); + if (nd_table_.node[nd].node_type == MB_FREE_NODE) + /* already free node */ + nd_table_close_node(&nd_table_, nd); +/**********************************/ +/** Close all open connections **/ +/**********************************/ +int modbus_tcp_silence_init(void) { + printf("[%lu] modbus_tcp_silence_init(): called...\n", pthread_self()); + /* 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! +/******************************/ +/** Shutdown the Library **/ +/******************************/ +int modbus_tcp_done(void) { + 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++) + nd_table_done(&nd_table_); +double modbus_tcp_get_min_timeout(int baud, --- /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 +/* 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 +#define RECV_BUFFER_SIZE MAX_L2_FRAME_LENGTH +#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) { + tmp.tv_usec = 1e6*(time - tmp.tv_sec); +/* Function to load a struct timespec correctly from a double. */ +static inline struct timespec d_to_timespec(double time) { + tmp.tv_nsec = 1e9*(time - tmp.tv_sec); +static inline struct timespec timespec_dif(struct timespec ts1, struct timespec ts2) { + 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; + ts.tv_nsec = 1000000000 + ts1.tv_nsec - ts2.tv_nsec; + ts.tv_sec = ts.tv_nsec = 0; +static inline struct timespec timespec_add(struct timespec ts1, struct timespec ts2) { + 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; +/* Function to convert a struct timespec to a struct timeval. */ +static inline struct timeval timespec_to_timeval(struct timespec ts) { + tv.tv_usec = ts.tv_nsec/1000; + * 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); +/************************************/ +/** 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. +static int my_select(int fd, fd_set *rfds, fd_set *wfds, const struct timespec *end_time) { + struct timespec cur_time; + struct timeval timeout, *tv_ptr; + fd_set tmp_rfds, *tmp_rfds_ptr, tmp_wfds, *tmp_wfds_ptr; + if (rfds != NULL) tmp_rfds_ptr = &tmp_rfds; + if (wfds != NULL) tmp_wfds_ptr = &tmp_wfds; + /*============================* + * wait for data availability * + *============================*/ + 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 + * NOTE: see also the timeout related comment in the + * modbus_tcp_read() function! + if (end_time == NULL) { + if ((end_time->tv_sec == 0) && (end_time->tv_nsec == 0)) { + timeout.tv_sec = timeout.tv_usec = 0; + if (clock_gettime(CLOCK_MONOTONIC, &cur_time) < 0) + timeout = timespec_to_timeval(timespec_dif(*end_time, cur_time)); + res = select(fd, tmp_rfds_ptr, tmp_wfds_ptr, NULL, tv_ptr); + 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); + printf("Comms time out\n"); + if ((res < 0) && (errno != EINTR)) { + if (rfds != NULL) *rfds = tmp_rfds; + if (wfds != NULL) *wfds = tmp_wfds; +#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. + /* 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 +/* 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. +/* 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. + * (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 + \ +/* 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 --- /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. +#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() +#define INADDR_NONE 0xffffffff +/* Last time I (msousa) checked, this was not being defined in QNX */ +typedef unsigned int socklen_t; +static inline int str_is_whitespace(const char *str) { + 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) { + 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 +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) { + // 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;} +static int getipbyname(struct in_addr *ip_addr, const char *host) { + // 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;} +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; +/* 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; + 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;} +/* 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; + // 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;} + if (bind(s, (struct sockaddr *)&sin, sizeof (sin)) < 0) {perror("bind()"); return -1;} --- /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. +/* 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