# HG changeset patch # User Mario de Sousa # Date 1488672346 0 # Node ID ae252e0fd9b8512708c07a2b533590c2fa5342dc Initial commit. diff -r 000000000000 -r ae252e0fd9b8 COPYING --- /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. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 . + +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: + + Copyright (C) + 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 +. + + 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 +. diff -r 000000000000 -r ae252e0fd9b8 COPYING.LESSER --- /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. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. \ No newline at end of file diff -r 000000000000 -r ae252e0fd9b8 Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile Sun Mar 05 00:05:46 2017 +0000 @@ -0,0 +1,44 @@ + +default: all + +all: libmb.a libmb.so + +OBJ_FILES = mb_ascii.o mb_rtu.o mb_tcp.o mb_master.o mb_slave.o mb_slave_and_master.o sin_util.o + +libmb.a: $(OBJ_FILES) + ar cr libmb.a $(OBJ_FILES) + +libmb.so: $(OBJ_FILES) + gcc -shared -fPIC -o libmb.so $(OBJ_FILES) + +clean: + -rm -rf *.o libmb.a libmb.so + + + +# use gcc +CC = gcc + +#get warnings, debugging information and optimization +CFLAGS = -Wall -Wpointer-arith -Wstrict-prototypes -Wwrite-strings +# CFLAGS += -Werror +CFLAGS += -ggdb -O3 -funroll-loops +# Note: if the optimizer crashes, we'll leave out the -O3 for those files + +# Required for compilation with beremiz, and to create shared object library +CFLAGS += -fPIC + + + +#how to make things from other directories if they are missing +../% /%: + $(MAKE) -C $(@D) $(@F) + +Makefile.depend depend: +# gcc -MM -MG -I$(LLIB) *.c \ + gcc -MM -MG *.c \ + | perl -pe 's/:/ Makefile.depend:/' \ + > Makefile.depend + +include Makefile.depend + diff -r 000000000000 -r ae252e0fd9b8 README --- /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 . + * + * This code is made available on the understanding that it will not be + * used in safety-critical situations without a full and competent review. + */ + + + + Modbus Protocol Libraries + ========================= + + This directory contains the libararies that implement the modbus protocol + stack. + + This protocol has been implemented as a two layer stack. Layer two includes + the mb_master and the mb_slave protocols. Layer one is composed of + the mb_rtu, mb_ascii and mb_tcp protocols. + + Layer1 protocols all implement the same interface, defined in mb_layer1.h + Layer2 protocols implement different interfaces, defined in mb_master.h + and mb_slave.h + + Which layer1 protocol that will be used by the program will depend on which + layer1 protocol implementation is linked to the final binary/executable. + It is not possible to define during run-time which layer1 protocol is to + be used. Each compiled program can only support a single layer1 protocol. + + Users of these libraries should only use functions defined in the layer2 + protocol header files (i.e. mb_master.h and mb_slave.h) + + If writing a program that will simultaneously be a master and a slave, + then only use the mb_slave_and_master.h header file! + In this case, do not forget to link the final binary to both the + master and slave protocol implementations (as well as the chosen + layer1 protocol implementation). + + + + ------------------------------------------ + | | | + layer 2 | mb_master.h | mb_slave.h | + | mb_master.c | mb_slave.c | + | | | + |----------------------------------------| + | mb_layer1.h | + Layer 1 | | | | + | mb_rtu.c | mb_ascii.c | mb_tcp.c | + | | | | + ------------------------------------------ diff -r 000000000000 -r ae252e0fd9b8 mb_addr.h --- /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 . + * + * This code is made available on the understanding that it will not be + * used in safety-critical situations without a full and competent review. + */ + + +#ifndef MODBUS_LAYER2_H +#define MODBUS_LAYER2_H + +#include /* struct timespec data type */ + +//#include +//#include +#include /* superset of previous */ // Required for INADDR_ANY + + +/* Library Error codes */ +#define PORT_FAILURE -101 +#define INTERNAL_ERROR -102 +#define TIMEOUT -103 +#define INVALID_FRAME -104 +#define MODBUS_ERROR -105 + +/* NOTE: Modbus error codes are defined in mb_util.h */ + + + + +typedef enum {optimize_speed, optimize_size} optimization_t; + + +typedef enum { + naf_ascii, + naf_rtu, + naf_tcp, + } node_addr_family_t; + +typedef struct { + const char *host; + const char *service; + int close_on_silence; + } node_addr_tcp_t; + +typedef struct { + const char *device; + int baud; /* plain baud rate, eg 2400; zero for the default 9600 */ + int parity; /* 0 for none, 1 for odd, 2 for even */ + int data_bits; + int stop_bits; + int ignore_echo; /* 1 => ignore echo; 0 => do not ignore echo */ + } node_addr_rtu_t; + +typedef node_addr_rtu_t node_addr_ascii_t; + +typedef union { + node_addr_ascii_t ascii; + node_addr_rtu_t rtu; + node_addr_tcp_t tcp; + } node_addr_common_t; + +typedef struct { + node_addr_family_t naf; + node_addr_common_t addr; + } node_addr_t; + +#endif /* MODBUS_LAYER2_H */ + + + + + + + + diff -r 000000000000 -r ae252e0fd9b8 mb_ascii.c --- /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 . + * + * 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 /* File control definitions */ +#include /* Standard input/output */ +#include +#include +#include /* POSIX terminal control definitions */ +#include /* Time structures for select() */ +#include /* POSIX Symbolic Constants */ +#include +#include /* Error definitions */ +#include +#include /* clock_gettime() */ +#include /* required for INT_MAX */ + +#include "mb_layer1.h" +#include "mb_ascii_private.h" + + +/* #define DEBUG */ /* uncomment to see the data sent and received */ + + + + +/************************************/ +/** **/ +/** Include common code... **/ +/** **/ +/************************************/ + +#include "mb_ds_util.h" /* data structures... */ +#include "mb_time_util.h" /* time conversion routines... */ + + +/**************************************************************/ +/**************************************************************/ +/**** ****/ +/**** ****/ +/**** Purpose and Formats ****/ +/**** ****/ +/**** ****/ +/**************************************************************/ +/**************************************************************/ +/* + + This file implements the ascii formating of the modbus protocol. + Many values, protocol related, are hardcoded into the code, as it + seems very unlikely this code will ever get re-used for anything + else but this specific protocol. + + Modbus ASCII frames have no timing restrictions whatsoever, and + abide by the following format: + + Header + ------ + size : 1 byte + value: ':' (i.e. '\0x3A') + + Body + ---- + size : variable, multiple of 2 + value: binary data converted to ascii format, + i.e. each binary data byte is converted into two ascii characters + representing the byte in hexadecimal. Allowable characters are + '0' to '9' and 'A' to 'D' + + LRC + --- + size : 2 bytes + value: Longitudinal Redundancy Check of data, excluding any headers, tails, + etc... + + Tail + ---- + size : 2 bytes + value: 'CR' + 'LF' (i.e. '\0x0D' + '\0x0A') + + +*/ + + + +/**************************************************************/ +/**************************************************************/ +/**** ****/ +/**** ****/ +/**** Forward Declarations ****/ +/**** and Defaults ****/ +/**** ****/ +/**************************************************************/ +/**************************************************************/ + + +typedef enum {fp_header, fp_body, fp_lrc, fp_tail, fp_done} frame_part_t; + + + + +/**************************************************************/ +/**************************************************************/ +/**** ****/ +/**** ****/ +/**** Local Utility functions... ****/ +/**** ****/ +/**** ****/ +/**************************************************************/ +/**************************************************************/ + + +/*****************************************/ +/** **/ +/** lrc functions **/ +/** **/ +/*****************************************/ + + +static inline void lrc_init(u8 *lrc) { + *lrc = 0; +} + +static inline void lrc_add_single(u8 *lrc, u8 data) { + *lrc += data; +} + +static inline void lrc_add_many(u8 *lrc, u8 *data, int count) { + for (; count > 0; count--, *lrc += *data++); +} + +static inline void lrc_end(u8 *lrc) { + *lrc = 1 + ~(*lrc); +} + + + +/**************************************/ +/** **/ +/** Initialise a struct termios **/ +/** **/ +/**************************************/ +static int termios_init(struct termios *tios, + int baud, + int parity, + int data_bits, + int stop_bits) { + speed_t baud_rate; + + if (tios == NULL) + return -1; + + /* reset all the values... */ + /* NOTE: the following are initialised later on... + tios->c_iflag = 0; + tios->c_oflag = 0; + tios->c_cflag = 0; + tios->c_lflag = 0; + */ + tios->c_line = 0; + + /* The minimum number of characters that should be received + * to satisfy a call to read(). + */ + tios->c_cc[VMIN ] = 0; + + /* The maximum inter-arrival interval between two characters, + * in deciseconds. + * + * NOTE: we could use this to detect the end of RTU frames, + * but we prefer to use select() that has higher resolution, + * even though this higher resolution is most probably not + * supported, and the effective resolution is 10ms, + * one tenth of a decisecond. + */ + tios->c_cc[VTIME] = 0; + + /* configure the input modes... */ + tios->c_iflag = IGNBRK | /* ignore BREAK condition on input */ + IGNPAR | /* ignore framing errors and parity errors */ + IXANY; /* enable any character to restart output */ + /* BRKINT Only active if IGNBRK is not set. + * generate SIGINT on BREAK condition, + * otherwise read BREAK as character \0. + * PARMRK Only active if IGNPAR is not set. + * replace bytes with parity errors with + * \377 \0, instead of \0. + * INPCK enable input parity checking + * ISTRIP strip off eighth bit + * IGNCR ignore carriage return on input + * INLCR only active if IGNCR is not set. + * translate newline to carriage return on input + * ICRNL only active if IGNCR is not set. + * translate carriage return to newline on input + * IUCLC map uppercase characters to lowercase on input + * IXON enable XON/XOFF flow control on output + * IXOFF enable XON/XOFF flow control on input + * IMAXBEL ring bell when input queue is full + */ + + /* configure the output modes... */ + tios->c_oflag = OPOST; /* enable implementation-defined output processing */ + /* ONOCR don't output CR at column 0 + * OLCUC map lowercase characters to uppercase on output + * ONLCR map NL to CR-NL on output + * OCRNL map CR to NL on output + * OFILL send fill characters for a delay, rather than + * using a timed delay + * OFDEL fill character is ASCII DEL. If unset, fill + * character is ASCII NUL + * ONLRET don't output CR + * NLDLY NL delay mask. Values are NL0 and NL1. + * CRDLY CR delay mask. Values are CR0, CR1, CR2, or CR3. + * TABDLY horizontal tab delay mask. Values are TAB0, TAB1, + * TAB2, TAB3, or XTABS. A value of XTABS expands + * tabs to spaces (with tab stops every eight columns). + * BSDLY backspace delay mask. Values are BS0 or BS1. + * VTDLY vertical tab delay mask. Values are VT0 or VT1. + * FFDLY form feed delay mask. Values are FF0 or FF1. + */ + + /* configure the control modes... */ + tios->c_cflag = CREAD | /* enable receiver. */ + CLOCAL; /* ignore modem control lines */ + /* HUPCL lower modem control lines after last process + * closes the device (hang up). + * CRTSCTS flow control (Request/Clear To Send). + */ + if (data_bits == 5) tios->c_cflag |= CS5; + else if (data_bits == 6) tios->c_cflag |= CS6; + else if (data_bits == 7) tios->c_cflag |= CS7; + else if (data_bits == 8) tios->c_cflag |= CS8; + else return -1; + + if (stop_bits == 1) tios->c_cflag &=~ CSTOPB; + else if (stop_bits == 2) tios->c_cflag |= CSTOPB; + else return -1; + + if(parity == 0) { /* none */ + tios->c_cflag &=~ PARENB; + tios->c_cflag &=~ PARODD; + } else if(parity == 2) { /* even */ + tios->c_cflag |= PARENB; + tios->c_cflag &=~ PARODD; + } else if(parity == 1) { /* odd */ + tios->c_cflag |= PARENB; + tios->c_cflag |= PARODD; + } else return -1; + + + /* configure the local modes... */ + tios->c_lflag = IEXTEN; /* enable implementation-defined input processing */ + /* ISIG when any of the characters INTR, QUIT, SUSP, or DSUSP + * are received, generate the corresponding signal. + * ICANON enable canonical mode. This enables the special + * characters EOF, EOL, EOL2, ERASE, KILL, REPRINT, + * STATUS, and WERASE, and buffers by lines. + * ECHO echo input characters. + */ + + /* Set the baud rate */ + /* Must be done before reseting all the values to 0! */ + switch(baud) { + case 110: baud_rate = B110; break; + case 300: baud_rate = B300; break; + case 600: baud_rate = B600; break; + case 1200: baud_rate = B1200; break; + case 2400: baud_rate = B2400; break; + case 4800: baud_rate = B4800; break; + case 9600: baud_rate = B9600; break; + case 19200: baud_rate = B19200; break; + case 38400: baud_rate = B38400; break; + case 57600: baud_rate = B57600; break; + case 115200: baud_rate = B115200; break; + default: return -1; + } /* switch() */ + + if ((cfsetispeed(tios, baud_rate) < 0) || + (cfsetospeed(tios, baud_rate) < 0)) + return -1;; + + return 0; +} + + + + + +/*****************************************/ +/** **/ +/** u8/ascii conversion functions **/ +/** **/ +/*****************************************/ + +/* Convert binary data to ascii format. + * Both xxx_data_lengths specify the available bytes in + * in the respective arrays. + */ +/* NOTE: this function is only called from a single location + * so we might just as well make it inline... + */ +static inline int bin_2_asc(u8 *bin_data, int bin_data_length, + u8 *asc_data, int asc_data_length) { + u8 nibble; + int count = 0; + u8 *last_bin_data = bin_data + bin_data_length; + /* we need L2_TO_ASC_CODING ascii bytes for each bin byte, therefore the + * '- (L2_TO_ASC_CODING - 1)' + */ + u8 *last_asc_data = asc_data + asc_data_length - (L2_TO_ASC_CODING - 1); + + while ((bin_data < last_bin_data) && + (asc_data < last_asc_data)) { + + nibble = (*bin_data & 0xF0) >> 4; + *(asc_data++) = (nibble <= 9)?nibble + '0':nibble - 10 + 'A'; + + nibble = (*bin_data & 0x0F); + *(asc_data++) = (nibble <= 9)?nibble + '0':nibble - 10 + 'A'; + + count++; + bin_data++; + } + + /* return number of bytes converted... */ + return count; +} + + +/* Convert from lowercase to upper case. */ +/* It probably does not make sense calling the generic toupper() + * whose functionality depends on the current locale. + * Our own specific function is most probably much faster... + */ +static inline u8 local_toupper(u8 val) { + if ((val >= 'a') && (val <= 'z')) + return val - 'a' + 'A'; + return val; +} + +/* Convert ascii data to bin format. + * *asc_data must be a two byte array. + * + * If a non-ascii character is found, returns -1 + */ +/* NOTE: this function is only called from a single location + * so we might just as well make it inline... + */ +static inline int asc_2_bin(u8 *asc_data, u8 *bin_data) { + if ((isxdigit(asc_data[0]) == 0) || + (isxdigit(asc_data[1]) == 0)) + return -1; + + asc_data[0] = local_toupper(asc_data[0]); + asc_data[1] = local_toupper(asc_data[1]); + + /* hi */ *(bin_data) = ((asc_data[0] <= '9')? + (asc_data[0] - '0'):(asc_data[0] - 'A' + 10)) * 0x10; + /* lo */ *(bin_data) += (asc_data[1] <= '9')? + (asc_data[1] - '0'):(asc_data[1] - 'A' + 10); + + return 0; +} + + + + +/************************************/ +/** **/ +/** A data structure - send buffer **/ +/** **/ +/************************************/ + +/* data structure used to store the data to be sent in ascii format. */ +/* The binary data is converted into ascii format before transmission. The + * frame is not converted as a single whole, but is rather done in chunks. + * The size of the chunks depends on the data size of the send_buffer. + * + * A lrc variable keeps a tab on the current value of the lrc as the data + * is being converted. + * + * Three special functions add the header, lrc and tail to the ascii frame. + */ + +/* NOTE: The algorithms in the insert functions require a minimum buffer + * size to work correctly... + */ +#define SEND_BUF_MIN_LENGTH ASC_FRAME_MIN_ELE_LENGTH + +typedef struct { + lb_buf_t data_buf; + + u8 lrc; /* the current value of the lrc, in binary format */ + } send_buf_t; + +/* A small auxiliary function... */ +static inline u8 *send_buf_init(send_buf_t *buf, int size, int max_data_start) { + /* The algorithms in other functions require a minimum size + * to work correctly... + */ + if (size < SEND_BUF_MIN_LENGTH) + return NULL; + + lrc_init(&buf->lrc); + return lb_init(&buf->data_buf, size, max_data_start); +} + +/* A small auxiliary function... */ +static inline void send_buf_done(send_buf_t *buf) { + lb_done(&buf->data_buf); +} + +/* A small auxiliary function... */ +static inline void send_buf_reset(send_buf_t *buf) { + lrc_init(&buf->lrc); + lb_data_purge_all(&buf->data_buf); +} + +/* A small auxiliary function... */ +static inline int send_buf_data_count(send_buf_t *buf) { + return lb_data_count(&buf->data_buf); +} + +/* A small auxiliary function... */ +static inline int send_buf_free_count(send_buf_t *buf) { + return lb_free_count(&buf->data_buf); +} +/* A small auxiliary function... */ +static inline u8 *send_buf_data(send_buf_t *buf) { + return lb_data(&buf->data_buf); +} + +/* A small auxiliary function... */ +static inline u8 *send_buf_free(send_buf_t *buf) { + return lb_free(&buf->data_buf); +} + +/* A small auxiliary function... */ +static inline int send_buf_data_add(send_buf_t *buf, u8 *data, int data_count) { + int res = bin_2_asc(data, data_count, send_buf_free(buf), send_buf_free_count(buf)); + if (res <=0) return res; + lb_data_add(&buf->data_buf, L2_TO_ASC_CODING * res); + lrc_add_many(&buf->lrc, data, res); + return res; +} + +/* A small auxiliary function... */ +static inline void send_buf_data_purge(send_buf_t *buf, int count) { + lb_data_purge(&buf->data_buf, count); +} + +/* A small auxiliary function... */ +static inline void send_buf_data_purge_all(send_buf_t *buf) { + lb_data_purge_all(&buf->data_buf); +} + +/* A small auxiliary function... */ +static inline int send_buf_lrc_append(send_buf_t *buf) { +#if ASC_FRAME_LRC_LENGTH != 2 +#error Code assumes LRC length of 2 bytes, but ASC_FRAME_LRC_LENGTH != 2 +#endif + if (lb_free_count(&buf->data_buf) < ASC_FRAME_LRC_LENGTH) + return -1; + lrc_end(&buf->lrc); + bin_2_asc(&buf->lrc, sizeof(buf->lrc), + lb_free(&buf->data_buf), ASC_FRAME_LRC_LENGTH); + lb_data_add(&buf->data_buf, ASC_FRAME_LRC_LENGTH); + return 0; +} + +static inline int send_buf_header_append(send_buf_t *buf) { +#if ASC_FRAME_HEADER_LENGTH != 1 +#error Code assumes HEADER length of 1 bytes, but ASC_FRAME_HEADER_LENGTH != 1 +#endif + if (lb_free_count(&buf->data_buf) < ASC_FRAME_HEADER_LENGTH) + return -1; + + /* add the ':' frame header */ + *lb_free(&buf->data_buf) = ASC_FRAME_HEADER; + lb_data_add(&buf->data_buf, ASC_FRAME_HEADER_LENGTH); + + return 0; +} + +static inline int send_buf_tail_append(send_buf_t *buf) { +#if ASC_FRAME_TAIL_LENGTH != 2 +#error Code assumes TAIL length of 2 bytes, but ASC_FRAME_TAIL_LENGTH != 2 +#endif + if (lb_free_count(&buf->data_buf) < ASC_FRAME_TAIL_LENGTH) + return -1; + + /* add the CR+LF frame delimiter */ + lb_free(&buf->data_buf)[0] = ASC_FRAME_TAIL_0; + lb_free(&buf->data_buf)[1] = ASC_FRAME_TAIL_1; + lb_data_add(&buf->data_buf, ASC_FRAME_TAIL_LENGTH); + + return 0; +} + + + + +/************************************/ +/** **/ +/** A data structure - recv buffer **/ +/** **/ +/************************************/ + +/* data structure used to store the data received from the bus. */ + +/* The ascii data received from the bus is added to the buffer, and is + * dynamically converted to binary format. Once a valid frame has been + * converted, conversion stops until this valid frame is deleted/purged. + */ + +/* NOTE: The algorithms in the insert functions require a minimum buffer + * size to work correctly... + */ +#define RECV_BUF_MIN_LENGTH ASC_FRAME_MIN_ELE_LENGTH + +#define RECV_BUF_BIN_BUF_SIZE (MAX_L2_FRAME_LENGTH + \ + ASC_FRAME_LRC_LENGTH / L2_TO_ASC_CODING) + +typedef struct { + lb_buf_t asc_data_buf; + u8 bin_data_buf[RECV_BUF_BIN_BUF_SIZE]; + int bin_data_free_ofs; + + frame_part_t frame_part; + u8 lrc; /* the current value of the lrc, in binary format */ + u8 lrc_1; /* the previous value of the lrc... */ + /* NOTE: We do a running conversion between ascii and binary format, + * i.e. we start converting from ascii to binary before we + * have received the complete ascii frame. This means that + * we also do a running computation of our local version of + * the frame lrc. + * The lrc, transmitted at the end of the ascii frame, + * but before the frame tail, also gets converted to binary + * before we get a chance to realize that it is the lrc value, + * and should therefore not be taken into account when computing + * our local version of the lrc. + * So we keep the previous value of the running lrc, and use + * that to confirm whether we have a valid frame. + */ + } recv_buf_t; + +/* A small auxiliary function... */ +static inline u8 *recv_buf_init(recv_buf_t *buf, int size, int max_data_start) { + /* The algorithms in other functions require a minimum size + * to work correctly... + */ + if (size < RECV_BUF_MIN_LENGTH) + return NULL; + + lrc_init(&buf->lrc); + buf->bin_data_free_ofs = 0; + buf->frame_part = fp_header; + return lb_init(&buf->asc_data_buf, size, max_data_start); +} + +/* A small auxiliary function... */ +static inline void recv_buf_done(recv_buf_t *buf) { + lb_done(&buf->asc_data_buf); +} + +/* A small auxiliary function... */ +static inline void recv_buf_reset(recv_buf_t *buf) { + lrc_init(&buf->lrc); + buf->bin_data_free_ofs = 0; + buf->frame_part = fp_header; + lb_data_purge_all(&buf->asc_data_buf); +} + +/* A small auxiliary function... */ +static inline u8 *recv_buf_data(recv_buf_t *buf) { + return lb_data(&buf->asc_data_buf); +} + +/* A small auxiliary function... */ +static inline u8 *recv_buf_free(recv_buf_t *buf) { + return lb_free(&buf->asc_data_buf); +} + +/* The function that really does all the conversion work... */ +/* It finds frame limits, converts the data into binary format, + * and checks for correct lrc in the received frame. + */ +/* NOTE: called indirectly from various locations! Do NOT inline! */ +static void recv_buf_data_parse(recv_buf_t *buf) { + int count; + u8 *data; + + data = lb_data(&buf->asc_data_buf); + count = lb_data_count(&buf->asc_data_buf); + + /* NOTE: We need at least ASC_FRAME_MIN_ELE_LENGTH bytes to + * to be able to find that element of minimum length + */ + while ((count >= ASC_FRAME_MIN_ELE_LENGTH) && (buf->frame_part != fp_done)) { + /* Check for frame header... */ + /* The following few lines of code assume that ASC_FRAME_HEADER_LENGTH is 1! */ +#if ASC_FRAME_HEADER_LENGTH != 1 +#error The code is written in such a way that can only handle ASC_FRAME_HEADER_LENGTH == 1 +#endif + if (data[0] == ASC_FRAME_HEADER) { + /* found the beginning of a frame... + * Even if we were previously converting a frame without errors, + * if we receive a new frame header we discard the previous + * frame that went unfinished! + */ + data += ASC_FRAME_HEADER_LENGTH; + count -= ASC_FRAME_HEADER_LENGTH; + buf->frame_part = fp_body; + lrc_init(&buf->lrc); + buf->bin_data_free_ofs = 0; + continue; + } + + /* Check for frame tail... */ + /* + * Note that the while() condition guarantees that we have at least + * two ascii bytes to handle. + */ + /* The following few lines of code assume that ASC_FRAME_TAIL_LENGTH is 2! */ +#if ASC_FRAME_TAIL_LENGTH != 2 +#error The code is written in such a way that can only handle ASC_FRAME_TAIL_LENGTH == 2 +#endif + if ((data[0] == ASC_FRAME_TAIL_0) && + (data[1] == ASC_FRAME_TAIL_1)) { + /* end of binary data... */ + data += ASC_FRAME_TAIL_LENGTH; + count -= ASC_FRAME_TAIL_LENGTH; + + /* let's check the lrc... */ + if (buf->bin_data_free_ofs <= 0) + /* we have reached the end of a frame that did not include + * any binary data, not even the lrc value itself! + */ + goto frame_error; + + /* Remember that we do not use the most recent lrc value, as this + * incorrectly includes the ascii bytes in the frame that code the + * frame's lrc. (pls read the note in the recv_but_t typedef) + */ + /* The following few lines of code assume that + * (ASC_FRAME_LRC_LENGTH / L2_TO_ASC_CODING) is 1! + */ +#if L2_TO_ASC_CODING != ASC_FRAME_LRC_LENGTH +#error The code is written in such a way that can only handle L2_TO_ASC_CODING == ASC_FRAME_LRC_LENGTH +#endif + lrc_end(&(buf->lrc_1)); + if (buf->lrc_1 == buf->bin_data_buf[buf->bin_data_free_ofs-1]) { + /* we have received a correct frame... */ + buf->frame_part = fp_done; + continue; + } else { + /* we have found a frame with an lrc error */ + goto frame_error; + } + } + + if (buf->frame_part == fp_header) { + /* we are searching for beginning of a frame... + * but we did not find it in the previous condition! + * We continue looking in the next ascii byte + */ + data++; + count--; + continue; + } + + if (buf->frame_part == fp_body) { + /* we have previously found the beginning of a frame, + * and are now converting the body into binary format... + * + * Note that the while() condition guarantees that we have at least + * two ascii bytes to convert into binary format. + */ + + /* this is normal data... let's convert... */ + if (asc_2_bin(data, buf->bin_data_buf + buf->bin_data_free_ofs) < 0) + /* error converting from ascii to binary. + * This must be due to an invalid ascii character in the ascii data. + * We discard the current frame... + * + * Note that we *do not* increment the data pointer, + * nor do we decrement the count variable. One of the ascii bytes could + * be the begining of a new valid frame, and we don't want to skip it! + */ + goto frame_error; + + buf->lrc_1 = buf->lrc; + lrc_add_single(&(buf->lrc), *(buf->bin_data_buf + buf->bin_data_free_ofs)); + + data += L2_TO_ASC_CODING; + count -= L2_TO_ASC_CODING; + if (++(buf->bin_data_free_ofs) >= RECV_BUF_BIN_BUF_SIZE) + /* Whoops, this shouldn't be hapening! + * The frame we are receiving is larger than the alocated buffer! + * Our only alternative is to discard the frame + * we are currently receiving... + */ + goto frame_error; + + continue; + } + + /* if none of the above, then it must be some transmission error */ + /* Actually, the code will never fall through since if we are in this loop, + * (frame_part == header) || (frame_part == body) is always true! + */ + data++; + count--; +frame_error: + lrc_init(&buf->lrc); + buf->bin_data_free_ofs = 0; + buf->frame_part = fp_header; + } /* while () */ + + lb_data_purge(&buf->asc_data_buf, lb_data_count(&buf->asc_data_buf) - count); +} + +/* A small auxiliary function... */ +static inline void recv_buf_search_frame(recv_buf_t *buf) { + if (buf->frame_part != fp_done) + recv_buf_data_parse(buf); +} + +/* add ascii format data to buffer */ +static inline void recv_buf_data_add(recv_buf_t *buf, int count) { + lb_data_add(&buf->asc_data_buf, count); +} + +/* A small auxiliary function... */ +static inline int recv_buf_data_count(recv_buf_t *buf) { + return lb_data_count(&buf->asc_data_buf); +} + +/* A small auxiliary function... */ +static inline int recv_buf_free_count(recv_buf_t *buf) { + return lb_free_count(&buf->asc_data_buf); +} + +/* Return pointer to frame, if a valid frame is available */ +static inline u8 *recv_buf_frame(recv_buf_t *buf) { + recv_buf_search_frame(buf); + if (buf->frame_part == fp_done) + /* we have found a frame...! */ + return buf->bin_data_buf; + + /* no frame... */ + return NULL; +} + +/* Return number of bytes in frame, if a valid frame is available */ +static inline int recv_buf_frame_count(recv_buf_t *buf) { + recv_buf_search_frame(buf); + if (buf->frame_part == fp_done) + /* we have found a frame...! */ + return buf->bin_data_free_ofs - ASC_FRAME_LRC_LENGTH/L2_TO_ASC_CODING; + + /* no frame... */ + return -1; +} + +/* Delete valid binary format frame! */ +static inline void recv_buf_frame_purge(recv_buf_t *buf) { + /* NOTE: we only delete valid frames!! + * Partially converted frames are not deleted, the + * remaining bytes may be received later! + */ + if (buf->frame_part == fp_done) { + buf->frame_part = fp_header; + buf->bin_data_free_ofs = 0; + } +} + + + +/************************************/ +/** **/ +/** A data structure - nd entry **/ +/** **/ +/************************************/ + +/* NOTE: nd = node descriptor */ + +typedef struct { + /* The file descriptor associated with this node */ + /* NOTE: if the node is not yet in use, i.e. if the node is free, + * then fd will be set to -1 + */ + int fd; + struct timeval time_15_char_; + + /* Modbus ascii frames are delimited by a ':' (colon) at the begining of + * a frame, and CR-LF sequence at the end the frame. + * + * Unless we want to take 'ages' reading the data off the serial line + * one byte at a time, we risk reading beyond the boundary of the + * frame currently being interpreted. The extra data belongs to the + * subsequent frame, and must therefore be buffered to be handled + * when the next frame is being interpreted. + * + * The receive buffer is therefore a static variable. + */ + recv_buf_t recv_buf_; + + /* The send ascii buffer could be a local function variable + * instead of a static variable, but we might just as well + * allocate the memory at startup and not risk running out + * of memory while the module is running. + */ + send_buf_t send_buf_; + + /* The old settings of the serial port, to be reset when the library is closed... */ + struct termios old_tty_settings_; + + /* ignore echo flag. + * If set to 1, then it means that we will be reading every byte we + * ourselves write out to the bus, so we must ignore those bytes read + * before we really read the data sent by remote nodes. + * + * This comes in useful when using a RS232-RS485 converter that does + * not correctly control the RTS-CTS lines... + */ + int ignore_echo; + } nd_entry_t; + + +static inline void nd_entry_init(nd_entry_t *nde) { + nde->fd = -1; /* The node is free... */ +} + +static int nd_entry_connect(nd_entry_t *nde, + node_addr_t *node_addr, + optimization_t opt) { + + int parity_bits, start_bits, char_bits; + struct termios settings; + int buf_size; + + /* + if (nde == NULL) + goto error_exit_0; + */ + if (nde->fd >= 0) + goto error_exit_0; + + /* initialise the termios data structure */ + if (termios_init(&settings, + node_addr->addr.ascii.baud, + node_addr->addr.ascii.parity, + node_addr->addr.ascii.data_bits, + node_addr->addr.ascii.stop_bits) + < 0) { +#ifdef DEBUG + fprintf(stderr, "Invalid serial line settings" + "(baud=%d, parity=%d, data_bits=%d, stop_bits=%d)", + node_addr->addr.ascii.baud, + node_addr->addr.ascii.parity, + node_addr->addr.ascii.data_bits, + node_addr->addr.ascii.stop_bits); +#endif + goto error_exit_1; + } + + /* set the ignore_echo flag */ + nde->ignore_echo = node_addr->addr.ascii.ignore_echo; + + /* initialise send buffer */ + buf_size = (opt == optimize_size)?SEND_BUFFER_SIZE_SMALL: + SEND_BUFFER_SIZE_LARGE; + if (send_buf_init(&nde->send_buf_, buf_size, buf_size - SEND_BUF_MIN_LENGTH) + == NULL) { +#ifdef DEBUG + fprintf(stderr, "Out of memory: error initializing send buffer"); +#endif + goto error_exit_1; + } + + /* initialise recv buffer */ + buf_size = (opt == optimize_size)?RECV_BUFFER_SIZE_SMALL: + RECV_BUFFER_SIZE_LARGE; + if (recv_buf_init(&nde->recv_buf_, buf_size, buf_size - RECV_BUF_MIN_LENGTH) + == NULL) { +#ifdef DEBUG + fprintf(stderr, "Out of memory: error initializing receive buffer"); +#endif + goto error_exit_2; + } + + /* open the serial port */ + if((nde->fd = open(node_addr->addr.ascii.device, O_RDWR | O_NOCTTY | O_NDELAY)) + < 0) { +#ifdef DEBUG + fprintf(stderr, "Error opening device %s (errno=%d)", + node_addr->addr.ascii.device, errno); +#endif + goto error_exit_3; + } + + if(tcgetattr(nde->fd, &nde->old_tty_settings_) < 0) { +#ifdef DEBUG + fprintf(stderr, "Error reading device's %s original settings.", + node_addr->addr.ascii.device); +#endif + goto error_exit_4; + } + + if(tcsetattr(nde->fd, TCSANOW, &settings) < 0) { +#ifdef DEBUG + fprintf(stderr, "Error configuring device %s" + "(baud=%d, parity=%d, data_bits=%d, stop_bits=%d)", + node_addr->addr.ascii.device, + node_addr->addr.ascii.baud, + node_addr->addr.ascii.parity, + node_addr->addr.ascii.data_bits, + node_addr->addr.ascii.stop_bits); +#endif + goto error_exit_4; + } + + parity_bits = (node_addr->addr.ascii.parity == 0)?0:1; + start_bits = 1; + char_bits = start_bits + node_addr->addr.ascii.data_bits + + parity_bits + node_addr->addr.ascii.stop_bits; +/* time_35_char_ = d_to_timeval(3.5*char_bits/baud); */ + nde->time_15_char_ = d_to_timeval(1.5*char_bits/node_addr->addr.ascii.baud); + +#ifdef DEBUG + printf("nd_entry_connect(): %s open\n", node_addr->addr.ascii.device ); + printf("nd_entry_connect(): returning fd=%d\n", nde->fd); +#endif + return nde->fd; + + error_exit_4: + close(nde->fd); + error_exit_3: + recv_buf_done(&nde->recv_buf_); + error_exit_2: + send_buf_done(&nde->send_buf_); + error_exit_1: + nde->fd = -1; /* set the node as free... */ + error_exit_0: + return -1; +} + + + +static int nd_entry_free(nd_entry_t *nde) { + if (nde->fd < 0) + /* already free */ + return -1; + + /* reset the tty device old settings... */ + if(tcsetattr(nde->fd, TCSANOW, &nde->old_tty_settings_) < 0) + fprintf(stderr, "Error reconfiguring serial port to it's original settings."); + + recv_buf_done(&nde->recv_buf_); + send_buf_done(&nde->send_buf_); + close(nde->fd); + nde->fd = -1; + + return 0; +} + + +static inline int nd_entry_is_free(nd_entry_t *nde) { + return (nde->fd < 0); +} + + + + +/************************************/ +/** **/ +/** A data structure - nd table **/ +/** **/ +/************************************/ + +typedef struct { + /* the array of node descriptors, and current size... */ + nd_entry_t *node; + int node_count; /* total number of nodes in the node[] array */ +} nd_table_t; + + + +#if 1 +/* nd_table_init() + * Version 1 of the nd_table_init() function. + * If called more than once, 2nd and any subsequent calls will + * be interpreted as a request to confirm that it was already correctly + * initialized with the requested number of nodes. + */ +static int nd_table_init(nd_table_t *ndt, int nd_count) { + int count; + + if (ndt->node != NULL) { + /* this function has already been called, and the node table is already initialised */ + return (ndt->node_count == nd_count)?0:-1; + } + + /* initialise the node descriptor metadata array... */ + ndt->node = malloc(sizeof(nd_entry_t) * nd_count); + if (ndt->node == NULL) { +#ifdef DEBUG + fprintf(stderr, "Out of memory: error initializing node address buffer"); +#endif + return -1; + } + ndt->node_count = nd_count; + + /* initialise the state of each node in the array... */ + for (count = 0; count < ndt->node_count; count++) { + nd_entry_init(&ndt->node[count]); + } /* for() */ + + return nd_count; /* number of succesfully created nodes! */ +} +#else +/* nd_table_init() + * Version 2 of the nd_table_init() function. + * If called more than once, 2nd and any subsequent calls will + * be interpreted as a request to reserve an extra new_nd_count + * number of nodes. This will be done using realloc(). + */ +static int nd_table_init(nd_table_t *ndt, int new_nd_count) { + int count; + + /* initialise the node descriptor metadata array... */ + ndt->node = realloc(ndt->node, sizeof(nd_entry_t) * (ndt->node_count + new_nd_count)); + if (ndt->node == NULL) { +#ifdef ERRMSG + fprintf(stderr, ERRMSG_HEAD "Out of memory: error initializing node address buffer\n"); +#endif + return -1; + } + + /* initialise the state of each newly added node in the array... */ + for (count = ndt->node_count; count < ndt->node_count + new_nd_count; count++) { + nd_entry_init(&ndt->node[count]); + } /* for() */ + ndt->node_count += new_nd_count; + + return new_nd_count; /* number of succesfully created nodes! */ +} +#endif + + +static inline nd_entry_t *nd_table_get_nd(nd_table_t *ndt, int nd) { + if ((nd < 0) || (nd >= ndt->node_count)) + return NULL; + + return &ndt->node[nd]; +} + + +static inline void nd_table_done(nd_table_t *ndt) { + int i; + + if (ndt->node == NULL) + return; + + /* close all the connections... */ + for (i = 0; i < ndt->node_count; i++) + nd_entry_free(&ndt->node[i]); + + /* Free memory... */ + free(ndt->node); + *ndt = (nd_table_t){.node=NULL, .node_count=0}; +} + + + +static inline int nd_table_get_free_nd(nd_table_t *ndt) { + int count; + + for (count = 0; count < ndt->node_count; count++) { + if (nd_entry_is_free(&ndt->node[count])) + return count; + } + + /* none found... */ + return -1; +} + + +static inline int nd_table_free_nd(nd_table_t *ndt, int nd) { + if ((nd < 0) || (nd >= ndt->node_count)) + return -1; + + return nd_entry_free(&ndt->node[nd]); +} + + + +/**************************************************************/ +/**************************************************************/ +/**** ****/ +/**** ****/ +/**** Global Library State ****/ +/**** ****/ +/**** ****/ +/**************************************************************/ +/**************************************************************/ + + + /* The node descriptor table... */ + /* NOTE: This variable must be correctly initialised here!! */ +static nd_table_t nd_table_ = {.node=NULL, .node_count=0}; + + /* The optimization choice... */ +static optimization_t optimization_; + + + +/**************************************************************/ +/**************************************************************/ +/**** ****/ +/**** ****/ +/**** Sending of Modbus ASCII Frames ****/ +/**** ****/ +/**** ****/ +/**************************************************************/ +/**************************************************************/ + +/* + * NOTE: for now the transmit_timeout is silently ignored in ASCII version! + */ +int modbus_ascii_write(int nd, + u8 *data, + size_t data_length, + u16 transaction_id, + const struct timespec *transmit_timeout + ) + { + fd_set rfds; + struct timeval timeout; + int res, bin_data_conv, send_retries; + frame_part_t frame_part; + nd_entry_t *nd_entry; + + /* check if nd is correct... */ + if ((nd_entry = nd_table_get_nd(&nd_table_, nd)) == NULL) + return -1; + + /* check if nd is initialzed... */ + if (nd_entry->fd < 0) + return -1; + + /* THE MAIN LOOP!!! */ + send_retries = ASC_FRAME_SEND_RETRY + 1; /* must try at least once... */ + while (send_retries > 0) { + + /******************************* + * synchronise with the bus... * + *******************************/ + /* Remember that a RS485 bus is half-duplex, so we have to wait until + * nobody is transmitting over the bus for our turn to transmit. + * This will never happen on a modbus network if the master and + * slave state machines never get out of synch (granted, it probably + * only has two states, but a state machine nonetheless), but we want + * to make sure we can re-synchronise if they ever do get out of synch. + * + * The following lines are an attempt at re-synchronising with the bus. + * Unfortunately, due to the completely asynchronous nature of the + * modbus-ascii protocol (there are no time boundaries for sending a frame!), + * it is impossible to guarantee that we will synchronise correctly. + * + * Use of RTS/CTS over a half-duplex coms chanel would eliminate this + * 'feature', but not all RS232/RS485 converters delay activating the + * CTS signal, even though there may be current activity on the bus. + * + * We first wait until the bus is silent for at least 1.5 character interval. + * Note that we only get feedback from the device driver once a whole byte + * has been received, so we must wait longer than 1 character interval to make + * sure there is no current activity on the bus). We then flush the input and + * output queues. + */ + /* NOTES: + * - we do not need to reset the rfds with FD_SET(ttyfd, &rfds) + * before every call to select! We only wait on one file descriptor, + * so if select returns succesfully, it must have that same file + * decriptor set in the rdfs! + * If select returns with a timeout, then we do not get to call + * select again! + * - We do not reset the timeout value. Normally this value is left + * unchanged when select() returns, so we will be witing for longer + * than the desired period. + * On Linux the timeout is changed to reflect the time remaining. + * In this case, things will work more nicely. + */ + FD_ZERO(&rfds); + FD_SET(nd_entry->fd, &rfds); + timeout = nd_entry->time_15_char_; + while ((res = select(nd_entry->fd+1, &rfds, NULL, NULL, &timeout)) != 0) { + if (res > 0) { + /* we are receiving data over the serial port! */ + /* Throw the data away! */ + tcflush(nd_entry->fd, TCIFLUSH); /* flush the input stream */ + /* reset the timeout value! */ + timeout = nd_entry->time_15_char_; + } else { + /* some kind of error ocurred */ + if (errno != EINTR) + /* we were not interrupted by a signal */ + return -1; + /* We will be callind select() again. + * We need to reset the FD SET ! + */ + FD_ZERO(&rfds); + FD_SET(nd_entry->fd, &rfds); + } + } /* while (select()) */ + + /* Flush both input and output streams... */ + /* NOTE: Due to the nature of the modbus protocol, + * when a frame is sent all previous + * frames that may have arrived at the sending node become + * irrelevant. + */ + tcflush(nd_entry->fd, TCIOFLUSH); /* flush the input & output streams */ + recv_buf_reset(&nd_entry->recv_buf_); /* reset the recv buffer */ + + /********************** + * write to output... * + **********************/ + send_buf_reset(&nd_entry->send_buf_); + + frame_part = fp_header; /* start off by sending the header... */ + bin_data_conv = 0; /* binary format data already converted to ascii format... */ + while ((frame_part != fp_done) || + (send_buf_data_count(&nd_entry->send_buf_) > 0)) { + + /* build the frame we will send over the wire... */ + /* We use a state machine with four states... */ + if (frame_part == fp_header) { + if (send_buf_header_append(&nd_entry->send_buf_) >= 0) + frame_part = fp_body; + } + if (frame_part == fp_body) { + res = send_buf_data_add(&nd_entry->send_buf_, data + bin_data_conv, data_length - bin_data_conv); + bin_data_conv += res; + if (bin_data_conv == data_length) + frame_part = fp_lrc; + } + if (frame_part == fp_lrc) { + if (send_buf_lrc_append(&nd_entry->send_buf_) >= 0) + frame_part = fp_tail; + } + if (frame_part == fp_tail) { + if (send_buf_tail_append(&nd_entry->send_buf_) >= 0) + frame_part = fp_done; + } + + /* send the frame... */ + if ((res = write(nd_entry->fd, + send_buf_data(&nd_entry->send_buf_), + send_buf_data_count(&nd_entry->send_buf_))) < 0) { + if ((errno != EAGAIN ) && (errno != EINTR )) { + break; + } else { + /* there is no harm if we get interrupted while writing the frame. + * The ascii version of modbus does not place any timing + * constraints whatsoever... + * + * We simply continue sending the frame... + */ + res = 0; + } + } +#ifdef DEBUG +/* Print each character that has just been sent on the bus */ + { int i; + printf("bytes sent -> ["); + for(i = 0; i < res; i++) + printf("%c", send_buf_data(&nd_entry->send_buf_)[i]); + printf("]\n"); + } +#endif + send_buf_data_purge(&nd_entry->send_buf_, res); + + if ((frame_part == fp_done) && + (send_buf_data_count(&nd_entry->send_buf_) == 0)) + /* sent frame successfully! */ + return data_length; + + } /* while(frame_not_sent) */ + /* NOTE: Some error occured while trying to transmit. It will probably + * not go away by itself, but there is no harm in trying again + * if the upper layer so wishes... + */ + send_retries--; + } /* while() MAIN LOOP */ + + /* maximum retries exceeded */ + return -1; +} + + + +/**************************************************************/ +/**************************************************************/ +/**** ****/ +/**** ****/ +/**** Receiving Modbus ASCII Frames ****/ +/**** ****/ +/**** ****/ +/**************************************************************/ +/**************************************************************/ + + +/************************************/ +/** **/ +/** Read a frame **/ +/** **/ +/************************************/ + +/* A function to read a valid frame off the modbus ascii bus. + * + * NOTES: + * - The returned frame is guaranteed to be a valid frame. + * - The returned length does *not* include the LRC. + */ +/* NOTE: This function is only called from one place in the rest of the code, + * so we might just as well make it inline... + */ +/* RETURNS: number of bytes in received frame + * -1 on read file error + * -2 on timeout + */ +static inline int read_frame(nd_entry_t *nd_entry, + u8 **recv_data_ptr, + struct timespec *end_time) +{ + /* temporary variables... */ + fd_set rfds; + int res; + + /* start by deleting any previously converted frames... */ + recv_buf_frame_purge(&nd_entry->recv_buf_); + + /*==============* + * read a frame * + *==============*/ +#ifdef DEBUG + printf("bytes read -> <"); +#endif + /* The main loop that reads one frame */ + /* (multiple calls to read() ) */ + /* and jumps out as soon as it finds a valid frame. */ + /* NOTE: Do not change this into a do {...} until() loop! + * The while loop may find valid frames in the data read off the + * bus in previous invocations of this same fucntion, and may + * therefore not execute any loop iteration whatsoever, + * and not call read() + */ + while ((*recv_data_ptr = recv_buf_frame(&nd_entry->recv_buf_)) == NULL) { + /* We need more bytes... */ + + /*-----------------------------* + * check for data availability * + *-----------------------------*/ + /* if we can't find a valid frame in the existing data, or no data + * was left over, then we need to read more bytes! + */ + FD_ZERO(&rfds); + FD_SET(nd_entry->fd, &rfds); + {int sel_res = my_select(nd_entry->fd + 1, &rfds, NULL, end_time); + if (sel_res < 0) + return -1; + if (sel_res == 0) + return -2; + } + + /*------------------* + * read frame bytes * + *------------------*/ + /* Read in as many bytes as possible... */ + res = read(nd_entry->fd, + recv_buf_free(&nd_entry->recv_buf_), + recv_buf_free_count(&nd_entry->recv_buf_)); + if (res < 0) { + if (errno != EINTR) + return -1; + else + res = 0; + } +#ifdef DEBUG + {/* display the hex code of each character received */ + int i; + for (i=0; i < res; i++) + printf("%c", recv_buf_free(&nd_entry->recv_buf_)[i]); + } +#endif + recv_buf_data_add(&nd_entry->recv_buf_, res); + } /* while ()*/ +#ifdef DEBUG + printf(">\n"); +#endif + + /* We have a frame! */ + return recv_buf_frame_count(&nd_entry->recv_buf_); +} + + + + + +/**************************************/ +/** **/ +/** Read a Modbus ASCII frame **/ +/** **/ +/**************************************/ + +/* The public function that reads a valid modbus frame. + * + * The returned frame is guaranteed to be different to the + * the frame stored in send_data, and to start with the + * same slave address stored in send_data[0]. + * + * If send_data is NULL, send_data_length = 0, or + * ignore_echo == 0, then the first valid frame read off + * the bus is returned. + * + * return value: The length (in bytes) of the valid frame, + * -1 on error + * -2 on timeout + */ + +int modbus_ascii_read(int *nd, + u8 **recv_data_ptr, + u16 *transaction_id, + const u8 *send_data, + int send_length, + const struct timespec *recv_timeout) { + + struct timespec end_time, *ts_ptr; + int res, recv_length; + int iter; /* Warning: if you change the var type of iter from int, + * then you must also change the INT_MAX constant + * further on in this function... + */ + u8 *local_recv_data_ptr; + nd_entry_t *nd_entry; + + /* Check input parameters... */ + if (nd == NULL) + return -1; + + if (recv_data_ptr == NULL) + recv_data_ptr = &local_recv_data_ptr; + + if ((send_data == NULL) && (send_length != 0)) + return -1; + + /* check if nd is correct... */ + if ((nd_entry = nd_table_get_nd(&nd_table_, *nd)) == NULL) + return -1; + + /* check if nd is initialzed... */ + if (nd_entry->fd < 0) + return -1; + + /* We will potentially read many frames, and we cannot reset the timeout + * for every frame we read. We therefore determine the absolute time_out, + * and use this as a parameter for each call to read_frame() instead of + * using a relative timeout. + * + * NOTE: see also the timeout related comment in the read_frame()= function! + */ + ts_ptr = NULL; + if (recv_timeout != NULL) { + ts_ptr = &end_time; + *ts_ptr = timespec_add_curtime(*recv_timeout); + } + + /* NOTE: When using a half-duplex RS-485 bus, some (most ?) RS232-485 + * converters will send back to the RS232 port whatever we write, + * so we will read in whatever we write out onto the bus. + * We will therefore have to compare + * the first frame we read with the one we sent. If they are + * identical it is because we are in fact working on a RS-485 + * bus and must therefore read in a second frame which will be + * the true response to our query. + * If the first frame we receive is different to the last frame we + * just sent, then we are *not* working on a RS-485 bus, and + * that is already the real response to our query. + * + * Flushing the input cache immediately *after* sending a frame + * could solve this issue, but we have no guarantee that this + * process would not get swapped out between the write() and + * flush() calls, and we could therefore be flushing the response + * frame along with the last frame we sent! + */ + + iter = 0; + while ((res = recv_length = read_frame(nd_entry, recv_data_ptr, ts_ptr)) >= 0) { + if (iter < INT_MAX) iter++; + + if ((send_length <= 0) || (nd_entry->ignore_echo == 0)) + /* any valid frame will do... */ + return recv_length; + + if ((send_length > L2_FRAME_SLAVEID_OFS + 1) && (iter == 1)) + /* We have a frame in send_data, + * so we must make sure we are not reading in the frame just sent... + * + * We must only do this for the first frame we read. Subsequent + * frames are guaranteed not to be the previously sent frame + * since the modbus_ascii_write() resets the recv buffer. + * Remember too that valid modbus responses may be exactly the same + * as the request frame!! + */ + if (recv_length == send_length) + if (memcmp(*recv_data_ptr, send_data, recv_length) == 0) + /* recv == send !!! */ + /* read in another frame. */ + continue; + + /* The frame read is either: + * - different to the frame in send_data + * - or there is only the slave id in send_data[L2_FRAME_SLAVEID_OFS] + * - or both of the above... + */ + if (send_length > L2_FRAME_SLAVEID_OFS) + if (recv_length > L2_FRAME_SLAVEID_OFS) + /* check that frame is from/to the correct slave... */ + if ((*recv_data_ptr)[L2_FRAME_SLAVEID_OFS] == send_data[L2_FRAME_SLAVEID_OFS]) + /* yep, it is... */ + return recv_length; + + /* The frame we have received is not acceptable... + * Let's read a new frame. + */ + } /* while(...) */ + + /* error reading response! */ + /* Return the error returned by read_frame! */ + return res; +} + + + + + +/**************************************************************/ +/**************************************************************/ +/**** ****/ +/**** ****/ +/**** Initialising and Shutting Down Library ****/ +/**** ****/ +/**** ****/ +/**************************************************************/ +/**************************************************************/ + +/******************************/ +/** **/ +/** Load Default Values **/ +/** **/ +/******************************/ + +static void set_defaults(int *baud, + int *parity, + int *data_bits, + int *stop_bits) { + /* Set the default values, if required... */ + if (*baud == 0) + *baud = DEF_BAUD_RATE; + if (*data_bits == 0) + *data_bits = DEF_DATA_BITS; + if (*stop_bits == 0) { + if (*parity == 0) + *stop_bits = DEF_STOP_BITS_NOP; /* no parity */ + else + *stop_bits = DEF_STOP_BITS_PAR; /* parity used */ + } +} + + + + + +/******************************/ +/** **/ +/** Initialise Library **/ +/** **/ +/******************************/ + +int modbus_ascii_init(int nd_count, + optimization_t opt, + int *extra_bytes) +{ +#ifdef DEBUG + printf("modbus_asc_init(): called...\n"); + printf("creating %d node descriptors\n", nd_count); + if (opt == optimize_speed) + printf("optimizing for speed\n"); + if (opt == optimize_size) + printf("optimizing for size\n"); +#endif + + /* check input parameters...*/ + if (0 == nd_count) { + if (extra_bytes != NULL) + // Not the corect value for this layer. + // What we set it to in case this layer is not used! + *extra_bytes = 0; + return 0; + } + if (nd_count <= 0) + goto error_exit_0; + + if (extra_bytes == NULL) + goto error_exit_0; + + /* initialise nd table... */ + if (nd_table_init(&nd_table_, nd_count) < 0) + goto error_exit_0; + + /* remember the optimization choice for later reference... */ + optimization_ = opt; + +#ifdef DEBUG + printf("modbus_asc_init(): returning succesfuly...\n"); +#endif + return 0; + +error_exit_0: + if (extra_bytes != NULL) + *extra_bytes = 0; // The value we set this to in case of error. + return -1; +} + + + +/******************************/ +/** **/ +/** Open node descriptor **/ +/** **/ +/******************************/ + +/* Open a node for master or slave operation. + * Returns the node descriptor, or -1 on error. + * + * This function is mapped onto both + * modbus_connect() and modbus_listen() + */ +int modbus_ascii_connect(node_addr_t node_addr) { + int node_descriptor; + nd_entry_t *nd_entry; + +#ifdef DEBUG + printf("modbus_ascii_connect(): called...\n"); + printf("opening %s\n", node_addr.addr.ascii.device); + printf("baud_rate = %d\n", node_addr.addr.ascii.baud); + printf("parity = %d\n", node_addr.addr.ascii.parity); + printf("data_bits = %d\n", node_addr.addr.ascii.data_bits); + printf("stop_bits = %d\n", node_addr.addr.ascii.stop_bits); + printf("ignore_echo = %d\n", node_addr.addr.ascii.ignore_echo); +#endif + + /* Check for valid address family */ + if (node_addr.naf != naf_ascii) + /* wrong address type... */ + goto error_exit_0; + + /* find a free node descriptor */ + if ((node_descriptor = nd_table_get_free_nd(&nd_table_)) < 0) + /* if no free nodes to initialize, then we are finished... */ + goto error_exit_0; + if ((nd_entry = nd_table_get_nd(&nd_table_, node_descriptor)) == NULL) + /* strange, this should not occur... */ + goto error_exit_0; + + /* set the default values... */ + set_defaults(&(node_addr.addr.ascii.baud), + &(node_addr.addr.ascii.parity), + &(node_addr.addr.ascii.data_bits), + &(node_addr.addr.ascii.stop_bits)); + + if (nd_entry_connect(nd_entry, &node_addr, optimization_) < 0) + goto error_exit_0; + +#ifdef DEBUG + printf("modbus_ascii_connect(): %s open\n", node_addr.addr.ascii.device ); + printf("modbus_ascii_connect(): returning nd=%d\n", node_descriptor); +#endif + return node_descriptor; + + error_exit_0: + return -1; +} + + + +int modbus_ascii_listen(node_addr_t node_addr) { + return modbus_ascii_connect(node_addr); +} + + + +/******************************/ +/** **/ +/** Close node descriptor **/ +/** **/ +/******************************/ + +int modbus_ascii_close(int nd) { + return nd_table_free_nd(&nd_table_, nd); +} + + + +/******************************/ +/** **/ +/** Shutdown Library **/ +/** **/ +/******************************/ + +int modbus_ascii_done(void) { + nd_table_done(&nd_table_); + return 0; +} + + + + + +/******************************/ +/** **/ +/** **/ +/** **/ +/******************************/ +int modbus_ascii_silence_init(void) { + return 0; +} + + + + +/******************************/ +/** **/ +/** **/ +/** **/ +/******************************/ + + +double modbus_ascii_get_min_timeout(int baud, + int parity, + int data_bits, + int stop_bits) { + int parity_bits, start_bits, char_bits; + + set_defaults(&baud, &parity, &data_bits, &stop_bits); + parity_bits = (parity == 0)?0:1; + start_bits = 1; + char_bits = start_bits + data_bits + parity_bits + stop_bits; + return (double)((MAX_ASC_FRAME_LENGTH * char_bits) / baud); +} + + + diff -r 000000000000 -r ae252e0fd9b8 mb_ascii_private.h --- /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 . + * + * This code is made available on the understanding that it will not be + * used in safety-critical situations without a full and competent review. + */ + + + +#ifndef MODBUS_ASCII_PRIVATE_H +#define MODBUS_ASCII_PRIVATE_H + + + +#include "mb_util.h" + + +/* serial port default configuration... */ +#define DEF_DATA_BITS 7 +#define DEF_STOP_BITS_PAR 1 /* default stop bits if parity is used */ +#define DEF_STOP_BITS_NOP 2 /* default stop bits if parity is not used */ +#define DEF_BAUD_RATE 9600 + + +/* Send retries of ascii frames... */ +#define ASC_FRAME_SEND_RETRY 0 + /* NOTES: + * - the above are the retries at the layer1 level, + * higher layers may decide to retry for themselves! + * - For ascii frames, it doesn't make much sense to retry + * if the first try failed... + */ + + +/* Buffer sizes... */ +#define SEND_BUFFER_SIZE_SMALL (MAX_ASC_FRAME_LENGTH / 2) +#define SEND_BUFFER_SIZE_LARGE (MAX_ASC_FRAME_LENGTH) +#define RECV_BUFFER_SIZE_SMALL (MAX_ASC_FRAME_LENGTH / 2) +#define RECV_BUFFER_SIZE_LARGE (MAX_ASC_FRAME_LENGTH) + + +/* Frame lengths... */ + +/* The smallest element in an ascii frame. + * This is used later to define the smallest value of certain buffers... + */ +#define ASC_FRAME_MIN_ELE_LENGTH ASC_FRAME_HEADER_LENGTH + +#if ASC_FRAME_MIN_ELE_LENGTH < ASC_FRAME_TAIL_LENGTH +#undef ASC_FRAME_MIN_ELE_LENGTH +#define ASC_FRAME_MIN_ELE_LENGTH ASC_FRAME_TAIL_LENGTH +#endif + +#if ASC_FRAME_MIN_ELE_LENGTH < ASC_FRAME_LRC_LENGTH +#undef ASC_FRAME_MIN_ELE_LENGTH +#define ASC_FRAME_MIN_ELE_LENGTH ASC_FRAME_LRC_LENGTH +#endif + +#if ASC_FRAME_MIN_ELE_LENGTH < L2_TO_ASC_CODING +#undef ASC_FRAME_MIN_ELE_LENGTH +#define ASC_FRAME_MIN_ELE_LENGTH L2_TO_ASC_CODING +#endif + + +#endif /* MODBUS_ASCII_PRIVATE_H */ + + + + + + + + diff -r 000000000000 -r ae252e0fd9b8 mb_ds_util.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 . + * + * This code is made available on the understanding that it will not be + * used in safety-critical situations without a full and competent review. + */ + + + + /* Data structures used by the modbus protocols... */ + + +#ifndef __MODBUS_DS_UTIL_H +#define __MODBUS_DS_UTIL_H + + +#include "mb_types.h" /* get the data types */ + +/**************************************/ +/** **/ +/** A data structure - linear buffer **/ +/** **/ +/**************************************/ + +/* An unbounded FIFO data structure. + * + * The user/caller writes and reads directly from the data structure's buffer, + * which eliminates slow copying of bytes between the user's and the structure's + * local memory. + * + * The data structure stores the current data linearly in a single memory array, + * i.e. the current data is stored from start to finish from a low address + * to a high address, and does *not* circle back to the bottom of the address + * space as is usual in a circular buffer. This allows the user/caller to + * pass the structure's own byte array on to other functions such as + * read() and write() for file operations. + * + * The FIFO is implemented by allocating more memory than the maximum number + * of bytes it will ever hold, and using the extra bytes at the top of the + * array as the bottom data bytes are released. When we run out of extra bytes, + * (actually, when the number of un-used bytes at the beginning is larger than + * a configured maximum), the whole data is moved down, freeing once again the + * extra bytes at the top of the array. + * + * Remember that we can optimize the data structure so that whenever it becomes + * empty, we can reset it to start off at the bottom of the byte array, i.e. we + * can set the start = end = 0; instead of simply setting the start = end, which + * may point to any position in the array. + * + * Taking the above into consideration, it would probably be a little more efficient + * to implement it as a circular buffer with an additional linearize() function + * the user could call whenever (s)he required the data to be stored linearly. + * Nevertheless, since it has already been implemented as a linear buffer, and since + * under normal circumstances the start and end pointers will be reset to 0 quite + * often (and therefore we get no additional benefit under normal circumstances), + * we will leave it as it is for the time being... + * + * + * The user is expected to call + * lb_init() -> to initialize the structure + * lb_done() -> to free the data structure's memory + * + * The user can store data starting off from... + * lb_free() -> pointer to address of free memory + * lb_free_count() -> number of free bytes available + * and then call + * lb_data_add() + * to add the data to the data structure + * + * Likewise, the user can read the data directly from + * lb_data() -> pointer to address of data location + * lb_free_count() -> number of data bytes available + * and free the data using + * lb_data_purge() + * to remove the data from the data structure + */ + + +typedef struct { + u8 *data; + int data_size; /* size of the *data buffer */ + int data_start; /* offset within *data were valid data starts */ + int data_end; /* offset within *data were valid data ends */ + int max_data_start; /* optimization parameter! When should it be normalised? */ + } lb_buf_t; + +/* NOTE: lb = Linear Buffer */ + +static inline u8 *lb_init(lb_buf_t *buf, int size, int max_data_start) { + if (size <= 0) + return NULL; + + if (max_data_start >= size) + max_data_start = size - 1; + + buf->data_size = size; + buf->data_start = 0; + buf->data_end = 0; + buf->max_data_start = max_data_start; + buf->data = (u8 *)malloc(size); + return buf->data; +} + +static inline void lb_done(lb_buf_t *buf) { + free(buf->data); + buf->data = NULL; +} + +static inline u8 *lb_normalize(lb_buf_t *buf) { + return (u8 *)memmove(buf->data, + buf->data + buf->data_start, + buf->data_end - buf->data_start); +} + +static inline u8 *lb_data(lb_buf_t *buf) { + return buf->data + buf->data_start; +} + +static inline int lb_data_count(lb_buf_t *buf) { + return buf->data_end - buf->data_start; +} + +static inline void lb_data_add(lb_buf_t *buf, int count) { + if ((buf->data_end += count) >= buf->data_size) + buf->data_end = buf->data_size - 1; +} + +static inline u8 *lb_data_purge(lb_buf_t *buf, int count) { + buf->data_start += count; + if (buf->data_start > buf->data_end) + buf->data_start = buf->data_end; + + if ((buf->data_end == buf->data_size) || (buf->data_start >= buf->max_data_start)) + return lb_normalize(buf); + + return buf->data + buf->data_start; +} + +static inline void lb_data_purge_all(lb_buf_t *buf) { + buf->data_start = buf->data_end = 0; +} + +static inline u8 *lb_free(lb_buf_t *buf) { + return buf->data + buf->data_end; +} + +static inline int lb_free_count(lb_buf_t *buf) { + return buf->data_size - buf->data_end; +} + + + + +#endif /* __MODBUS_DS_UTIL_H */ diff -r 000000000000 -r ae252e0fd9b8 mb_layer1.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 . + * + * This code is made available on the understanding that it will not be + * used in safety-critical situations without a full and competent review. + */ + + + +#ifndef MODBUS_LAYER1_H +#define MODBUS_LAYER1_H + +#include /* struct timespec data type */ + +#include "mb_types.h" /* get the data types */ +#include "mb_addr.h" /* get definitions of common variable types */ + + +/* Define max registers and bits */ +#define MAX_READ_BITS 2000 /* Functions 0x01 and 0x02 */ +#define MAX_READ_REGS 125 /* Functions 0x03 and 0x04 */ +#define MAX_WRITE_COILS 1968 /* Function 0x0F */ +#define MAX_WRITE_REGS 123 /* Function 0x10 */ + + +/* Declare TCP layer1 functions */ +#define modbus_write modbus_tcp_write +#define modbus_read modbus_tcp_read +#define modbus_init modbus_tcp_init +#define modbus_done modbus_tcp_done +#define modbus_connect modbus_tcp_connect +#define modbus_listen modbus_tcp_listen +#define modbus_close modbus_tcp_close +#define modbus_silence_init modbus_tcp_silence_init +#define modbus_get_min_timeout modbus_tcp_get_min_timeout + +#include "mb_layer1_prototypes.h" + +#undef modbus_write +#undef modbus_read +#undef modbus_init +#undef modbus_done +#undef modbus_connect +#undef modbus_listen +#undef modbus_close +#undef modbus_silence_init +#undef modbus_get_min_timeout + + + + + +/* Declare RTU layer1 functions */ +#define modbus_write modbus_rtu_write +#define modbus_read modbus_rtu_read +#define modbus_init modbus_rtu_init +#define modbus_done modbus_rtu_done +#define modbus_connect modbus_rtu_connect +#define modbus_listen modbus_rtu_listen +#define modbus_close modbus_rtu_close +#define modbus_silence_init modbus_rtu_silence_init +#define modbus_get_min_timeout modbus_rtu_get_min_timeout + +#include "mb_layer1_prototypes.h" + +#undef modbus_write +#undef modbus_read +#undef modbus_init +#undef modbus_done +#undef modbus_connect +#undef modbus_listen +#undef modbus_close +#undef modbus_silence_init +#undef modbus_get_min_timeout + + + + + +/* Declare ASCII layer1 functions */ +#define modbus_write modbus_ascii_write +#define modbus_read modbus_ascii_read +#define modbus_init modbus_ascii_init +#define modbus_done modbus_ascii_done +#define modbus_connect modbus_ascii_connect +#define modbus_listen modbus_ascii_listen +#define modbus_close modbus_ascii_close +#define modbus_silence_init modbus_ascii_silence_init +#define modbus_get_min_timeout modbus_ascii_get_min_timeout + +#include "mb_layer1_prototypes.h" + +#undef modbus_write +#undef modbus_read +#undef modbus_init +#undef modbus_done +#undef modbus_connect +#undef modbus_listen +#undef modbus_close +#undef modbus_silence_init +#undef modbus_get_min_timeout + + + + +#define modbus_write (*modbus_write ) +#define modbus_read (*modbus_read ) +#define modbus_init (*modbus_init ) +#define modbus_done (*modbus_done ) +#define modbus_connect (*modbus_connect ) +#define modbus_listen (*modbus_listen ) +#define modbus_close (*modbus_close ) +#define modbus_silence_init (*modbus_silence_init ) +#define modbus_get_min_timeout (*modbus_get_min_timeout) + +typedef struct { + #include "mb_layer1_prototypes.h" +} layer1_funct_ptr_t; + +#undef modbus_write +#undef modbus_read +#undef modbus_init +#undef modbus_done +#undef modbus_connect +#undef modbus_listen +#undef modbus_close +#undef modbus_silence_init +#undef modbus_get_min_timeout + + + +extern layer1_funct_ptr_t fptr_[4]; + + + + + +#endif /* MODBUS_LAYER1_H */ + + diff -r 000000000000 -r ae252e0fd9b8 mb_layer1_prototypes.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 . + * + * This code is made available on the understanding that it will not be + * used in safety-critical situations without a full and competent review. + */ + + + + + /* write a modbus frame */ + /* WARNING: when calling this function, the *frame_data buffer + * must be allocated with an extra *extra_bytes + * beyond those required for the frame_length. + * This is because the extra bytes will be used + * to store the crc before sending the frame. + * + * The *extra_bytes value will be returned by the + * modbus_init() function call. + */ + /* NOTE: calling this function will flush the input stream, + * which means any frames that may have arrived + * but have not yet been read using modbus_read() + * will be permanently lost... + */ +int modbus_write(int nd, + u8 *frame_data, + size_t frame_length, + u16 transaction_id, + const struct timespec *transmit_timeout + ); + + /* read a modbus frame */ +/* + * The frame is read from: + * - the node descriptor nd, if nd >= 0 + * - any valid and initialised node descriptor, if nd = -1 + * In this case, the node where the data is eventually read from + * is returned in *nd. + * NOTE: (only avaliable if using TCP) + */ + /* NOTE: calling modbus_write() will flush the input stream, + * which means any frames that may have arrived + * but have not yet been read using modbus_read() + * will be permanently lost... + * + * NOTE: Ususal select semantics for (a: recv_timeout == NULL) and + * (b: *recv_timeout == 0) also apply. + * (a) Indefinite timeout + * (b) Try once, and and quit if no data available. + */ + /* NOTE: send_data and send_length is used to pass to the modbus_read() function + * the frame that was previously sent over the same connection (node). + * This data is then allows the modbus_read() function to ignore any + * data that is read but identical to the previously sent data. This + * is used when using serial ports that echoes back all the data that is + * sent out over the same serial port. When using some RS232 to RS485 + * converters, this functionality is essential as not all these converters + * are capable of not echoing back the sent data. + * These parameters are ignored when using TCP! + */ + + /* RETURNS: number of bytes read + * -1 on read from file/node error + * -2 on timeout + */ +int modbus_read(int *nd, /* node descriptor */ + u8 **recv_data_ptr, + u16 *transaction_id, + const u8 *send_data, + int send_length, + const struct timespec *recv_timeout); + + + /* init the library */ +int modbus_init(int nd_count, /* maximum number of nodes... */ + optimization_t opt, + int *extra_bytes); + + /* shutdown the library...*/ +int modbus_done(void); + + +/* Open a node for master / slave operation. + * Returns the node descriptor, or -1 on error. + */ +int modbus_connect(node_addr_t node_addr); +int modbus_listen(node_addr_t node_addr); + +/* Close a node, needs a node descriptor as argument... */ +int modbus_close(int nd); + +/* Tell the library that the user will probably not be communicating + * for some time... + * This will allow the library to release any resources it will not + * be needing during the silence. + * NOTE: This is onlyused by the TCP version to close down tcp connections + * when the silence will going to be longer than second. + */ +int modbus_silence_init(void); + + /* determine the minmum acceptable timeout... */ + /* NOTE: timeout values passed to modbus_read() lower than the value returned + * by this function may result in frames being aborted midway, since they + * take at least modbus_get_min_timeout() seconds to transmit. + */ +double modbus_get_min_timeout(int baud, + int parity, + int data_bits, + int stop_bits); + + + + + + + + + + diff -r 000000000000 -r ae252e0fd9b8 mb_master.c --- /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 . + * + * This code is made available on the understanding that it will not be + * used in safety-critical situations without a full and competent review. + */ + + +/* mb_master.c */ + + +#include /* File control definitions */ +#include /* Standard input/output */ +#include +#include +#include /* POSIX terminal control definitions */ +#include /* Time structures for select() */ +#include /* POSIX Symbolic Constants */ +#include /* Error definitions */ + +#include /* pthread_mutex_[un]lock() */ + +#include /* required for htons() and ntohs() */ +#include "mb_layer1.h" +#include "mb_master.h" +#include "mb_master_private.h" + +/* #define DEBUG */ /* uncomment to see the data sent and received */ + +#define modbus_write fptr_[layer1_fin].modbus_write +#define modbus_read fptr_[layer1_fin].modbus_read +#define modbus_init fptr_[layer1_fin].modbus_init +#define modbus_done fptr_[layer1_fin].modbus_done +#define modbus_connect fptr_[layer1_fin].modbus_connect +#define modbus_listen fptr_[layer1_fin].modbus_listen +#define modbus_close fptr_[layer1_fin].modbus_close +#define modbus_silence_init fptr_[layer1_fin].modbus_silence_init +#define modbus_get_min_timeout fptr_[layer1_fin].modbus_get_min_timeout + +/* the lower two bits of ttyfd are used to store the index to layer1 function pointers */ +/* layer1_fin index to fptr_[] is in lowest 2 bits of fd */ +#define get_ttyfd() int layer1_fin = fd & 3; int ttyfd = fd / 4; + + + +/******************************************/ +/******************************************/ +/** **/ +/** Global Variables... **/ +/** **/ +/******************************************/ +/******************************************/ + /* The layer 1 (RTU, ASCII, TCP) implementation will be adding some headers and CRC (at the end) + * of the packet we build here (actually currently it is only at the end). Since we want to + * re-use the same buffer so as not to continuosly copy the same info from buffer to buffer, + * we need tp allocate more bytes than the ones we need for this layer. Therefore, the + * extra_bytes parameter. + * + * Note that we add one more extra byte. This is because some packets will not be + * starting off at byte 0, but rather at byte 1 of the buffer. This is in order to guarantee + * that the data that is sent on the buffer is aligned on even bytes (the 16 bit words!). + * This will allow us to reference this memory as an u16 *, without producing 'bus error' + * messages in some embedded devices that do not allow acessing u16 on odd numbered addresses. + */ +static int buff_extra_bytes_; +#define QUERY_BUFFER_SIZE (MAX_L2_FRAME_LENGTH + buff_extra_bytes_ + 1) + + +/******************************************/ +/******************************************/ +/** **/ +/** Local Utility functions... **/ +/** **/ +/******************************************/ +/******************************************/ + + +/* + * Function to determine next transaction id. + * + * We use a library wide transaction id, which means that we + * use a new transaction id no matter what slave to which we will + * be sending the request... + */ +static inline u16 next_transaction_id(void) { + static u16 next_id = 0; + return next_id++; +} + + +/* + * Functions to convert u16 variables + * between network and host byte order + * + * NOTE: Modbus uses MSByte first, just like + * tcp/ip, so we use the htons() and + * ntoh() functions to guarantee + * code portability. + */ +static inline u16 mb_hton(u16 h_value) {return htons(h_value);} +static inline u16 mb_ntoh(u16 m_value) {return ntohs(m_value);} +static inline u8 msb (u16 value) {return (value >> 8) & 0xFF;} +static inline u8 lsb (u16 value) {return value & 0xFF;} + + + + +/*************************************************/ +/*************************************************/ +/** **/ +/** Common functions for Modbus Protocol. **/ +/** **/ +/*************************************************/ +/*************************************************/ + +/* build the common elements of a query frame */ +static inline int build_packet(u8 slave, + u8 function, + u16 start_addr, + u16 count, + u8 *packet) { + union { + u16 u16; + u8 u8[2]; + } tmp; + + packet[0] = slave, + packet[1] = function; + /* NOTE: + * Modbus uses high level addressing starting off from 1, but + * this is sent as 0 on the wire! + * We could expect the user to specify high level addressing + * starting at 1, and do the conversion to start off at 0 here. + * However, to do this we would then need to use an u32 data type + * to correctly hold the address supplied by the user (which could + * correctly be 65536, which does not fit in an u16), which would + * in turn require us to check whether the address supplied by the user + * is correct (i.e. <= 65536). + * I decided to go with the other option of using an u16, and + * requiring the user to use addressing starting off at 0! + */ + /* NOTE: we do not use up casting - i.e. the following + * *((u16 *)(packet+2)) = mb_hton(start_addr); + * because packet+2 is NOT aligned with an even address, and would + * therefore result in 'bus error' when using compilers that do not + * automatically do the required decomposing of this supposedly + * single bus access into two distinct bus accesses. + * (Note that some compilers do do this decomposing automatically + * in which case the following is not necessary). + * At the moment, I (Mario de Sousa) know of at least one cross-compiler + * that does not do the decomposing automatically, i.e. the + * AVR32 cross-compiler. + */ + tmp.u16 = mb_hton(start_addr); + packet[2] = tmp.u8[0]; + packet[3] = tmp.u8[1]; + tmp.u16 = mb_hton(count); + packet[4] = tmp.u8[0]; + packet[5] = tmp.u8[1]; + + return 6; +} + + + +/* Execute a Query/Response transaction between client and server */ +/* returns: <0 -> ERROR: error codes + * >2 -> SUCCESS: frame length + * 0..2 -> will never be returned! + */ +static int mb_transaction(u8 *packet, + int query_length, + u8 **data, + int fd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout) { + int error = TIMEOUT; + int response_length = INTERNAL_ERROR; + u16 send_transaction_id, recv_transaction_id; + get_ttyfd(); /* declare the ttyfd variable, ... */ + + /* We must also initialize the recv_transaction_id with the same value, + * since some layer 1 protocols do not support transaction id's, so + * simply return the recv_transaction_id variable without any changes... + */ + /* NOTE: we re-use the same transaction id for all send re-tries., since, in truth, + * it is still the same transaction. This will also simplify re-synchronising with + * some slaves that keep a buffer of outstanding requests, and will reply to all of + * them, in FIFO order. In this case, once an error occurs we will be swamping the + * slave with requests. By using the same transaction id, we may correctly consider + * the reply to the first request sent as the reply to the third request! This means + * we stop re-trying the sending of further requests, and no longer swamp the slave... + */ + send_transaction_id = recv_transaction_id = next_transaction_id(); + + for (send_retries++; send_retries > 0; send_retries--) { + error = TIMEOUT; + + if (modbus_write(ttyfd, packet, query_length, send_transaction_id, response_timeout) < 0) + {error = PORT_FAILURE; continue;} + + /* if we receive a correct response but with a wrong transaction id or wrong modbus function, we try to + * receive another frame instead of returning an error or re-sending the request! This first frame could + * have been a response to a previous request of ours that timed out waiting for a response, and the + * response we are waiting for could be coming 'any minute now'. + */ + do { + response_length = modbus_read(&ttyfd, data, &recv_transaction_id, + packet, query_length, response_timeout); + + /* TIMEOUT condition */ + /* However, if we had previously received an invalid frame, or some other error, + * we return that error instead! + * Note that the 'error' variable was initialised with the TIMEOUT error + * condition, so if no previous error ocurred, we will be returning the + * TIMEOUT error condition here! + */ + if(response_length == -2) return error; + /* NOTE we want to break out of this while loop without even running the while() + * condition, as that condition is only valid if response_length > 3 !! + */ + if(response_length < 0) {error = PORT_FAILURE; break;} + /* This should never occur! Modbus_read() should only return valid frames! */ + if(response_length < 3) return INTERNAL_ERROR; + + } while (/* we have the wrong transaction id */ + (send_transaction_id != recv_transaction_id) + /* not a response frame to _our_ query */ + || + (((*data)[1] & ~0x80) != packet[1]) + /* NOTE: no need to check whether (*data)[0] = slave! */ + /* This has already been done by the modbus_read() function! */ + ); + + if(response_length < 0) {error = PORT_FAILURE; continue;} + + /* Now check whether we received a Modbus Exception frame */ + if (((*data)[1] & 0x80) != 0) { /* we have an exception frame! */ + /* NOTE: we have already checked above that data[2] exists! */ + if (error_code != NULL) *error_code = (*data)[2]; + return MODBUS_ERROR; + } + /* success! Let's get out of the send retry loop... */ + return response_length; + } + /* reached the end of the retries... */ + return error; +} + + +/**************************************/ +/**************************************/ +/** **/ +/** Modbus Protocol Functions. **/ +/** **/ +/**************************************/ +/**************************************/ + + + +/* Execute a transaction for functions that READ BITS. + * Bits are stored on an int array, one bit per int. + * Called by: read_input_bits() + * read_output_bits() + */ +static int read_bits(u8 function, + u8 slave, + u16 start_addr, + u16 count, + u16 *dest, + int dest_size, + int ttyfd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex) { + + u8 packet[QUERY_BUFFER_SIZE]; + u8 *data; + int response_length, query_length; + int temp, i, bit, dest_pos = 0; + int coils_processed = 0; + + query_length = build_packet(slave, function, start_addr, count, packet); + if (query_length < 0) return INTERNAL_ERROR; + + response_length = mb_transaction(packet, query_length, &data, ttyfd, + send_retries, error_code, response_timeout); + + if (response_length < 0) return response_length; + /* NOTE: Integer division. (count+7)/8 is equivalent to ceil(count/8) */ + if (response_length != 3 + (count+7)/8) return INVALID_FRAME; + if (data[2] != (count+7)/8) return INVALID_FRAME; + + if (NULL != data_access_mutex) pthread_mutex_lock(data_access_mutex); + for( i = 0; (i < data[2]) && (i < dest_size); i++ ) { + temp = data[3 + i]; + for( bit = 0x01; (bit & 0xff) && (coils_processed < count); ) { + dest[dest_pos] = (temp & bit)?1:0; + coils_processed++; + dest_pos++; + bit = bit << 1; + } + } + if (NULL != data_access_mutex) pthread_mutex_unlock(data_access_mutex); + + return response_length; +} + + + +/* Execute a transaction for functions that READ BITS. + * Bits are stored on an u32 array, 32 bits per u32. + * Unused bits in last u32 are set to 0. + * Called by: read_input_bits_u32() + * read_output_bits_u32() + */ +static int read_bits_u32(u8 function, + u8 slave, + u16 start_addr, + u16 count, /* number of bits !! */ + u32 *dest, + int ttyfd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout) { + u8 packet[QUERY_BUFFER_SIZE]; + u8 *data; + int response_length, query_length; + int byte_count, i, dest_pos = 0; + + query_length = build_packet(slave, function, start_addr, count, packet); + if (query_length < 0) return INTERNAL_ERROR; + + response_length = mb_transaction(packet, query_length, &data, ttyfd, + send_retries, error_code, response_timeout); + + if (response_length < 0) return response_length; + /* NOTE: Integer division. (count+7)/8 is equivalent to ceil(count/8) */ + if (response_length != 3 + (count+7)/8) return INVALID_FRAME; + if (data[2] != (count+7)/8) return INVALID_FRAME; + + byte_count = data[2]; + data += 3; + /* handle groups of 4 bytes... */ + for(i = 0, dest_pos = 0; i + 3 < byte_count; i += 4, dest_pos++) + dest[dest_pos] = data[i] + data[i+1]*0x100 + data[i+2]*0x10000 + data[i+3]*0x1000000; + /* handle any remaining bytes... begining with the last! */ + if (i < byte_count) dest[dest_pos] = 0; + for(byte_count--; i <= byte_count; byte_count--) + dest[dest_pos] = dest[dest_pos]*0x100 + data[byte_count]; + + return response_length; +} + + + +/* FUNCTION 0x01 - Read Coils + * Bits are stored on an int array, one bit per int. + */ +inline int read_output_bits(u8 slave, + u16 start_addr, + u16 count, + u16 *dest, + int dest_size, + int ttyfd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex) { + if( count > MAX_READ_BITS ) { + count = MAX_READ_BITS; + #ifdef DEBUG + fprintf( stderr, "Too many coils requested.\n" ); + #endif + } + + return read_bits(0x01 /* function */, + slave, start_addr, count, dest, dest_size, ttyfd, + send_retries, error_code, response_timeout, data_access_mutex); +} + + + +/* FUNCTION 0x01 - Read Coils + * Bits are stored on an u32 array, 32 bits per u32. + * Unused bits in last u32 are set to 0. + */ +inline int read_output_bits_u32(u8 slave, + u16 start_addr, + u16 count, + u32 *dest, + int ttyfd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout) { + if( count > MAX_READ_BITS ) { + count = MAX_READ_BITS; + #ifdef DEBUG + fprintf( stderr, "Too many coils requested.\n" ); + #endif + } + + return read_bits_u32(0x01 /* function */, + slave, start_addr, count, dest, ttyfd, + send_retries, error_code, response_timeout); +} + + +/* FUNCTION 0x02 - Read Discrete Inputs + * Bits are stored on an int array, one bit per int. + */ +inline int read_input_bits(u8 slave, + u16 start_addr, + u16 count, + u16 *dest, + int dest_size, + int ttyfd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex) { + if( count > MAX_READ_BITS ) { + count = MAX_READ_BITS; + #ifdef DEBUG + fprintf( stderr, "Too many coils requested.\n" ); + #endif + } + + return read_bits(0x02 /* function */, + slave, start_addr, count, dest, dest_size, ttyfd, + send_retries, error_code, response_timeout, data_access_mutex); +} + + + +/* FUNCTION 0x02 - Read Discrete Inputs + * Bits are stored on an u32 array, 32 bits per u32. + * Unused bits in last u32 are set to 0. + */ +inline int read_input_bits_u32(u8 slave, + u16 start_addr, + u16 count, + u32 *dest, + int ttyfd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout) { + if( count > MAX_READ_BITS ) { + count = MAX_READ_BITS; + #ifdef DEBUG + fprintf( stderr, "Too many coils requested.\n" ); + #endif + } + + return read_bits_u32(0x02 /* function */, + slave, start_addr, count, dest, ttyfd, + send_retries, error_code, response_timeout); +} + + + + + + +/* Execute a transaction for functions that READ REGISTERS. + * Called by: read_input_words() + * read_output_words() + */ +static int read_registers(u8 function, + u8 slave, + u16 start_addr, + u16 count, + u16 *dest, + int dest_size, + int ttyfd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex) { + u8 *data; + u8 packet[QUERY_BUFFER_SIZE]; + int response_length; + int query_length; + int temp,i; + + query_length = build_packet(slave, function, start_addr, count, packet); + if (query_length < 0) return INTERNAL_ERROR; + + response_length = mb_transaction(packet, query_length, &data, ttyfd, + send_retries, error_code, response_timeout); + + if (response_length < 0) return response_length; + if (response_length != 3 + 2*count) return INVALID_FRAME; + if (data[2] != 2*count) return INVALID_FRAME; + + if (NULL != data_access_mutex) pthread_mutex_lock(data_access_mutex); + for(i = 0; (i < (data[2]*2)) && (i < dest_size); i++ ) { + temp = data[3 + i *2] << 8; /* copy reg hi byte to temp hi byte*/ + temp = temp | data[4 + i * 2]; /* copy reg lo byte to temp lo byte*/ + dest[i] = temp; + } + if (NULL != data_access_mutex) pthread_mutex_unlock(data_access_mutex); + + return response_length; +} + + + +/* Execute a transaction for functions that READ REGISTERS. + * return the array with the data to the calling function + * Called by: read_input_words_u16_ref() + * read_output_words_u16_ref() + */ + +static int read_registers_u16_ref(u8 function, + u8 slave, + u16 start_addr, + u16 count, + u16 **dest, + int ttyfd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout) { + u8 *data; + u8 packet[QUERY_BUFFER_SIZE]; + int response_length; + int query_length; + int i, byte_count; + + query_length = build_packet(slave, function, start_addr, count, packet); + if (query_length < 0) return INTERNAL_ERROR; + + response_length = mb_transaction(packet, query_length, &data, ttyfd, + send_retries, error_code, response_timeout); + + if (response_length < 0) return response_length; + if (response_length != 3 + 2*count) return INVALID_FRAME; + if (data[2] != 2*count) return INVALID_FRAME; + + byte_count = data[2]; + data = data + 3; /* & data[3] */ + + if (ntohs(0x0102) != 0x0102) { + /* little endian host... => we need to swap the bytes! */ + for(i = 0; i < byte_count; i++ ) { + /* the following 3 lines result in the two values being exchanged! */ + data[i ] = data[i] ^ data[i+1]; + data[i+1] = data[i] ^ data[i+1]; + data[i ] = data[i] ^ data[i+1]; + } + } + *dest = (u16 *)data; + return byte_count; +} + + + + +/* Execute a transaction for functions that READ REGISTERS. + * u16 registers are stored in array of u32, two registers per u32. + * Unused bits of last u32 element are set to 0. + * Called by: read_input_words_u32() + * read_output_words_u32() + */ +static int read_registers_u32(u8 function, + u8 slave, + u16 start_addr, + u16 count, + u32 *dest, + int ttyfd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout) { + u8 *data; + u8 packet[QUERY_BUFFER_SIZE]; + int response_length; + int query_length; + int i, byte_count, dest_pos; + + query_length = build_packet(slave, function, start_addr, count, packet); + if (query_length < 0) return INTERNAL_ERROR; + + response_length = mb_transaction(packet, query_length, &data, ttyfd, + send_retries, error_code, response_timeout); + + if (response_length < 0) return response_length; + if (response_length != 3 + 2*count) return INVALID_FRAME; + if (data[2] != 2*count) return INVALID_FRAME; + + byte_count = data[2]; + data += 3; + + if (ntohs(0x0102) == 0x0102) { + /* big endian host... */ + /* handle groups of 4 bytes... */ + for(i = 0, dest_pos = 0; i + 3 < byte_count; i += 4, dest_pos++) { + *(((u8 *)(dest + dest_pos))+ 0) = *(data+i+3); + *(((u8 *)(dest + dest_pos))+ 1) = *(data+i+4); + *(((u8 *)(dest + dest_pos))+ 2) = *(data+i+0); + *(((u8 *)(dest + dest_pos))+ 3) = *(data+i+1); + } + /* handle any remaining bytes... + * since byte_count is supposed to be multiple of 2, + * (and has already been verified above 'if (data[2] != 2*count)') + * this will be either 2, or none at all! + */ + if (i + 1 < byte_count) + *(((u8 *)(dest + dest_pos))+ 0) = 0; + *(((u8 *)(dest + dest_pos))+ 1) = 0; + *(((u8 *)(dest + dest_pos))+ 2) = *(data+i+0); + *(((u8 *)(dest + dest_pos))+ 3) = *(data+i+1); + } else { + /* little endian host... */ + /* handle groups of 4 bytes... */ + for(i = 0, dest_pos = 0; i + 3 < byte_count; i += 4, dest_pos++) { + *(((u8 *)(dest + dest_pos))+ 0) = *(data+i+1); + *(((u8 *)(dest + dest_pos))+ 1) = *(data+i+0); + *(((u8 *)(dest + dest_pos))+ 2) = *(data+i+3); + *(((u8 *)(dest + dest_pos))+ 3) = *(data+i+2); + } + /* handle any remaining bytes... + * since byte_count is supposed to be multiple of 2, + * (and has already been verified above 'if (data[2] != 2*count)') + * this will be either 2, or none at all! + */ + if (i + 1 < byte_count) + *(((u8 *)(dest + dest_pos))+ 0) = *(data+i+1); + *(((u8 *)(dest + dest_pos))+ 1) = *(data+i+0); + *(((u8 *)(dest + dest_pos))+ 2) = 0; + *(((u8 *)(dest + dest_pos))+ 3) = 0; + } + + return response_length; +} + + + + + + + +/* FUNCTION 0x03 - Read Holding Registers */ +inline int read_output_words(u8 slave, + u16 start_addr, + u16 count, + u16 *dest, + int dest_size, + int ttyfd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex) { + if( count > MAX_READ_REGS ) { + count = MAX_READ_REGS; + #ifdef DEBUG + fprintf( stderr, "Too many registers requested.\n" ); + #endif + } + + return read_registers(0x03 /* function */, + slave, start_addr, count, dest, dest_size, ttyfd, + send_retries, error_code, response_timeout, data_access_mutex); +} + + + + +/* FUNCTION 0x03 - Read Holding Registers + * u16 registers are stored in array of u32, two registers per u32. + * Unused bits of last u32 element are set to 0. + */ +inline int read_output_words_u32(u8 slave, + u16 start_addr, + u16 count, + u32 *dest, + int ttyfd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout) { + if( count > MAX_READ_REGS ) { + count = MAX_READ_REGS; + #ifdef DEBUG + fprintf( stderr, "Too many registers requested.\n" ); + #endif + } + + return read_registers_u32(0x03 /* function */, + slave, start_addr, count, dest, ttyfd, + send_retries, error_code, response_timeout); +} + + + + +/* FUNCTION 0x03 - Read Holding Registers + * return the array with the data to the calling function + */ +inline int read_output_words_u16_ref(u8 slave, + u16 start_addr, + u16 count, + u16 **dest, + int ttyfd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout) { + if( count > MAX_READ_REGS ) { + count = MAX_READ_REGS; + #ifdef DEBUG + fprintf( stderr, "Too many registers requested.\n" ); + #endif + } + + return read_registers_u16_ref(0x03 /* function */, + slave, start_addr, count, dest, ttyfd, send_retries, + error_code, response_timeout); +} + + + + +/* FUNCTION 0x04 - Read Input Registers */ +inline int read_input_words(u8 slave, + u16 start_addr, + u16 count, + u16 *dest, + int dest_size, + int ttyfd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex) { + if( count > MAX_READ_REGS ) { + count = MAX_READ_REGS; + #ifdef DEBUG + fprintf( stderr, "Too many input registers requested.\n" ); + #endif + } + + return read_registers(0x04 /* function */, + slave, start_addr, count, dest, dest_size, ttyfd, send_retries, + error_code, response_timeout, data_access_mutex); +} + + +/* FUNCTION 0x04 - Read Input Registers + * u16 registers are stored in array of u32, two registers per u32. + * Unused bits of last u32 element are set to 0. + */ +inline int read_input_words_u32(u8 slave, + u16 start_addr, + u16 count, + u32 *dest, + int ttyfd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout) { + if( count > MAX_READ_REGS ) { + count = MAX_READ_REGS; + #ifdef DEBUG + fprintf( stderr, "Too many input registers requested.\n" ); + #endif + } + + return read_registers_u32(0x04 /* function */, + slave, start_addr, count, dest, ttyfd, send_retries, + error_code, response_timeout); +} + + + + +/* FUNCTION 0x04 - Read Input Registers + * return the array with the data to the calling function + */ +inline int read_input_words_u16_ref(u8 slave, + u16 start_addr, + u16 count, + u16 **dest, + int ttyfd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout) { + if( count > MAX_READ_REGS ) { + count = MAX_READ_REGS; + #ifdef DEBUG + fprintf( stderr, "Too many input registers requested.\n" ); + #endif + } + + return read_registers_u16_ref(0x04 /* function */, + slave, start_addr, count, dest, ttyfd, send_retries, + error_code, response_timeout); +} + + + +/* Execute a transaction for functions that WRITE a sinlge BIT. + * Called by: write_output_bit() + * write_output_word() + */ +static int set_single(u8 function, + u8 slave, + u16 addr, + u16 value, + int ttyfd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex) { + u8 packet[QUERY_BUFFER_SIZE]; + u8 *data; + int query_length, response_length; + + if (NULL != data_access_mutex) pthread_mutex_lock(data_access_mutex); + query_length = build_packet(slave, function, addr, value, packet); + if (NULL != data_access_mutex) pthread_mutex_unlock(data_access_mutex); + if (query_length < 0) return INTERNAL_ERROR; + + response_length = mb_transaction(packet, query_length, &data, ttyfd, send_retries, + error_code, response_timeout); + + if (response_length < 0) return response_length; + if (response_length != 6) return INVALID_FRAME; + + if ((data[2] != packet[2]) || (data[3] != packet[3]) || + (data[4] != packet[4]) || (data[5] != packet[5])) + return INVALID_FRAME; + + return response_length; +} + + + + + + +/* FUNCTION 0x05 - Force Single Coil */ +inline int write_output_bit(u8 slave, + u16 coil_addr, + u16 state, + int fd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex) { + if (state) state = 0xFF00; + + return set_single(0x05 /* function */, + slave, coil_addr, state, fd, send_retries, + error_code, response_timeout, data_access_mutex); +} + + + + + +/* FUNCTION 0x06 - Write Single Register */ +inline int write_output_word(u8 slave, + u16 reg_addr, + u16 value, + int fd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex) { + return set_single(0x06 /* function */, + slave, reg_addr, value, fd, send_retries, + error_code, response_timeout, data_access_mutex); +} + + + + +/* FUNCTION 0x0F - Force Multiple Coils */ +int write_output_bits(u8 slave, + u16 start_addr, + u16 coil_count, + u16 *data, + int ttyfd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex) { + int byte_count, i; + u8 bit; + int coil_check = 0; + int data_array_pos = 0; + int query_length, response_length; + u8 packet[QUERY_BUFFER_SIZE]; + u8 *rdata; + + if( coil_count > MAX_WRITE_COILS ) { + coil_count = MAX_WRITE_COILS; + #ifdef DEBUG + fprintf( stderr, "Writing to too many coils.\n" ); + #endif + } + + query_length = build_packet(slave, 0x0F /* function */, + start_addr, coil_count, packet); + if (query_length < 0) return INTERNAL_ERROR; + + /* NOTE: Integer division. (count+7)/8 is equivalent to ceil(count/8) */ + byte_count = (coil_count+7)/8; + packet[query_length] = byte_count; + + if (NULL != data_access_mutex) pthread_mutex_lock(data_access_mutex); + bit = 0x01; + for(i = 0; i < byte_count; i++) { + packet[++query_length] = 0; + while((bit & 0xFF) && (coil_check++ < coil_count)) { + if(data[data_array_pos++]) {packet[query_length] |= bit;} + else {packet[query_length] &= ~bit;} + bit <<= 1; + } + bit = 0x01; + } + if (NULL != data_access_mutex) pthread_mutex_unlock(data_access_mutex); + + response_length = mb_transaction(packet, ++query_length, &rdata, ttyfd, send_retries, + error_code, response_timeout); + + if (response_length < 0) return response_length; + if (response_length != 6) return INVALID_FRAME; + if ((rdata[2] != packet[2]) || + (rdata[3] != packet[3]) || + (rdata[4] != packet[4]) || + (rdata[5] != packet[5])) return INVALID_FRAME; + + return response_length; +} + + + +/* FUNCTION 0x0F - Force Multiple Coils + * Bits should be stored on an u32 array, 32 bits per u32. + * Unused bits in last u32 should be set to 0. + */ +int write_output_bits_u32(u8 slave, + u16 start_addr, + u16 coil_count, + u32 *data, + int ttyfd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout) { + int org_pos, byte_count, i; + int query_length, response_length; + u8 packet[QUERY_BUFFER_SIZE]; + u8 *rdata; + + if( coil_count > MAX_WRITE_COILS ) { + coil_count = MAX_WRITE_COILS; + #ifdef DEBUG + fprintf( stderr, "Writing to too many coils.\n" ); + #endif + } + + query_length = build_packet(slave, 0x0F /* function */, + start_addr, coil_count, packet); + if (query_length < 0) return INTERNAL_ERROR; + + /* NOTE: Integer division. This is equivalent of determining the ceil(count/8) */ + byte_count = (coil_count+7)/8; + packet[query_length] = byte_count; + + /* handle groups of 4 bytes... */ + for(i = 0, org_pos = 0; i + 3 < byte_count; i += 4, org_pos++) { + packet[++query_length] = data[org_pos] & 0xFF; data[org_pos] >>= 8; + packet[++query_length] = data[org_pos] & 0xFF; data[org_pos] >>= 8; + packet[++query_length] = data[org_pos] & 0xFF; data[org_pos] >>= 8; + packet[++query_length] = data[org_pos] & 0xFF; + } + /* handle any remaining bytes... */ + for(; i < byte_count; i++) { + packet[++query_length] = data[org_pos] & 0xFF; + data[org_pos] >>= 8; + } + + response_length = mb_transaction(packet, ++query_length, &rdata, ttyfd, send_retries, + error_code, response_timeout); + + if (response_length < 0) return response_length; + if (response_length != 6) return INVALID_FRAME; + if ((rdata[2] != packet[2]) || + (rdata[3] != packet[3]) || + (rdata[4] != packet[4]) || + (rdata[5] != packet[5])) return INVALID_FRAME; + + return response_length; +} + + + + + +/* FUNCTION 0x10 - Force Multiple Registers */ +int write_output_words(u8 slave, + u16 start_addr, + u16 reg_count, + u16 *data, + int ttyfd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex) { + u8 byte_count; + int i, query_length, response_length; + u8 packet[QUERY_BUFFER_SIZE]; + u8 *rdata; + + if( reg_count > MAX_WRITE_REGS ) { + reg_count = MAX_WRITE_REGS; + #ifdef DEBUG + fprintf( stderr, "Trying to write to too many registers.\n" ); + #endif + } + + query_length = build_packet(slave, 0x10 /* function */, + start_addr, reg_count, packet); + if (query_length < 0) return INTERNAL_ERROR; + + byte_count = reg_count*2; + packet[query_length] = byte_count; + + if (NULL != data_access_mutex) pthread_mutex_lock(data_access_mutex); + for( i = 0; i < reg_count; i++ ) { + packet[++query_length] = data[i] >> 8; + packet[++query_length] = data[i] & 0x00FF; + } + if (NULL != data_access_mutex) pthread_mutex_unlock(data_access_mutex); + + response_length = mb_transaction(packet, ++query_length, &rdata, ttyfd, send_retries, + error_code, response_timeout); + + if (response_length < 0) return response_length; + if (response_length != 6) return INVALID_FRAME; + if ((rdata[2] != packet[2]) || + (rdata[3] != packet[3]) || + (rdata[4] != packet[4]) || + (rdata[5] != packet[5])) return INVALID_FRAME; + + return response_length; +} + + + + +/* FUNCTION 0x10 - Force Multiple Registers + * u16 registers are stored in array of u32, two registers per u32. + * Unused bits of last u32 element are set to 0. + */ +int write_output_words_u32(u8 slave, + u16 start_addr, + /* number of 16 bit registers packed in the u32 array! */ + u16 reg_count, + u32 *data, + int ttyfd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout) { + u8 byte_count; + int i, query_length, response_length; + u8 packet_[QUERY_BUFFER_SIZE]; + u8 *packet = packet_; /* remove the const'ness of packet_ */ + u8 *rdata; + + if( reg_count > MAX_WRITE_REGS ) { + reg_count = MAX_WRITE_REGS; + #ifdef DEBUG + fprintf( stderr, "Trying to write to too many registers.\n" ); + #endif + } + + /* Make sure that the de-referencing and up-casting going on later on in + * this function, i.e. code like the following line: + * *((u16 *)packet) = XXX + * will result in u16 words starting off on even addresses. + * If we don't do this, some compilers (e.g. AVR32 cross-compiler) will + * generate code which, when executed, will result in 'bus error'. + * + * The following packet++ means that the first byte of the packet array is + * essentially never used. Notice too that the size of thepacket array + * already takes into account this un-used byte. + */ + packet++; + + query_length = build_packet(slave, 0x10 /* function */, + start_addr, reg_count, packet); + if (query_length < 0) return INTERNAL_ERROR; + + byte_count = reg_count*2; + packet[query_length] = byte_count; + + /* handle groups of 4 bytes... */ + for(i = 0; 4*i + 3 < byte_count; i++) { + *((u16 *)(packet+(++query_length))) = mb_hton(data[i]); ++query_length; + *((u16 *)(packet+(++query_length))) = mb_hton(data[i] >> 16); ++query_length; + } + + /* handle any remaining bytes... + * since byte_count is supposed to be multiple of 2, + * (and has already been verified above 'if (data[2] != 2*count)') + * this will be either 2, or none at all! + */ + if (4*i + 1 < byte_count) { + *((u16 *)(packet+(++query_length))) = mb_hton(data[i]); ++query_length; + } + + response_length = mb_transaction(packet, ++query_length, &rdata, ttyfd, send_retries, + error_code, response_timeout); + + if (response_length < 0) return response_length; + if (response_length != 6) return INVALID_FRAME; + if ((rdata[2] != packet[2]) || + (rdata[3] != packet[3]) || + (rdata[4] != packet[4]) || + (rdata[5] != packet[5])) return INVALID_FRAME; + + return response_length; +} + + + + + + +/************************************************/ +/************************************************/ +/** **/ +/** Modbus Library Management Functions. **/ +/** **/ +/************************************************/ +/************************************************/ + + + +/* Initialise the Modbus Master Layer */ +int mb_master_init__(int extra_bytes) { + #ifdef DEBUG + fprintf(stderr, "mb_master_init__(extra_bytes=%d), QUERY_BUFFER_SIZE=%d\n", extra_bytes, QUERY_BUFFER_SIZE); + #endif + buff_extra_bytes_ = extra_bytes; + return 0; +} + + +/* Shut down the Modbus Master Layer */ +int mb_master_done__(void) { + return 0; +} + + +#if 0 +int mb_master_init(int nd_count) { + int extra_bytes; + + #ifdef DEBUG + fprintf( stderr, "mb_master_init()\n"); + fprintf( stderr, "creating %d nodes\n", nd_count); + #endif + + /* initialise layer 1 library */ + if (modbus_init(nd_count, DEF_OPTIMIZATION, &extra_bytes) < 0) + goto error_exit_0; + + /* initialise this library */ + if (mb_master_init__(extra_bytes) < 0) + goto error_exit_1; + + return 0; + +error_exit_1: + modbus_done(); +error_exit_0: + return -1; +} + + +int mb_master_done(void) { + mb_master_done__(); + return modbus_done(); +} +#endif + + +/* Establish a connection to a remote server/slave */ +/* NOTE: We use the lower 2 bits of the returned node id to identify which + * layer1 implementation to use. + * 0 -> TCP + * 1 -> RTU + * 2 -> ASCII + * 4 -> unused + * The node id used by the layer1 is shifted left 2 bits + * before returning the node id to the caller! + */ +int mb_master_connect(node_addr_t node_addr) { + int res = -1; + + #ifdef DEBUG + fprintf( stderr, "mb_master_tcp connect()\n"); + #endif + + /* call layer 1 library */ + switch(node_addr.naf) { + case naf_tcp: + res = modbus_tcp_connect(node_addr); + if (res >= 0) res = res*4 + 0 /* offset into fptr_ with TCP functions */; + return res; + case naf_rtu: + res = modbus_rtu_connect(node_addr); + if (res >= 0) res = res*4 + 1 /* offset into fptr_ with RTU functions */; + return res; + case naf_ascii: + res = modbus_ascii_connect(node_addr); + if (res >= 0) res = res*4 + 2 /* offset into fptr_ with ASCII functions */; + return res; + } + + return -1; +} + + + + + +/* Shut down a connection to a remote server/slave */ +int mb_master_close(int fd) { + #ifdef DEBUG + fprintf( stderr, "mb_master_close(): nd = %d\n", fd); + #endif + get_ttyfd(); /* declare the ttyfd variable, ... */ + /* call layer 1 library */ + return modbus_close(ttyfd); +} + + + + + + +/* Tell the library that communications will be suspended for some time. */ +/* RTU and ASCII versions ignore this function + * TCP version closes all the open tcp connections (connections are automatically + * re-established the next time an IO function to the slave is requested). + * To be more precise, the TCP version makes an estimate of how long + * the silence will be based on previous invocations to this exact same + * function, and will only close the connections if this silence is + * expected to be longer than 1 second! + * (The closing of connections is specified in Modbus specification) + */ +int mb_master_tcp_silence_init(void) { + #ifdef DEBUG + fprintf( stderr, "mb_master_silence_init():\n"); + #endif + /* call layer 1 library */ + return modbus_tcp_silence_init(); +} + + diff -r 000000000000 -r ae252e0fd9b8 mb_master.h --- /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 . + * + * This code is made available on the understanding that it will not be + * used in safety-critical situations without a full and competent review. + */ + + +/* mb_master.h */ + + +#ifndef MODBUS_MASTER_H +#define MODBUS_MASTER_H + +#include /* struct timespec data structure */ + +#include "mb_types.h" /* get the data types */ +#include "mb_addr.h" /* get definition of common variable types and error codes */ + + + +/*********************************************************************** + + Note: All functions used for sending or receiving data via + modbus return these return values. + + + Returns: string_length if OK + -1 on internal error or port failure + -2 on timeout + -3 if a valid yet un-expected frame is received! + -4 for modbus exception errors + (in this case exception code is returned in *error_code) + +***********************************************************************/ + + +/* FUNCTION 0x01 - Read Coils + * Bits are stored on an int array, one bit per int. + */ +inline int read_output_bits(u8 slave, + u16 start_addr, + u16 count, + u16 *dest, + int dest_size, + int fd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex); +#define read_coils(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10) \ + read_output_bits(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10) + + +/* FUNCTION 0x01 - Read Coils + * Bits are stored on an u32 array, 32 bits per u32. + * Unused bits in last u32 are set to 0. + */ +inline int read_output_bits_u32(u8 slave, + u16 start_addr, + u16 count, + u32 *dest, + int fd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout); +#define read_coils_u32(p1,p2,p3,p4,p5,p6,p7,p8) \ + read_output_bits_u32(p1,p2,p3,p4,p5,p6,p7,p8) + + + + +/* FUNCTION 0x02 - Read Discrete Inputs + * Bits are stored on an int array, one bit per int. + */ +inline int read_input_bits(u8 slave, + u16 start_addr, + u16 count, + u16 *dest, + int dest_size, + int fd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex); +#define read_discrete_inputs(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10) \ + read_input_bits (p1,p2,p3,p4,p5,p6,p7,p8,p9,p10) + + +/* FUNCTION 0x02 - Read Discrete Inputs + * Bits are stored on an u32 array, 32 bits per u32. + * Unused bits in last u32 are set to 0. + */ +inline int read_input_bits_u32(u8 slave, + u16 start_addr, + u16 count, + u32 *dest, + int fd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout); +#define read_discrete_inputs_u32(p1,p2,p3,p4,p5,p6,p7,p8) \ + read_input_bits_u32 (p1,p2,p3,p4,p5,p6,p7,p8) + + + + +/* FUNCTION 0x03 - Read Holding Registers */ +inline int read_output_words(u8 slave, + u16 start_addr, + u16 count, + u16 *dest, + int dest_size, + int fd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex); +#define read_holding_registers(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10) \ + read_output_words (p1,p2,p3,p4,p5,p6,p7,p8,p9,p10) + + +/* FUNCTION 0x03 - Read Holding Registers + * u16 registers are stored in array of u32, two registers per u32. + * Unused bits of last u32 element are set to 0. + */ +inline int read_output_words_u32(u8 slave, + u16 start_addr, + u16 count, + u32 *dest, + int fd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout); +#define read_holding_registers_u32(p1,p2,p3,p4,p5,p6,p7,p8) \ + read_output_words_u32 (p1,p2,p3,p4,p5,p6,p7,p8) + + +/* FUNCTION 0x03 - Read Holding Registers + * return the array with the data to the calling function + */ +inline int read_output_words_u16_ref(u8 slave, + u16 start_addr, + u16 count, + u16 **dest, + int ttyfd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout); +#define read_holding_registers_u16_ref(p1,p2,p3,p4,p5,p6,p7,p8) \ + read_output_words_u16_ref (p1,p2,p3,p4,p5,p6,p7,p8) + + + +/* FUNCTION 0x04 - Read Input Registers */ +inline int read_input_words(u8 slave, + u16 start_addr, + u16 count, + u16 *dest, + int dest_size, + int fd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex); +#define read_input_registers(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10) \ + read_input_words (p1,p2,p3,p4,p5,p6,p7,p8,p9,p10) + + + +/* FUNCTION 0x04 - Read Input Registers + * u16 registers are stored in array of u32, two registers per u32. + * Unused bits of last u32 element are set to 0. + */ +inline int read_input_words_u32(u8 slave, + u16 start_addr, + u16 count, + u32 *dest, + int fd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout); +#define read_input_registers_u32(p1,p2,p3,p4,p5,p6,p7,p8) \ + read_input_words_u32 (p1,p2,p3,p4,p5,p6,p7,p8) + + + +/* FUNCTION 0x04 - Read Input Registers + * return the array with the data to the calling function + */ +inline int read_input_words_u16_ref(u8 slave, + u16 start_addr, + u16 count, + u16 **dest, + int ttyfd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout); +#define read_input_registers_u16_ref(p1,p2,p3,p4,p5,p6,p7,p8) \ + read_input_words_u16_ref (p1,p2,p3,p4,p5,p6,p7,p8) + + + + +/* FUNCTION 0x05 - Force Single Coil */ +inline int write_output_bit(u8 slave, + u16 coil_addr, + u16 state, + int fd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex); +#define force_single_coil(p1,p2,p3,p4,p5,p6,p7,p8) \ + write_output_bit (p1,p2,p3,p4,p5,p6,p7,p8) + + + + + + +/* FUNCTION 0x06 - Write Single Register */ +inline int write_output_word(u8 slave, + u16 reg_addr, + u16 value, + int fd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex); +#define write_single_register(p1,p2,p3,p4,p5,p6,p7,p8) \ + write_output_word (p1,p2,p3,p4,p5,p6,p7,p8) + + + + + +/* FUNCTION 0x0F - Force Multiple Coils */ +int write_output_bits(u8 slave, + u16 start_addr, + u16 coil_count, + u16 *data, + int fd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex); +#define force_multiple_coils(p1,p2,p3,p4,p5,p6,p7,p8,p9) \ + write_output_bits (p1,p2,p3,p4,p5,p6,p7,p8,p9) + + +/* FUNCTION 0x0F - Force Multiple Coils + * Bits should be stored on an u32 array, 32 bits per u32. + * Unused bits in last u32 should be set to 0. + */ +int write_output_bits_u32(u8 slave, + u16 start_addr, + u16 coil_count, + u32 *data, + int fd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout); +#define force_multiple_coils_u32(p1,p2,p3,p4,p5,p6,p7,p8) \ + write_output_bits_u32 (p1,p2,p3,p4,p5,p6,p7,p8) + + + +/* FUNCTION 0x10 - Force Multiple Registers */ +int write_output_words(u8 slave, + u16 start_addr, + u16 reg_count, + u16 *data, + int fd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout, + pthread_mutex_t *data_access_mutex); +#define force_multiple_registers(p1,p2,p3,p4,p5,p6,p7,p8,p9) \ + write_output_words (p1,p2,p3,p4,p5,p6,p7,p8,p9) + + + +/* FUNCTION 0x10 - Force Multiple Registers + * u16 registers are stored in array of u32, two registers per u32. + * Unused bits of last u32 element are set to 0. + */ +int write_output_words_u32(u8 slave, + u16 start_addr, + u16 reg_count, + u32 *data, + int fd, + int send_retries, + u8 *error_code, + const struct timespec *response_timeout); + +#define force_multiple_registers_u32(p1,p2,p3,p4,p5,p6,p7,p8) \ + write_output_words_u32 (p1,p2,p3,p4,p5,p6,p7,p8) + + + + + + +/* Initialise the Modbus Library to work as Master only */ +int mb_master_init(int nd_count); +/* Shut down the Modbus Library */ +int mb_master_done(void); + + + +/* Establish a connection to a remote server/slave. + * The address type (naf_tcp, naf_rtu, naf_ascii) specifies the lower + * layer to use for the newly opened node. + */ +int mb_master_connect(node_addr_t node_addr); +/* Shut down a connection to a remote server/slave */ +int mb_master_close(int nd); + + + + +/* Tell the library that communications will be suspended for some time. */ +/* RTU and ASCII versions ignore this function + * TCP version closes all the open tcp connections (connections are automatically + * re-established the next time an IO function to the slave is requested). + * To be more precise, the TCP version makes an estimate of how long + * the silence will be based on previous invocations to this exact same + * function, and will only close the connections if this silence is + * expected to be longer than 1 second! + */ +int mb_master_tcp_silence_init(void); + + + +#endif /* MODBUS_MASTER_H */ + + + + + + + + diff -r 000000000000 -r ae252e0fd9b8 mb_master_private.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 . + * + * This code is made available on the understanding that it will not be + * used in safety-critical situations without a full and competent review. + */ + + + +#ifndef MODBUS_MASTER_PRIVATE_H +#define MODBUS_MASTER_PRIVATE_H + +#include "mb_util.h" +#include "mb_master.h" + + + + +#define DEF_LAYER2_SEND_RETRIES 1 + +#define DEF_IGNORE_ECHO 0 + +#define DEF_OPTIMIZATION optimize_speed + + +int mb_master_init__(int extra_bytes); +int mb_master_done__(void); + + +#endif /* MODBUS_MASTER_PRIVATE_H */ + + + + + + + + diff -r 000000000000 -r ae252e0fd9b8 mb_rtu.c --- /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 . + * + * 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 /* File control definitions */ +#include /* Standard input/output */ +#include +#include +#include /* POSIX terminal control definitions */ +#include /* Time structures for select() */ +#include /* POSIX Symbolic Constants */ +#include +#include /* Error definitions */ +#include /* clock_gettime() */ +#include /* required for INT_MAX */ + +#include /* required for htons() and ntohs() */ + +#include "mb_layer1.h" /* The public interface this file implements... */ +#include "mb_rtu_private.h" + + +#define ERRMSG +#define ERRMSG_HEAD "ModbusRTU: " + +// #define DEBUG /* uncomment to see the data sent and received */ + +#ifdef DEBUG +#ifndef ERRMSG +#define ERRMSG +#endif +#endif + + +#define SAFETY_MARGIN 10 + +/************************************/ +/** **/ +/** Include common code... **/ +/** **/ +/************************************/ + +#include "mb_ds_util.h" /* data structures... */ +#include "mb_time_util.h" /* time conversion routines... */ + + + +/**************************************************************/ +/**************************************************************/ +/**** ****/ +/**** ****/ +/**** Forward Declarations ****/ +/**** and Defaults ****/ +/**** ****/ +/**************************************************************/ +/**************************************************************/ + + /* CRC funtions... */ +typedef u16 (*crc_func_t)(u8 *buf, int cnt); +static u16 crc_slow(u8 *buf, int cnt); +static u16 crc_fast(u8 *buf, int cnt); + + /* slow version does not need to be initialised, so we use it as default. */ +#define DEF_CRC_FUNCTION crc_slow + + +/**************************************************************/ +/**************************************************************/ +/**** ****/ +/**** ****/ +/**** Local Utility functions... ****/ +/**** ****/ +/**** ****/ +/**************************************************************/ +/**************************************************************/ + +/************************************/ +/** **/ +/** Miscelaneous Utility functions **/ +/** **/ +/************************************/ + +/* + * Functions to convert u16 variables + * between network and host byte order + * + * NOTE: Modbus uses MSByte first, just like + * tcp/ip, so we use the htons() and + * ntoh() functions to guarantee + * code portability. + */ +static inline u16 mb_hton(u16 h_value) + {return htons(h_value);} /* return h_value; */ + +static inline u16 mb_ntoh(u16 m_value) + {return ntohs(m_value);} /* return m_value; */ + +/* return Most Significant Byte of value; */ +static inline u8 msb(u16 value) + {return (value >> 8) & 0xFF;} + +/* return Least Significant Byte of value; */ +static inline u8 lsb(u16 value) + {return value & 0xFF;} + +#define u16_v(char_ptr) (*((u16 *)(&(char_ptr)))) + + + +/**************************************/ +/** **/ +/** Initialise a termios struct **/ +/** **/ +/**************************************/ +static int termios_init(struct termios *tios, + int baud, + int parity, + int data_bits, + int stop_bits) { + speed_t baud_rate; + + if (tios == NULL) + return -1; + + /* reset all the values... */ + /* NOTE: the following are initialised later on... + tios->c_iflag = 0; + tios->c_oflag = 0; + tios->c_cflag = 0; + tios->c_lflag = 0; + */ + tios->c_line = 0; + + /* The minimum number of characters that should be received + * to satisfy a call to read(). + */ + tios->c_cc[VMIN ] = 0; + + /* The maximum inter-arrival interval between two characters, + * in deciseconds. + * + * NOTE: we could use this to detect the end of RTU frames, + * but we prefer to use select() that has higher resolution, + * even though this higher resolution is most probably not + * supported, and the effective resolution is 10ms, + * one tenth of a decisecond. + */ + tios->c_cc[VTIME] = 0; + + /* configure the input modes... */ + tios->c_iflag = IGNBRK | /* ignore BREAK condition on input */ + IGNPAR | /* ignore framing errors and parity errors */ + IXANY; /* enable any character to restart output */ + /* BRKINT Only active if IGNBRK is not set. + * generate SIGINT on BREAK condition, + * otherwise read BREAK as character \0. + * PARMRK Only active if IGNPAR is not set. + * replace bytes with parity errors with + * \377 \0, instead of \0. + * INPCK enable input parity checking + * ISTRIP strip off eighth bit + * IGNCR ignore carriage return on input + * INLCR only active if IGNCR is not set. + * translate newline to carriage return on input + * ICRNL only active if IGNCR is not set. + * translate carriage return to newline on input + * IUCLC map uppercase characters to lowercase on input + * IXON enable XON/XOFF flow control on output + * IXOFF enable XON/XOFF flow control on input + * IMAXBEL ring bell when input queue is full + */ + + /* configure the output modes... */ + tios->c_oflag = OPOST; /* enable implementation-defined output processing */ + /* ONOCR don't output CR at column 0 + * OLCUC map lowercase characters to uppercase on output + * ONLCR map NL to CR-NL on output + * OCRNL map CR to NL on output + * OFILL send fill characters for a delay, rather than + * using a timed delay + * OFDEL fill character is ASCII DEL. If unset, fill + * character is ASCII NUL + * ONLRET don't output CR + * NLDLY NL delay mask. Values are NL0 and NL1. + * CRDLY CR delay mask. Values are CR0, CR1, CR2, or CR3. + * TABDLY horizontal tab delay mask. Values are TAB0, TAB1, + * TAB2, TAB3, or XTABS. A value of XTABS expands + * tabs to spaces (with tab stops every eight columns). + * BSDLY backspace delay mask. Values are BS0 or BS1. + * VTDLY vertical tab delay mask. Values are VT0 or VT1. + * FFDLY form feed delay mask. Values are FF0 or FF1. + */ + + /* configure the control modes... */ + tios->c_cflag = CREAD | /* enable receiver. */ + CLOCAL; /* ignore modem control lines */ + /* HUPCL lower modem control lines after last process + * closes the device (hang up). + * CRTSCTS flow control (Request/Clear To Send). + */ + if (data_bits == 5) tios->c_cflag |= CS5; + else if (data_bits == 6) tios->c_cflag |= CS6; + else if (data_bits == 7) tios->c_cflag |= CS7; + else if (data_bits == 8) tios->c_cflag |= CS8; + else return -1; + + if (stop_bits == 1) tios->c_cflag &=~ CSTOPB; + else if (stop_bits == 2) tios->c_cflag |= CSTOPB; + else return -1; + + if(parity == 0) { /* none */ + tios->c_cflag &=~ PARENB; + tios->c_cflag &=~ PARODD; + } else if(parity == 2) { /* even */ + tios->c_cflag |= PARENB; + tios->c_cflag &=~ PARODD; + } else if(parity == 1) { /* odd */ + tios->c_cflag |= PARENB; + tios->c_cflag |= PARODD; + } else return -1; + + + /* configure the local modes... */ + tios->c_lflag = IEXTEN; /* enable implementation-defined input processing */ + /* ISIG when any of the characters INTR, QUIT, SUSP, or DSUSP + * are received, generate the corresponding signal. + * ICANON enable canonical mode. This enables the special + * characters EOF, EOL, EOL2, ERASE, KILL, REPRINT, + * STATUS, and WERASE, and buffers by lines. + * ECHO echo input characters. + */ + + /* Set the baud rate */ + /* Must be done before reseting all the values to 0! */ + switch(baud) { + case 110: baud_rate = B110; break; + case 300: baud_rate = B300; break; + case 600: baud_rate = B600; break; + case 1200: baud_rate = B1200; break; + case 2400: baud_rate = B2400; break; + case 4800: baud_rate = B4800; break; + case 9600: baud_rate = B9600; break; + case 19200: baud_rate = B19200; break; + case 38400: baud_rate = B38400; break; + case 57600: baud_rate = B57600; break; + case 115200: baud_rate = B115200; break; + default: return -1; + } /* switch() */ + + if ((cfsetispeed(tios, baud_rate) < 0) || + (cfsetospeed(tios, baud_rate) < 0)) + return -1;; + + return 0; +} + + +/************************************/ +/** **/ +/** A data structure - recv buffer **/ +/** **/ +/************************************/ + +/* A data structutre used for the receive buffer, i.e. the buffer + * that stores the bytes we receive from the bus. + * + * What we realy needed here is an unbounded buffer. This may be + * implemented by: + * - a circular buffer the size of the maximum frame length + * - a linear buffer somewhat larger than the maximum frame length + * + * Due to the fact that this library's API hands over the frame data + * in a linear buffer, and also reads the data (i,e, calls to read()) + * into a linear buffer: + * - the circular buffer would be more efficient in aborted frame + * situations + * - the linear is more efficient when no aborted frames are recieved. + * + * I have decided to optimize for the most often encountered situation, + * i.e. when no aborted frames are received. + * + * The linear buffer has a size larger than the maximum + * number of bytes we intend to store in it. We simply start ignoring + * the first bytes in the buffer in which we are not interested in, and + * continue with the extra bytes of the buffer. When we reach the limit + * of these extra bytes, we shift the data down so it once again + * uses the first bytes of the buffer. The more number of extra bytes, + * the more efficient it will be. + * + * Note that if we don't receive any aborted frames, it will work as a + * simple linear buffer, and no memory shifts will be required! + */ + +typedef struct { + lb_buf_t data_buf; + /* Flag: + * 1 => We have detected a frame boundary using 3.5 character silence + * 0 => We have not yet detected any frame boundary + */ + int found_frame_boundary; /* ==1 => valid data ends at a frame boundary. */ + /* Flag: + * Used in the call to search_for_frame() as the history parameter! + */ + int frame_search_history; + } recv_buf_t; + +/* A small auxiliary function... */ +static inline u8 *recv_buf_init(recv_buf_t *buf, int size, int max_data_start) { + buf->found_frame_boundary = 0; + buf->frame_search_history = 0; + return lb_init(&buf->data_buf, size, max_data_start); +} + + +/* A small auxiliary function... */ +static inline void recv_buf_done(recv_buf_t *buf) { + buf->found_frame_boundary = 0; + buf->frame_search_history = 0; + lb_done(&buf->data_buf); +} + + +/* A small auxiliary function... */ +static inline void recv_buf_reset(recv_buf_t *buf) { + buf->found_frame_boundary = 0; + buf->frame_search_history = 0; + lb_data_purge_all(&buf->data_buf); +} + + +/************************************/ +/** **/ +/** A data structure - nd entry **/ +/** **/ +/************************************/ + +/* NOTE: nd = node descriptor */ + +typedef struct { + /* The file descriptor associated with this node */ + /* NOTE: if the node is not yet in use, i.e. if the node is free, + * then fd will be set to -1 + */ + int fd; + + /* the time it takes to transmit 1.5 characters at the current baud rate */ + struct timeval time_15_char_; + /* the time it takes to transmit 3.5 characters at the current baud rate */ + struct timeval time_35_char_; + + /* Due to the algorithm used to work around aborted frames, the modbus_read() + * function might read beyond the current modbus frame. The extra bytes + * must be stored for the subsequent call to modbus_read(). + */ + recv_buf_t recv_buf_; + + /* The old settings of the serial port, to be reset when the library is closed... */ + struct termios old_tty_settings_; + + /* ignore echo flag. + * If set to 1, then it means that we will be reading every byte we + * ourselves write out to the bus, so we must ignore those bytes read + * before we really read the data sent by remote nodes. + * + * This comes in useful when using a RS232-RS485 converter that does + * not correctly control the RTS-CTS lines... + */ + int ignore_echo; + } nd_entry_t; + + +static inline void nd_entry_init(nd_entry_t *nde) { + nde->fd = -1; /* The node is free... */ +} + + + +static int nd_entry_connect(nd_entry_t *nde, + node_addr_t *node_addr, + optimization_t opt) { + + int parity_bits, start_bits, char_bits; + struct termios settings; + int buf_size; + + /* + if (nde == NULL) + goto error_exit_0; + */ + if (nde->fd >= 0) + goto error_exit_0; + + /* initialise the termios data structure */ + if (termios_init(&settings, + node_addr->addr.rtu.baud, + node_addr->addr.rtu.parity, + node_addr->addr.rtu.data_bits, + node_addr->addr.rtu.stop_bits) + < 0) { +#ifdef ERRMSG + fprintf(stderr, ERRMSG_HEAD "Invalid serial line settings" + "(baud=%d, parity=%d, data_bits=%d, stop_bits=%d)\n", + node_addr->addr.rtu.baud, + node_addr->addr.rtu.parity, + node_addr->addr.rtu.data_bits, + node_addr->addr.rtu.stop_bits); +#endif + goto error_exit_1; + } + + /* set the ignore_echo flag */ + nde->ignore_echo = node_addr->addr.rtu.ignore_echo; + + /* initialise recv buffer */ + buf_size = (opt == optimize_size)?RECV_BUFFER_SIZE_SMALL: + RECV_BUFFER_SIZE_LARGE; + if (recv_buf_init(&nde->recv_buf_, buf_size, buf_size - MAX_RTU_FRAME_LENGTH) + == NULL) { +#ifdef ERRMSG + fprintf(stderr, ERRMSG_HEAD "Out of memory: error initializing receive buffer\n"); +#endif + goto error_exit_2; + } + + /* open the serial port */ + if((nde->fd = open(node_addr->addr.rtu.device, O_RDWR | O_NOCTTY | O_NDELAY)) + < 0) { +#ifdef ERRMSG + perror("open()"); + fprintf(stderr, ERRMSG_HEAD "Error opening device %s\n", + node_addr->addr.rtu.device); +#endif + goto error_exit_3; + } + + if(tcgetattr(nde->fd, &nde->old_tty_settings_) < 0) { +#ifdef ERRMSG + perror("tcgetattr()"); + fprintf(stderr, ERRMSG_HEAD "Error reading device's %s original settings.\n", + node_addr->addr.rtu.device); +#endif + goto error_exit_4; + } + + if(tcsetattr(nde->fd, TCSANOW, &settings) < 0) { +#ifdef ERRMSG + perror("tcsetattr()"); + fprintf(stderr, ERRMSG_HEAD "Error configuring device %s " + "(baud=%d, parity=%d, data_bits=%d, stop_bits=%d)\n", + node_addr->addr.rtu.device, + node_addr->addr.rtu.baud, + node_addr->addr.rtu.parity, + node_addr->addr.rtu.data_bits, + node_addr->addr.rtu.stop_bits); +#endif + goto error_exit_4; + } + + parity_bits = (node_addr->addr.rtu.parity == 0)?0:1; + start_bits = 1; + char_bits = start_bits + node_addr->addr.rtu.data_bits + + parity_bits + node_addr->addr.rtu.stop_bits; + nde->time_15_char_ = d_to_timeval(SAFETY_MARGIN*1.5*char_bits/node_addr->addr.rtu.baud); + nde->time_35_char_ = d_to_timeval(SAFETY_MARGIN*3.5*char_bits/node_addr->addr.rtu.baud); + +#ifdef DEBUG + fprintf(stderr, "nd_entry_connect(): %s ope{.node=NULL, .node_count=0};n\n", node_addr->addr.rtu.device ); + fprintf(stderr, "nd_entry_connect(): returning fd=%d\n", nde->fd); +#endif + return nde->fd; + + error_exit_4: + close(nde->fd); + error_exit_3: + recv_buf_done(&nde->recv_buf_); + error_exit_2: + error_exit_1: + nde->fd = -1; /* set the node as free... */ + error_exit_0: + return -1; +} + + + +static int nd_entry_free(nd_entry_t *nde) { + if (nde->fd < 0) + /* already free */ + return -1; + + /* reset the tty device old settings... */ +#ifdef ERRMSG + int res = +#endif + tcsetattr(nde->fd, TCSANOW, &nde->old_tty_settings_); +#ifdef ERRMSG + if(res < 0) + fprintf(stderr, ERRMSG_HEAD "Error reconfiguring serial port to it's original settings.\n"); +#endif + + recv_buf_done(&nde->recv_buf_); + close(nde->fd); + nde->fd = -1; + + return 0; +} + + + + +static inline int nd_entry_is_free(nd_entry_t *nde) { + return (nde->fd < 0); +} + + + + +/************************************/ +/** **/ +/** A data structure - nd table **/ +/** **/ +/************************************/ + +typedef struct { + /* the array of node descriptors, and current size... */ + nd_entry_t *node; + int node_count; /* total number of nodes in the node[] array */ +} nd_table_t; + + +#if 1 +/* nd_table_init() + * Version 1 of the nd_table_init() function. + * If called more than once, 2nd and any subsequent calls will + * be interpreted as a request to confirm that it was already correctly + * initialized with the requested number of nodes. + */ +static int nd_table_init(nd_table_t *ndt, int nd_count) { + int count; + + if (ndt->node != NULL) { + /* this function has already been called, and the node table is already initialised */ + return (ndt->node_count == nd_count)?0:-1; + } + + /* initialise the node descriptor metadata array... */ + ndt->node = malloc(sizeof(nd_entry_t) * nd_count); + if (ndt->node == NULL) { +#ifdef ERRMSG + fprintf(stderr, ERRMSG_HEAD "Out of memory: error initializing node address buffer\n"); +#endif + return -1; + } + ndt->node_count = nd_count; + + /* initialise the state of each node in the array... */ + for (count = 0; count < ndt->node_count; count++) { + nd_entry_init(&ndt->node[count]); + } /* for() */ + + return nd_count; /* number of succesfully created nodes! */ +} +#else +/* nd_table_init() + * Version 2 of the nd_table_init() function. + * If called more than once, 2nd and any subsequent calls will + * be interpreted as a request to reserve an extra new_nd_count + * number of nodes. This will be done using realloc(). + */ +static int nd_table_init(nd_table_t *ndt, int new_nd_count) { + int count; + + /* initialise the node descriptor metadata array... */ + ndt->node = realloc(ndt->node, sizeof(nd_entry_t) * (ndt->node_count + new_nd_count)); + if (ndt->node == NULL) { +#ifdef ERRMSG + fprintf(stderr, ERRMSG_HEAD "Out of memory: error initializing node address buffer\n"); +#endif + return -1; + } + + /* initialise the state of each newly added node in the array... */ + for (count = ndt->node_count; count < ndt->node_count + new_nd_count; count++) { + nd_entry_init(&ndt->node[count]); + } /* for() */ + ndt->node_count += new_nd_count; + + return new_nd_count; /* number of succesfully created nodes! */ +} +#endif + + +static inline nd_entry_t *nd_table_get_nd(nd_table_t *ndt, int nd) { + if ((nd < 0) || (nd >= ndt->node_count)) + return NULL; + + return &ndt->node[nd]; +} + + +static inline void nd_table_done(nd_table_t *ndt) { + int i; + + if (ndt->node == NULL) + return; + + /* close all the connections... */ + for (i = 0; i < ndt->node_count; i++) + nd_entry_free(&ndt->node[i]); + + /* Free memory... */ + free(ndt->node); + *ndt = (nd_table_t){.node=NULL, .node_count=0}; +} + + + +static inline int nd_table_get_free_nd(nd_table_t *ndt) { + int count; + + for (count = 0; count < ndt->node_count; count++) { + if (nd_entry_is_free(&ndt->node[count])) + return count; + } + + /* none found... */ + return -1; +} + + +static inline int nd_table_free_nd(nd_table_t *ndt, int nd) { + if ((nd < 0) || (nd >= ndt->node_count)) + return -1; + + return nd_entry_free(&ndt->node[nd]); +} + + + +/**************************************************************/ +/**************************************************************/ +/**** ****/ +/**** ****/ +/**** Global Library State ****/ +/**** ****/ +/**** ****/ +/**************************************************************/ +/**************************************************************/ + + /* The node descriptor table... */ + /* NOTE: This variable must be correctly initialised here!! */ +static nd_table_t nd_table_ = {.node=NULL, .node_count=0}; + + /* The optimization choice... */ +static optimization_t optimization_; + + /* the crc function currently in use... */ + /* This will depend on the optimisation choice... */ +crc_func_t crc_calc = DEF_CRC_FUNCTION; + + + +/**************************************************************/ +/**************************************************************/ +/**** ****/ +/**** ****/ +/**** CRC functions ****/ +/**** ****/ +/**** ****/ +/**************************************************************/ +/**************************************************************/ + +#if RTU_FRAME_CRC_LENGTH < 2 +#error The CRC on modbus RTU frames requires at least 2 bytes in the frame length. +#endif + + +/************************************/ +/** **/ +/** Read the CRC of a frame **/ +/** **/ +/************************************/ + +/* NOTE: cnt is number of bytes in the frame _excluding_ CRC! */ +static inline u16 crc_read(u8 *buf, int cnt) { + /* For some strange reason, the crc is transmited + * LSB first, unlike all other values... + */ + return (buf[cnt + 1] << 8) | buf[cnt]; +} + + +/************************************/ +/** **/ +/** Write the CRC of a frame **/ +/** **/ +/************************************/ + +/* NOTE: cnt is number of bytes in the frame _excluding_ CRC! */ +static inline void crc_write(u8 *buf, int cnt) { + /* For some strange reason, the crc is transmited + * LSB first, unlike all other values... + * + * u16_v(query[string_length]) = mb_hton(temp_crc); -> This is wrong !! + */ + /* NOTE: We have already checked above that RTU_FRAME_CRC_LENGTH is >= 2 */ + u16 crc = crc_calc(buf, cnt); + buf[cnt] = lsb(crc); + buf[cnt+1] = msb(crc); +} + + + +/************************************/ +/** **/ +/** A slow version of the **/ +/** CRC function **/ +/** **/ +/************************************/ + +/* crc optimized for smallest memory footprint */ +static u16 crc_slow(u8 *buf, int cnt) +{ + int bit; + u16 temp,flag; + + temp=0xFFFF; + + while (cnt-- != 0) { + temp=temp ^ *buf++; + for (bit=1; bit<=8; bit++) { + flag = temp & 0x0001; + /* NOTE: + * - since temp is unsigned, we are guaranteed a zero in MSbit; + * - if it were signed, the value placed in the MSbit would be + * compiler dependent! + */ + temp >>= 1; + if (flag) + temp=temp ^ 0xA001; + } + } + return(temp); +} + + + + +/************************************/ +/** **/ +/** A fast version of the **/ +/** CRC function **/ +/** **/ +/************************************/ +static u8 *crc_fast_buf = NULL; + +/* crc optimized for speed */ +static u16 crc_fast(u8 *buf, int cnt) +{ +/* NOTE: The following arrays have been replaced by an equivalent + * array (crc_fast_buf[]) initialised at run-time. + */ +/* + static u8 buf_lsb[] = {0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, + 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, + 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, + 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, + 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, + 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, + 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, + 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, + 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, + 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, + 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, + 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, + 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, + 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, + 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, + 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, + 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, + 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, + 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, + 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, + 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, + 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, + 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, + 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, + 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, + 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, + 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, + 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, + 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, + 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, + 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, + 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40 + }; + + static u8 buf_msb[] = {0x00, 0xc0, 0xc1, 0x01, 0xc3, 0x03, 0x02, 0xc2, + 0xc6, 0x06, 0x07, 0xc7, 0x05, 0xc5, 0xc4, 0x04, + 0xcc, 0x0c, 0x0d, 0xcd, 0x0f, 0xcf, 0xce, 0x0e, + 0x0a, 0xca, 0xcb, 0x0b, 0xc9, 0x09, 0x08, 0xc8, + 0xd8, 0x18, 0x19, 0xd9, 0x1b, 0xdb, 0xda, 0x1a, + 0x1e, 0xde, 0xdf, 0x1f, 0xdd, 0x1d, 0x1c, 0xdc, + 0x14, 0xd4, 0xd5, 0x15, 0xd7, 0x17, 0x16, 0xd6, + 0xd2, 0x12, 0x13, 0xd3, 0x11, 0xd1, 0xd0, 0x10, + 0xf0, 0x30, 0x31, 0xf1, 0x33, 0xf3, 0xf2, 0x32, + 0x36, 0xf6, 0xf7, 0x37, 0xf5, 0x35, 0x34, 0xf4, + 0x3c, 0xfc, 0xfd, 0x3d, 0xff, 0x3f, 0x3e, 0xfe, + 0xfa, 0x3a, 0x3b, 0xfb, 0x39, 0xf9, 0xf8, 0x38, + 0x28, 0xe8, 0xe9, 0x29, 0xeb, 0x2b, 0x2a, 0xea, + 0xee, 0x2e, 0x2f, 0xef, 0x2d, 0xed, 0xec, 0x2c, + 0xe4, 0x24, 0x25, 0xe5, 0x27, 0xe7, 0xe6, 0x26, + 0x22, 0xe2, 0xe3, 0x23, 0xe1, 0x21, 0x20, 0xe0, + 0xa0, 0x60, 0x61, 0xa1, 0x63, 0xa3, 0xa2, 0x62, + 0x66, 0xa6, 0xa7, 0x67, 0xa5, 0x65, 0x64, 0xa4, + 0x6c, 0xac, 0xad, 0x6d, 0xaf, 0x6f, 0x6e, 0xae, + 0xaa, 0x6a, 0x6b, 0xab, 0x69, 0xa9, 0xa8, 0x68, + 0x78, 0xb8, 0xb9, 0x79, 0xbb, 0x7b, 0x7a, 0xba, + 0xbe, 0x7e, 0x7f, 0xbf, 0x7d, 0xbd, 0xbc, 0x7c, + 0xb4, 0x74, 0x75, 0xb5, 0x77, 0xb7, 0xb6, 0x76, + 0x72, 0xb2, 0xb3, 0x73, 0xb1, 0x71, 0x70, 0xb0, + 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, + 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, + 0x9c, 0x5c, 0x5d, 0x9d, 0x5f, 0x9f, 0x9e, 0x5e, + 0x5a, 0x9a, 0x9b, 0x5b, 0x99, 0x59, 0x58, 0x98, + 0x88, 0x48, 0x49, 0x89, 0x4b, 0x8b, 0x8a, 0x4a, + 0x4e, 0x8e, 0x8f, 0x4f, 0x8d, 0x4d, 0x4c, 0x8c, + 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, + 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40 + }; +*/ + u8 crc_msb = 0xFF; + u8 crc_lsb = 0xFF; + int index; + + if (cnt <= 0) { + fprintf(stderr, "\nInternal program error in file %s at line %d\n\n\n", __FILE__, __LINE__); + exit(EXIT_FAILURE); + } + + while (cnt-- != 0) { + index = 2 * (crc_lsb ^ *buf++); + crc_lsb = crc_msb ^ crc_fast_buf[index]/* buf_lsb[index/2] */; + crc_msb = crc_fast_buf[index + 1] /* buf_msb[index/2] */; + } + + return crc_msb*0x0100 + crc_lsb; +} + + +/************************************/ +/** **/ +/** init() and done() functions **/ +/** of fast CRC version **/ +/** **/ +/************************************/ + +static inline int crc_fast_init(void) { + int i; + u8 data[2]; + u16 tmp_crc; + + if ((crc_fast_buf = (u8 *)malloc(256 * 2)) == NULL) + return -1; + + for (i = 0x00; i < 0x100; i++) { + data[0] = 0xFF; + data[1] = i; + data[1] = ~data[1]; + tmp_crc = crc_slow(data, 2); + crc_fast_buf[2*i ] = lsb(tmp_crc); + crc_fast_buf[2*i + 1] = msb(tmp_crc); + } + + return 0; +} + + +static inline void crc_fast_done(void) { + free(crc_fast_buf); +} + + +/************************************/ +/** **/ +/** init() and done() functions **/ +/** of generic CRC **/ +/** **/ +/************************************/ + +static inline int crc_init(optimization_t opt) { + switch (opt) { + case optimize_speed: + if (crc_fast_init() < 0) + return -1; + crc_calc = crc_fast; + return 0; + case optimize_size : + crc_calc = crc_slow; + return 0; + default: + return -1; + } + + /* humour the compiler */ + return -1; +} + + +static inline int crc_done(void) { + if (crc_calc == crc_fast) + crc_fast_done(); + + crc_calc = DEF_CRC_FUNCTION; + return 0; +} + + + +/**************************************************************/ +/**************************************************************/ +/**** ****/ +/**** ****/ +/**** Sending of Modbus RTU Frames ****/ +/**** ****/ +/**** ****/ +/**************************************************************/ +/**************************************************************/ + +/* W A R N I N G + * ============= + * The modbus_rtu_write() function assumes that the caller + * has allocated a few bytes extra for the buffer containing + * the data. These bytes will be used to write the crc. + * + * The caller of this function MUST make sure that the data + * buffer, although only containing data_length bytes, has + * been allocated with a size equal to or larger than + * data_length + RTU_FRAME_CRC_LENGTH bytes + * + * I know, this is a very ugly hack, but we don't have much + * choice (please read other comments further on for more + * explanations) + * + * We will nevertheless try and make this explicit by having the + * library initialisation function (modbus_rtu_init() ) return a + * value specifying how many extra bytes this buffer should have. + * Maybe this way this very ugly hack won't go unnoticed, and we + * won't be having any segmentation faults...! + * + * NOTE: for now the transmit_timeout is silently ignored in RTU version! + */ +int modbus_rtu_write(int nd, + u8 *data, + size_t data_length, + u16 transaction_id, + const struct timespec *transmit_timeout + ) +{ + fd_set rfds; + struct timeval timeout; + int res, send_retries; + nd_entry_t *nd_entry; + +#ifdef DEBUG + fprintf(stderr, "modbus_rtu_write(fd=%d) called...\n", nd); +#endif + /* check if nd is correct... */ + if ((nd_entry = nd_table_get_nd(&nd_table_, nd)) == NULL) + return -1; + + /* check if nd is initialzed... */ + if (nd_entry->fd < 0) + return -1; + + /************************** + * append crc to frame... * + **************************/ +/* WARNING: + * The crc_write() function assumes that we have an extra + * RTU_FRAME_CRC_LENGTH free bytes at the end of the *data + * buffer. + * The caller of this function had better make sure he has + * allocated those extra bytes, or a segmentation fault will + * occur. + * Please read on why we leave this as it is... + * + * REASONS: + * We want to write the data and the crc in a single call to + * the OS. This is the only way we can minimally try to gurantee + * that we will not be introducing a silence of more than 1.5 + * character transmission times between any two characters. + * + * We could do the above using one of two methods: + * (a) use a special writev() call in which the data + * to be sent is stored in two buffers (one for the + * data and the other for the crc). + * (b) place all the data in a single linear buffer and + * use the normal write() function. + * + * We cannot use (a) since the writev(2) function does not seem + * to be POSIX compliant... + * (b) has the drawback that we would need to allocate a new buffer, + * and copy all the data into that buffer. We have enough copying of + * data between buffers as it is, so we won't be doing it here + * yet again! + * + * The only option that seems left over is to have the caller + * of this function allocate a few extra bytes. Let's hope he + * does not forget! +*/ + crc_write(data, data_length); + data_length += RTU_FRAME_CRC_LENGTH; + +#ifdef DEBUG +/* Print the hex value of each character that is about to be + * sent over the bus. + */ + { int i; + for(i = 0; i < data_length; i++) + fprintf(stderr, "[0x%2X]", data[i]); + fprintf(stderr, "\n"); + } +#endif + /* THE MAIN LOOP!!! */ + /* NOTE: The modbus standard specifies that the message must + * be sent continuosly over the wire with maximum + * inter-character delays of 1.5 character intervals. + * + * If the write() call is interrupted by a signal, then + * this delay will most probably be exceeded. We should then + * re-start writing the query from the begining. + * + * BUT, can we really expect the write() call to return + * query_length on every platform when no error occurs? + * The write call would still be correct if it only wrote + * 1 byte at a time! + * + * To protect ourselves getting into an infinte loop in the + * above cases, we specify a maximum number of retries, and + * hope for the best...! The worst will now be we simply do + * not get to send out a whole frame, and will therefore always + * fail on writing a modbus frame! + */ + send_retries = RTU_FRAME_SEND_RETRY + 1; /* must try at least once... */ + while (send_retries > 0) { + + /******************************* + * synchronise with the bus... * + *******************************/ + /* Remember that a RS485 bus is half-duplex, so we have to wait until + * nobody is transmitting over the bus for our turn to transmit. + * This will never happen on a modbus network if the master and + * slave state machines never get out of synch (granted, it probably + * only has two states, but a state machine nonetheless), but we want + * to make sure we can re-synchronise if they ever do get out of synch. + * + * The following lines will guarantee that we will re-synchronise our + * state machine with the current state of the bus. + * + * We first wait until the bus has been silent for at least + * char_interval_timeout (i.e. 3.5 character interval). We then flush + * any input and output that might be on the cache. + */ + /* NOTES: + * - we do not need to reset the rfds with FD_SET(ttyfd, &rfds) + * before every call to select! We only wait on one file descriptor, + * so if select returns succesfully, it must have that same file + * decriptor set in the rdfs! + * If select returns with a timeout, then we do not get to call + * select again! + * - On Linux, timeout (i.e. timeout) is modified by select() to + * reflect the amount of time not slept; most other implementations + * do not do this. In the cases in which timeout is not modified, + * we will simply have to wait for longer periods if select is + * interrupted by a signal. + */ + FD_ZERO(&rfds); + FD_SET(nd_entry->fd, &rfds); + timeout = nd_entry->time_35_char_; + while ((res = select(nd_entry->fd+1, &rfds, NULL, NULL, &timeout)) != 0) { + if (res > 0) { + /* we are receiving data over the serial port! */ + /* Throw the data away! */ + tcflush(nd_entry->fd, TCIFLUSH); /* flush the input stream */ + /* reset the timeout value! */ + timeout = nd_entry->time_35_char_; + /* We do not need to reset the FD SET here! */ + } else { + /* some kind of error ocurred */ + if (errno != EINTR) + /* we were not interrupted by a signal */ + return -1; + /* We will be calling select() again. + * We need to reset the FD SET ! + */ + FD_ZERO(&rfds); + FD_SET(nd_entry->fd, &rfds); + } + } /* while (select()) */ + + /* Flush both input and output streams... */ + /* NOTE: Due to the nature of the modbus protocol, + * when a frame is sent all previous + * frames that may have arrived at the sending node become + * irrelevant. + */ + tcflush(nd_entry->fd, TCIOFLUSH); /* flush the input & output streams */ + recv_buf_reset(&nd_entry->recv_buf_); /* reset the recv buffer */ + + /********************** + * write to output... * + **********************/ + /* Please see the comment just above the main loop!! */ + if ((res = write(nd_entry->fd, data, data_length)) != data_length) { + if ((res < 0) && (errno != EAGAIN ) && (errno != EINTR )) + return -1; + } else { + /* query succesfully sent! */ + /* res == query_length */ + + /* NOTE: We do not flush the input stream after sending the frame! + * If the process gets swapped out between the end of writing + * to the serial port, and the call to flush the input of the + * same serial port, the response to the modbus query may be + * sent over between those two calls. This would result in the + * tcflush(ttyfd, TCIFLUSH) call flushing out the response + * to the query we have just sent! + * Not a good thing at all... ;-) + */ + return data_length - RTU_FRAME_CRC_LENGTH; + } + /* NOTE: The maximum inter-character delay of 1.5 character times + * has most probably been exceeded, so we abort the frame and + * retry again... + */ + send_retries--; + } /* while() MAIN LOOP */ + + /* maximum retries exceeded */ + return -1; +} + + + +/**************************************************************/ +/**************************************************************/ +/**** ****/ +/**** ****/ +/**** Receiving Modbus RTU Frames ****/ +/**** ****/ +/**** ****/ +/**************************************************************/ +/**************************************************************/ + +#if MIN_FRAME_LENGTH < 2 +#error Modbus RTU frames have a minimum length larger than MIN_FRAME_LENGTH. +#endif + +/************************************/ +/** **/ +/** Guess length of frame **/ +/** being read. **/ +/** **/ +/************************************/ + +/* Auxiliary function to the search_for_frame() function. + * + * NOTE: data_byte_count must be >=2 for correct operation, therefore + * the #error condition above. + * + * Function to determine the length of the frame currently being read, + * assuming it is a query/response frame. + * + * The guess is obtained by analysing the bytes that have already been + * read. Sometimes we cannot be sure what is the frame length, because + * not enough bytes of the frame have been read yet (for example, frames + * that have a byte_count value which has not yet been read). In these + * cases we return not the frame length, but an error (-1). + * + * If we find the data does not make any sense (i.e. it cannot be a valid + * modbus frame), we return -1. + */ +static int frame_length(u8 *frame_data, + int frame_data_length, + /* The array containing the lengths of frames. */ + /* - query_frame_length[] + * - response_frame_length[] + */ + i8 *frame_length_array) { + + u8 function_code; + int res; + + /* check consistency of input parameters... */ + /* + if ((frame_data == NULL) || (frame_length_array == NULL) || (frame_data_length < 2)) + return -1; + */ + + function_code = frame_data[L2_FRAME_FUNCTION_OFS]; + + /* hard code the length of response to diagnostic function 8 (0x08), with + * subfunction 21 (0x15), and sub-sub-function (a.k.a. operation) 3 (0x03), + * which contains a byte count... + */ + if ((function_code == 0x08) && (frame_length_array == response_frame_lengths)) { + if (frame_data_length < 4) { + /* not enough info to determine the sub-function... */ + return -1; + } else { + if ((frame_data[2] == 0x00) && (frame_data[3] == 0x15)) { + /* we need a couple more bytes to figure out the sub-sub-function... */ + if (frame_data_length < 6) { + /* not enough info to determine the sub-sub-function... */ + return -1; + } else { + if ((frame_data[4] == 0x00) && (frame_data[5] == 0x03)) { + /* We have found a response frame to diagnostic sub-function ... */ + if (frame_data_length < 8) { + /* not enough info to determine the frame length */ + return -1; + } else { + return /*HEADER*/ 6 + mb_ntoh(u16_v(frame_data[6])) + RTU_FRAME_CRC_LENGTH; + } + } + } + } + } + } + + res = frame_length_array[function_code]; + + switch(res) { + case BYTE_COUNT_3 : + if (frame_data_length >= 3) + return BYTE_COUNT_3_HEADER + frame_data[2] + RTU_FRAME_CRC_LENGTH; + break; + case BYTE_COUNT_34: + if (frame_data_length >= 4) + return BYTE_COUNT_34_HEADER + mb_ntoh(u16_v(frame_data[2])) + RTU_FRAME_CRC_LENGTH; + break; + case BYTE_COUNT_7 : + if (frame_data_length >= 7) + return BYTE_COUNT_7_HEADER + frame_data[6] + RTU_FRAME_CRC_LENGTH; + break; + case BYTE_COUNT_11: + if (frame_data_length >= 11) + return BYTE_COUNT_11_HEADER + frame_data[10] + RTU_FRAME_CRC_LENGTH; + break; + case BYTE_COUNT_U : + return -1; + default: + return res + RTU_FRAME_CRC_LENGTH; + } /* switch() */ + + /* unknown frame length */ + return -1; +} + + + +/************************************/ +/** **/ +/** Search for a frame **/ +/** **/ +/************************************/ + +/* Search for a valid frame in the current data. + * If no valid frame is found, then we return -1. + * + * NOTE: Since frame verification is done by calculating the CRC, which is rather + * CPU intensive, and this function may be called several times with the same, + * data, we keep state regarding the result of previous invocations... + * That is the reason for the *search_history parameter! + */ +static int search_for_frame(u8 *frame_data, + int frame_data_length, + int *search_history) { + int query_length, resp_length; + u8 function_code; + /* *search_history flag will have or'ed of following values... */ +#define SFF_HIST_NO_QUERY_FRAME 0x01 +#define SFF_HIST_NO_RESPONSE_FRAME 0x02 +#define SFF_HIST_NO_FRAME (SFF_HIST_NO_RESPONSE_FRAME + SFF_HIST_NO_QUERY_FRAME) + + if ((*search_history == SFF_HIST_NO_FRAME) || + (frame_data_length < MIN_FRAME_LENGTH) || + (frame_data_length > MAX_RTU_FRAME_LENGTH)) + return -1; + + function_code = frame_data[L2_FRAME_FUNCTION_OFS]; + + /* check for exception frame... */ + if ((function_code && 0x80) == 0x80) { + if (frame_data_length >= EXCEPTION_FRAME_LENGTH + RTU_FRAME_CRC_LENGTH) { + /* let's check CRC for valid frame. */ + if ( crc_calc(frame_data, EXCEPTION_FRAME_LENGTH) + == crc_read(frame_data, EXCEPTION_FRAME_LENGTH)) + return EXCEPTION_FRAME_LENGTH + RTU_FRAME_CRC_LENGTH; + else + /* We have checked the CRC, and it is not a valid frame! */ + *search_history |= SFF_HIST_NO_FRAME; + } + return -1; + } + + /* check for valid function code */ + if ((function_code > MAX_FUNCTION_CODE) || (function_code < 1)) { + /* This is an invalid frame!!! */ + *search_history |= SFF_HIST_NO_FRAME; + return -1; + } + + /* let's guess the frame length */ + query_length = resp_length = -1; + if ((*search_history & SFF_HIST_NO_QUERY_FRAME) == 0) + query_length = frame_length(frame_data, frame_data_length, query_frame_lengths); + if ((*search_history & SFF_HIST_NO_RESPONSE_FRAME) == 0) + resp_length = frame_length(frame_data, frame_data_length, response_frame_lengths); + + /* let's check whether any of the lengths are valid...*/ + /* If any of the guesses coincides with the available data length + * we check that length first... + */ + if ((frame_data_length == query_length) || (frame_data_length == resp_length)) { + if ( crc_calc(frame_data, frame_data_length - RTU_FRAME_CRC_LENGTH) + == crc_read(frame_data, frame_data_length - RTU_FRAME_CRC_LENGTH)) + return frame_data_length; + /* nope, wrong guess...*/ + if (frame_data_length == query_length) + *search_history |= SFF_HIST_NO_QUERY_FRAME; + if (frame_data_length == resp_length) + *search_history |= SFF_HIST_NO_RESPONSE_FRAME; + } + + /* let's shoot for a query frame */ + if ((*search_history & SFF_HIST_NO_QUERY_FRAME) == 0) { + if (query_length >= 0) { + if (frame_data_length >= query_length) { + /* let's check if we have a valid frame */ + if ( crc_calc(frame_data, query_length - RTU_FRAME_CRC_LENGTH) + == crc_read(frame_data, query_length - RTU_FRAME_CRC_LENGTH)) + return query_length; + else + /* We have checked the CRC, and it is not a valid frame! */ + *search_history |= SFF_HIST_NO_QUERY_FRAME; + } + } + } + + /* let's shoot for a response frame */ + if ((*search_history & SFF_HIST_NO_RESPONSE_FRAME) == 0) { + if (resp_length >= 0) { + if (frame_data_length >= resp_length) { + /* let's check if we have a valid frame */ + if ( crc_calc(frame_data, resp_length - RTU_FRAME_CRC_LENGTH) + == crc_read(frame_data, resp_length - RTU_FRAME_CRC_LENGTH)) + return resp_length; + else + *search_history |= SFF_HIST_NO_RESPONSE_FRAME; + } + } + } + + /* Could not find valid frame... */ + return -1; +} + + + +/************************************/ +/** **/ +/** Read a frame **/ +/** **/ +/************************************/ + +/* A small auxiliary function, just to make the code easier to read... */ +static inline void next_frame_offset(recv_buf_t *buf, u8 *slave_id) { + buf->frame_search_history = 0; + lb_data_purge(&(buf->data_buf), 1 /* skip one byte */); + + if (slave_id == NULL) + return; + + /* keep ignoring bytes, until we find one == *slave_id, + * or no more bytes... + */ + while (lb_data_count(&(buf->data_buf)) != 0) { + if (*lb_data(&(buf->data_buf)) == *slave_id) + return; + lb_data_purge(&(buf->data_buf), 1 /* skip one byte */); + } +} + +/* A small auxiliary function, just to make the code easier to read... */ +static inline int return_frame(recv_buf_t *buf, + int frame_length, + u8 **recv_data_ptr) { +#ifdef DEBUG + fprintf(stderr, "\n" ); + fprintf(stderr, "returning valid frame of %d bytes.\n", frame_length); +#endif + /* set the data pointer */ + *recv_data_ptr = lb_data(&(buf->data_buf)); + /* remove the frame bytes off the buffer */ + lb_data_purge(&(buf->data_buf), frame_length); + /* reset the search_history flag */ + buf->frame_search_history = 0; + /* if the buffer becomes empty, then reset boundary flag */ + if (lb_data_count(&(buf->data_buf)) <= 0) + buf->found_frame_boundary = 0; + /* return the frame length, excluding CRC */ + return frame_length - RTU_FRAME_CRC_LENGTH; +} + +/* A function to read a valid frame off the rtu bus. + * + * NOTES: + * - The returned frame is guaranteed to be a valid frame. + * - The returned length does *not* include the CRC. + * - The returned frame is not guaranteed to have the same + * slave id as that stored in (*slave_id). This value is used + * merely in optimizing the search for wanted valid frames + * after reading an aborted frame. Only in this situation do + * we limit our search for frames with a slvae id == (*slave_id). + * Under normal circumstances, the value in (*slave_id) is + * simply ignored... + * If any valid frame is desired, then slave_id should be NULL. + * + */ + +/* NOTE: We cannot relly on the 3.5 character interval between frames to detect + * end of frame. We are reading the bytes from a user process, so in + * essence the bytes we are reading are coming off a cache. + * Any inter-character delays between the arrival of the bytes are + * lost as soon as they were placed in the cache. + * + * Our only recourse is to analyse the frame we are reading in real-time, + * and check if it is a valid frame by checking it's CRC. + * To optimise this, we must be able to figure out the length + * of the frame currently being received by analysing the first bytes + * of that frame. Unfortunately, we have three problems with this: + * 1) The spec does not specify the format of every possible modbus + * frame. For ex.functions 9, 10, 13, 14, 18 and 19(?). + * 2) It is not possible to figure out whether a frame is a query + * or a response by just analysing the frame, and query and response + * frames have different sizes... + * 3) A frame may be aborted in the middle! We have no easy way of telling + * if what we are reading is a partial (aborted) frame, followed by a + * correct frame. + * Possible solutions to: + * 1) We could try to reverse engineer, but at the moment I have no + * PLCs that will generate the required frames. + * The chosen method is to verify the CRC if we are lucky enough to + * detect the 3.5 frame boundary imediately following one of these + * frames of unknown length. + * If we do not detect any frame boundary, then our only option + * is to consider it an aborted frame. + * 2) We aim for the query frame (usually the shortest), and check + * it's CRC. If it matches, we accept, the frame, otherwise we try + * a response frame. + * 3) The only way is to consider a frame boundary after each byte, + * (i.e. ignore one bye at a time) and verify if the following bytes + * constitue a valid frame (by checking the CRC). + * + * When reading an aborted frame followed by two or more valid frames, if + * we are unlucky and do not detetect any frame boundary using the 3.5 + * character interval, then we will most likely be reading in bytes + * beyond the first valid frame. This means we will have to store the extra + * bytes we have already read, so they may be handled the next time the + * read_frame() function is called. + */ + /* + * NOTE: The modbus RTU spec is inconsistent on how to handle + * inter-character delays larger than 1.5 characters. + * - On one paragraph it is stated that any delay larger than + * 1.5 character times aborts the current frame, and a new + * frame is started. + * - On another paragraph it is stated that a frame must begin + * with a silence of 3.5 character times. + * + * We will therefore consider that any delay larger than 1.5 character + * times terminates a valid frame. All the above references to the 3.5 character + * interval should therefore be read as a 1.5 character interval. + */ +/* NOTE: This function is only called from one place in the rest of the code, + * so we might just as well make it inline... + */ +/* RETURNS: number of bytes in received frame + * -1 on read file error + * -2 on timeout + */ +static inline int read_frame(nd_entry_t *nd_entry, + u8 **recv_data_ptr, + struct timespec *end_time, + u8 *slave_id) +{ + /* temporary variables... */ + fd_set rfds; + struct timeval timeout; + int res, read_stat; + int frame_length; + recv_buf_t *recv_buf = &nd_entry->recv_buf_; + + /* Flag: + * 1 => we are reading in an aborted frame, so we must + * start ignoring bytes... + */ + int found_aborted_frame; + + /* assume error... */ + *recv_data_ptr = NULL; + + /*===================================* + * Check for frame in left over data * + *===================================*/ + /* If we have any data left over from previous call to read_frame() + * (i.e. this very same function), then we try to interpret that + * data, and do not wait for any extra bytes... + */ + frame_length = search_for_frame(lb_data(&recv_buf->data_buf), + lb_data_count(&recv_buf->data_buf), + &recv_buf->frame_search_history); + if (frame_length > 0) + /* We found a valid frame! */ + return return_frame(recv_buf, frame_length, recv_data_ptr); + + /* If the left over data finished at a frame boundary, and since it + * doesn't contain any valid frame, we discard those bytes... + */ + if (recv_buf->found_frame_boundary == 1) + recv_buf_reset(recv_buf); + + /*============================* + * wait for data availability * + *============================*/ + /* if we can't find a valid frame in the existing data, or no data + * was left over, then we need to read more bytes! + */ + FD_ZERO(&rfds); + FD_SET(nd_entry->fd, &rfds); + {int sel_res = my_select(nd_entry->fd + 1, &rfds, NULL, end_time); + if (sel_res < 0) + return -1; + if (sel_res == 0) + return -2; + } + + /*==============* + * read a frame * + *==============*/ + /* The main loop that reads one frame */ + /* (multiple calls to read() ) */ + /* and jumps out as soon as it finds a valid frame. */ + + found_aborted_frame = 0; + FD_ZERO(&rfds); + FD_SET(nd_entry->fd, &rfds); + while (1) { + + /*------------------* + * read frame bytes * + *------------------*/ + /* Read in as many bytes as possible... + * But only if we have not found a frame boundary. Once we find + * a frame boundary, we do not want to read in any more bytes + * and mix them up with the current frame's bytes. + */ + if (recv_buf->found_frame_boundary == 0) { + read_stat = read(nd_entry->fd, + lb_free(&recv_buf->data_buf), + lb_free_count(&recv_buf->data_buf)); + if (read_stat < 0) { + if (errno != EINTR) + return -1; + else + read_stat = 0; + } +#ifdef DEBUG + {/* display the hex code of each character received */ + int i; + fprintf(stderr, "-"); + for (i=0; i < read_stat; i++) + fprintf(stderr, "<0x%2X>", *(lb_free(&recv_buf->data_buf) + i)); + } +#endif + lb_data_add(&recv_buf->data_buf, read_stat); + } + + /*-----------------------* + * check for valid frame * + *-----------------------*/ + frame_length = search_for_frame(lb_data(&recv_buf->data_buf), + lb_data_count(&recv_buf->data_buf), + &recv_buf->frame_search_history); + if (frame_length > 0) + /* We found a valid frame! */ + return return_frame(recv_buf, frame_length, recv_data_ptr); + + /* if we reach this point, we are sure we do not have valid frame + * of known length in the current data with the current offset... + */ + + /*---------------------------------* + * Have we found an aborted frame? * + *---------------------------------*/ + if (lb_data_count(&recv_buf->data_buf) >= MAX_RTU_FRAME_LENGTH) + found_aborted_frame = 1; + + /*---------------------------------* + * Must we try a new frame_offset? * + *---------------------------------*/ + if (found_aborted_frame == 1) { + /* Note that the found_aborted_frame flag is only set if: + * 1 - we have previously detected a frame_boundary, + * (i.e. found_frame_boundary is == 1 !!) so we won't be + * reading in more bytes; + * 2 - we have read more bytes than the maximum frame length + * + * Considering we have just failed finding a valid frame, and the above + * points (1) and (2), then there is no way we are still going to + * find a valid frame in the current data. + * We must therefore try a new first byte for the frame... + */ + next_frame_offset(recv_buf, slave_id); + } + + /*-----------------------------* + * check for data availability * + *-----------------------------*/ + if (recv_buf->found_frame_boundary == 0) { + /* We need more bytes!! */ + /* + * if no character at the buffer, then we wait time_15_char_ + * before accepting end of frame + */ + /* NOTES: + * - On Linux, timeout is modified by select() to reflect + * the amount of time not slept; most other implementations do + * not do this. On those platforms we will simply have to wait + * longer than we wished if select() is by any chance interrupted + * by a signal... + */ + timeout = nd_entry->time_15_char_; + while ((res = select(nd_entry->fd+1, &rfds, NULL, NULL, &timeout)) < 0) { + if (errno != EINTR) + return -1; + /* We will be calling select() again. + * We need to reset the FD SET ! + */ + FD_ZERO(&rfds); + FD_SET(nd_entry->fd, &rfds); + } + + if (res == 0) { + int frame_length = lb_data_count(&recv_buf->data_buf); + /* We have detected an end of frame using timing boundaries... */ + recv_buf->found_frame_boundary = 1; /* => stop trying to read any more bytes! */ + + /* Let's check if we happen to have a correct frame... */ + if ((frame_length <= MAX_RTU_FRAME_LENGTH) && + (frame_length - RTU_FRAME_CRC_LENGTH > 0)) { + if ( crc_calc(lb_data(&recv_buf->data_buf), frame_length - RTU_FRAME_CRC_LENGTH) + == crc_read(lb_data(&recv_buf->data_buf), frame_length - RTU_FRAME_CRC_LENGTH)) { + /* We have found a valid frame. Let's get out of here! */ + return return_frame(recv_buf, frame_length, recv_data_ptr); + } + } + + /* We have detected a frame boundary, but the frame we read + * is not valid... + * + * One of the following reasons must be the cause: + * 1 - we are reading a single aborted frame. + * 2 - we are reading more than one frame. The first frame, + * followed by any number of valid and/or aborted frames, + * may be one of: + * a - a valid frame whose length is unknown to us, + * i.e. it is not specified in the public Modbus spec. + * b - an aborted frame. + * + * Due to the complexity of reading 2a as a correct frame, we will + * consider it as an aborted frame. (NOTE: it is possible, but + * we will ignore it until the need arises... hopefully, never!) + * + * To put it succintly, what wee now have is an 'aborted' frame + * followed by one or more aborted and/or valid frames. To get to + * any valid frames, and since we do not know where they begin, + * we will have to consider every byte as the possible begining + * of a valid frame. For this permutation, we ignore the first byte, + * and carry on from there... + */ + found_aborted_frame = 1; + lb_data_purge(&recv_buf->data_buf, 1 /* skip one byte */); + recv_buf->frame_search_history = 0; + } + } + + /*-------------------------------* + * check for data yet to process * + *-------------------------------*/ + if ((lb_data_count(&recv_buf->data_buf) < MIN_FRAME_LENGTH) && + (recv_buf->found_frame_boundary == 1)) { + /* We have no more data to process, and will not read anymore! */ + recv_buf_reset(recv_buf); + /* Return TIMEOUT error */ + return -2; + } + } /* while (1)*/ + + /* humour the compiler... */ + return -1; +} + + + + + +/************************************/ +/** **/ +/** Read a Modbus RTU frame **/ +/** **/ +/************************************/ + +/* The public function that reads a valid modbus frame. + * + * The returned frame is guaranteed to be different to the + * the frame stored in send_data, and to start with the + * same slave address stored in send_data[0]. + * + * If send_data is NULL, send_data_length = 0, or + * ignore_echo == 0, then the first valid frame read off + * the bus is returned. + * + * return value: The length (in bytes) of the valid frame, + * -1 on error + * -2 on timeout + */ + +int modbus_rtu_read(int *nd, + u8 **recv_data_ptr, + u16 *transaction_id, + const u8 *send_data, + int send_length, + const struct timespec *recv_timeout) { + struct timespec end_time, *ts_ptr; + int res, recv_length, iter; + u8 *local_recv_data_ptr; + u8 *slave_id, local_slave_id; + nd_entry_t *nd_entry; + + /* Check input parameters... */ + if (nd == NULL) + return -1; + + if (recv_data_ptr == NULL) + recv_data_ptr = &local_recv_data_ptr; + + if ((send_data == NULL) && (send_length != 0)) + return -1; + + /* check if nd is correct... */ + if ((nd_entry = nd_table_get_nd(&nd_table_, *nd)) == NULL) + return -1; + + /* check if nd is initialzed... */ + if (nd_entry->fd < 0) + return -1; + + slave_id = NULL; + if (send_length > L2_FRAME_SLAVEID_OFS) { + local_slave_id = send_data[L2_FRAME_SLAVEID_OFS]; + slave_id = &local_slave_id; + } + + /* We will potentially read many frames, and we cannot reset the timeout + * for every frame we read. We therefore determine the absolute time_out, + * and use this as a parameter for each call to read_frame() instead of + * using a relative timeout. + * + * NOTE: see also the timeout related comment in the read_frame()= function! + */ + /* get the current time... */ + ts_ptr = NULL; + if (recv_timeout != NULL) { + ts_ptr = &end_time; + *ts_ptr = timespec_add_curtime(*recv_timeout); + } + + /* NOTE: When using a half-duplex RS-485 bus, some (most ?) RS232-485 + * converters will send back to the RS232 port whatever we write, + * so we will read in whatever we write out onto the bus. + * We will therefore have to compare + * the first frame we read with the one we sent. If they are + * identical it is because we are in fact working on a RS-485 + * bus and must therefore read in a second frame which will be + * the true response to our query. + * If the first frame we receive is different to the query we + * just sent, then we are *not* working on a RS-485 bus, and + * that is already the real response to our query. + * + * Flushing the input cache immediately after sending the query + * could solve this issue, but we have no guarantee that this + * process would not get swapped out between the write() and + * flush() calls, and we could therefore be flushing the response + * frame! + */ + + iter = 0; + while ((res = recv_length = read_frame(nd_entry, recv_data_ptr, ts_ptr, slave_id)) >= 0) { + if (iter < INT_MAX) iter++; + + if ((send_length <= 0) || (nd_entry->ignore_echo == 0)) + /* any valid frame will do... */ + return recv_length; + + if ((send_length > L2_FRAME_SLAVEID_OFS + 1) && (iter == 1)) + /* We have a frame in send_data, + * so we must make sure we are not reading in the frame just sent... + * + * We must only do this for the first frame we read. Subsequent + * frames are guaranteed not to be the previously sent frame + * since the modbus_rtu_write() resets the recv buffer. + * Remember too that valid modbus responses may be exactly the same + * as the request frame!! + */ + if (recv_length == send_length) + if (memcmp(*recv_data_ptr, send_data, recv_length) == 0) + /* recv == send !!! */ + /* read in another frame. */ + continue; + + /* The frame read is either: + * - different to the frame in send_data + * - or there is only the slave id in send_data[0] + * - or both of the above... + */ + if (send_length > L2_FRAME_SLAVEID_OFS) + if (recv_length > L2_FRAME_SLAVEID_OFS) + /* check that frame is from/to the correct slave... */ + if ((*recv_data_ptr)[L2_FRAME_SLAVEID_OFS] == send_data[L2_FRAME_SLAVEID_OFS]) + /* yep, it is... */ + return recv_length; + + /* The frame we have received is not acceptable... + * Let's read a new frame. + */ + } /* while(...) */ + + /* error reading response! */ + /* Return the error returned by read_frame! */ + return res; +} + + + + + +/**************************************************************/ +/**************************************************************/ +/**** ****/ +/**** ****/ +/**** Initialising and Shutting Down Library ****/ +/**** ****/ +/**** ****/ +/**************************************************************/ +/**************************************************************/ + +/******************************/ +/** **/ +/** Load Default Values **/ +/** **/ +/******************************/ + +static void set_defaults(int *baud, + int *parity, + int *data_bits, + int *stop_bits) { + /* Set the default values, if required... */ + if (*baud == 0) + *baud = DEF_BAUD_RATE; + if (*data_bits == 0) + *data_bits = DEF_DATA_BITS; + if (*stop_bits == 0) { + if (*parity == 0) + *stop_bits = DEF_STOP_BITS_NOP; /* no parity */ + else + *stop_bits = DEF_STOP_BITS_PAR; /* parity used */ + } +} + + +/******************************/ +/** **/ +/** Initialise Library **/ +/** **/ +/******************************/ + +int modbus_rtu_init(int nd_count, + optimization_t opt, + int *extra_bytes) +{ +#ifdef DEBUG + fprintf(stderr, "modbus_rtu_init(): called...\n"); + fprintf(stderr, "creating %d node descriptors\n", nd_count); + if (opt == optimize_speed) + fprintf(stderr, "optimizing for speed\n"); + if (opt == optimize_size) + fprintf(stderr, "optimizing for size\n"); +#endif + + /* check input parameters...*/ + if (0 == nd_count) { + if (extra_bytes != NULL) + // Not the corect value for this layer. + // What we set it to in case this layer is not used! + *extra_bytes = 0; + return 0; + } + if (nd_count <= 0) + goto error_exit_0; + + if (extra_bytes == NULL) + goto error_exit_0; + + if (crc_init(opt) < 0) { +#ifdef ERRMSG + fprintf(stderr, ERRMSG_HEAD "Out of memory: error initializing crc buffers\n"); +#endif + goto error_exit_0; + } + + /* set the extra_bytes value... */ + /* Please see note before the modbus_rtu_write() function for a + * better understanding of this extremely ugly hack... + * + * The number of extra bytes that must be allocated to the data buffer + * before calling modbus_rtu_write() + */ + *extra_bytes = RTU_FRAME_CRC_LENGTH; + + /* initialise nd table... */ + if (nd_table_init(&nd_table_, nd_count) < 0) + goto error_exit_0; + + /* remember the optimization choice for later reference... */ + optimization_ = opt; + +#ifdef DEBUG + fprintf(stderr, "modbus_rtu_init(): returning succesfuly...\n"); +#endif + return 0; + +error_exit_0: + if (extra_bytes != NULL) + // Not the corect value for this layer. + // What we set it to in case of error! + *extra_bytes = 0; + return -1; +} + + + +/******************************/ +/** **/ +/** Open node descriptor **/ +/** **/ +/******************************/ + +/* Open a node for master or slave operation. + * Returns the node descriptor, or -1 on error. + * + * This function is mapped onto both + * modbus_connect() and modbus_listen() + */ +int modbus_rtu_connect(node_addr_t node_addr) { + int node_descriptor; + nd_entry_t *nd_entry; + +#ifdef DEBUG + fprintf(stderr, "modbus_rtu_connect(): called...\n"); + fprintf(stderr, "opening %s\n", node_addr.addr.rtu.device); + fprintf(stderr, "baud_rate = %d\n", node_addr.addr.rtu.baud); + fprintf(stderr, "parity = %d\n", node_addr.addr.rtu.parity); + fprintf(stderr, "data_bits = %d\n", node_addr.addr.rtu.data_bits); + fprintf(stderr, "stop_bits = %d\n", node_addr.addr.rtu.stop_bits); + fprintf(stderr, "ignore_echo = %d\n", node_addr.addr.rtu.ignore_echo); +#endif + + /* Check for valid address family */ + if (node_addr.naf != naf_rtu) + /* wrong address type... */ + goto error_exit_0; + + /* find a free node descriptor */ + if ((node_descriptor = nd_table_get_free_nd(&nd_table_)) < 0) + /* if no free nodes to initialize, then we are finished... */ + goto error_exit_0; + if ((nd_entry = nd_table_get_nd(&nd_table_, node_descriptor)) == NULL) + /* strange, this should not occur... */ + goto error_exit_0; + + /* set the default values... */ + set_defaults(&(node_addr.addr.rtu.baud), + &(node_addr.addr.rtu.parity), + &(node_addr.addr.rtu.data_bits), + &(node_addr.addr.rtu.stop_bits)); + +#ifdef DEBUG + fprintf(stderr, "modbus_rtu_connect(): calling nd_entry_connect()\n"); +#endif + if (nd_entry_connect(nd_entry, &node_addr, optimization_) < 0) + goto error_exit_0; + +#ifdef DEBUG + fprintf(stderr, "modbus_rtu_connect(): %s open\n", node_addr.addr.rtu.device); + fprintf(stderr, "modbus_rtu_connect(): returning nd=%d\n", node_descriptor); +#endif + return node_descriptor; + + error_exit_0: +#ifdef DEBUG + fprintf(stderr, "modbus_rtu_connect(): error!\n"); +#endif + return -1; +} + + + +int modbus_rtu_listen(node_addr_t node_addr) { + return modbus_rtu_connect(node_addr); +} + + + +/******************************/ +/** **/ +/** Close node descriptor **/ +/** **/ +/******************************/ + +int modbus_rtu_close(int nd) { + return nd_table_free_nd(&nd_table_, nd); +} + + + +/******************************/ +/** **/ +/** Shutdown Library **/ +/** **/ +/******************************/ + +int modbus_rtu_done(void) { + nd_table_done(&nd_table_); + crc_done(); + + return 0; +} + + + + +/******************************/ +/** **/ +/** **/ +/** **/ +/******************************/ +int modbus_rtu_silence_init(void) { + return 0; +} + + + + +/******************************/ +/** **/ +/** **/ +/** **/ +/******************************/ + + +double modbus_rtu_get_min_timeout(int baud, + int parity, + int data_bits, + int stop_bits) { + int parity_bits, start_bits, char_bits; + + set_defaults(&baud, &parity, &data_bits, &stop_bits); + parity_bits = (parity == 0)?0:1; + start_bits = 1; + char_bits = start_bits + data_bits + parity_bits + stop_bits; + return (double)((MAX_RTU_FRAME_LENGTH * char_bits) / baud); +} + + diff -r 000000000000 -r ae252e0fd9b8 mb_rtu_private.h --- /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 . + * + * This code is made available on the understanding that it will not be + * used in safety-critical situations without a full and competent review. + */ + + + +#ifndef MODBUS_RTU_PRIVATE_H +#define MODBUS_RTU_PRIVATE_H + +#include "mb_types.h" /* get the data types */ +#include "mb_util.h" + + +/* serial port default configuration... */ +#define DEF_DATA_BITS 8 +#define DEF_STOP_BITS_PAR 1 /* default stop bits if parity is used */ +#define DEF_STOP_BITS_NOP 2 /* default stop bits if parity is not used */ +#define DEF_BAUD_RATE 9600 + + +/* Send retries of rtu frames... */ +#define RTU_FRAME_SEND_RETRY 1 + /* NOTES: + * - the above are the retries at the layer1 level, + * higher layers may decide to retry for themselves! + */ + + +/* Buffer sizes... */ + /* We use double the maximum frame length for the read buffer, + * due to the algorithm used to work around aborted frames. + */ +#define RECV_BUFFER_SIZE_SMALL (MAX_RTU_FRAME_LENGTH + 10) +#define RECV_BUFFER_SIZE_LARGE (2 * MAX_RTU_FRAME_LENGTH) + + +/* Frame lengths... */ + + /* The number of bytes in each frame format, excluding CRC. + * + * BYTE_COUNT_3 denotes that third byte of frame contains the number of bytes; + * - total number of bytes in frame is + * BYTE_COUNT_3_HEADER + byte_count + * BYTE_COUNT_34 denotes that third+fourth bytes of frame contain the number of bytes; + * - total number of bytes in frame is + * BYTE_COUNT_34_HEADER + byte_count + * BYTE_COUNT_7 denotes that seventh byte of frame contain the number of bytes; + * - total number of bytes in frame is + * BYTE_COUNT_7_HEADER + byte_count + * BYTE_COUNT_11 denotes that eleventh byte of frame contain the number of bytes; + * - total number of bytes in frame is + * BYTE_COUNT_11_HEADER + byte_count + * BYTE_COUNT_U denotes unknown number of bytes; + */ + +#define BYTE_COUNT_3_HEADER 3 +#define BYTE_COUNT_34_HEADER 4 +#define BYTE_COUNT_7_HEADER 7 +#define BYTE_COUNT_11_HEADER 11 + +#define BYTE_COUNT_3 (-3) +#define BYTE_COUNT_34 (-34) +#define BYTE_COUNT_7 (-7) +#define BYTE_COUNT_11 (-11) +#define BYTE_COUNT_U (-128) + + +#define MAX_FUNCTION_CODE 0x18 + +#define MIN_FRAME_LENGTH 3 +#define EXCEPTION_FRAME_LENGTH 3 + +static i8 query_frame_lengths[MAX_FUNCTION_CODE+1] = { + /* 0x00 */ 0, /* unused */ + /* 0x01 */ 6, /* Read Coil Status */ + /* 0x02 */ 6, /* Read Input Status */ + /* 0x03 */ 6, /* Read Holding Registers */ + /* 0x04 */ 6, /* Read Input Registers */ + /* 0x05 */ 6, /* Force Single Coil */ + /* 0x06 */ 6, /* Preset Single Register */ + /* 0x07 */ 2, /* Read Exception Status */ + /* 0x08 */ 4, /* Diagnostics */ + /* 0x09 */ BYTE_COUNT_U, /* Program 484 */ + /* 0x0A */ BYTE_COUNT_U, /* Poll 484 */ + /* 0x0B */ 2, /* Fetch Comm. Event Counter */ + /* 0x0C */ 2, /* Fetch Comm. Event Log */ + /* 0x0D */ BYTE_COUNT_U, /* Program Controller */ + /* 0x0E */ BYTE_COUNT_U, /* Poll Controller */ + /* 0x0F */ BYTE_COUNT_7, /* Force Multiple Coils */ + /* 0x10 */ BYTE_COUNT_7, /* Preset Multiple Registers */ + /* 0x11 */ 2, /* Report Slave ID */ + /* 0x12 */ BYTE_COUNT_U, /* Program 884/M84 */ + /* 0x13 */ BYTE_COUNT_U, /* Reset. Comm. Link */ + /* 0x14 */ BYTE_COUNT_3, /* Read General Reference */ + /* 0x15 */ BYTE_COUNT_3, /* Write General Reference */ + /* 0x16 */ 8, /* Mask Write 4X Register */ + /* 0x17 */ BYTE_COUNT_11, /* Read/Write 4x Register */ + /* 0x18 */ 4 /* Read FIFO Queue */ + }; + +static i8 response_frame_lengths[MAX_FUNCTION_CODE+1] = { + /* 0x00 */ 0, /* unused */ + /* 0x01 */ BYTE_COUNT_3, /* Read Coil Status */ + /* 0x02 */ BYTE_COUNT_3, /* Read Input Status */ + /* 0x03 */ BYTE_COUNT_3, /* Read Holding Registers */ + /* 0x04 */ BYTE_COUNT_3, /* Read Input Registers */ + /* 0x05 */ 6, /* Force Single Coil */ + /* 0x06 */ 6, /* Preset Single Register */ + /* 0x07 */ 3, /* Read Exception Status */ + /* 0x08 */ 6,/*see (1)*/ /* Diagnostics */ + /* 0x09 */ BYTE_COUNT_U, /* Program 484 */ + /* 0x0A */ BYTE_COUNT_U, /* Poll 484 */ + /* 0x0B */ 6, /* Fetch Comm. Event Counter */ + /* 0x0C */ BYTE_COUNT_3, /* Fetch Comm. Event Log */ + /* 0x0D */ BYTE_COUNT_U, /* Program Controller */ + /* 0x0E */ BYTE_COUNT_U, /* Poll Controller */ + /* 0x0F */ 6, /* Force Multiple Coils */ + /* 0x10 */ 6, /* Preset Multiple Registers */ + /* 0x11 */ BYTE_COUNT_3, /* Report Slave ID */ + /* 0x12 */ BYTE_COUNT_U, /* Program 884/M84 */ + /* 0x13 */ BYTE_COUNT_U, /* Reset. Comm. Link */ + /* 0x14 */ BYTE_COUNT_3, /* Read General Reference */ + /* 0x15 */ BYTE_COUNT_3, /* Write General Reference */ + /* 0x16 */ 8, /* Mask Write 4X Register */ + /* 0x17 */ BYTE_COUNT_3, /* Read/Write 4x Register */ + /* 0x18 */ BYTE_COUNT_34 /* Read FIFO Queue */ + }; + +/* NOTE (1): + * The diagnostic function (0x08) has sub-functions. In particular, + * sub-function 21 (0x15) has two sub-sub-functions. In the very + * particular case of *one* of these sub-sub-functions, the reply + * frame does *not* have a size of 4, but is variable in length + * and includes a byte counter. + * To take this into account in the table would require an extra two + * tables. + * The above length has been hardcoded into the frame_length() function + * (in file modbus_rtu.c) + */ + + +#define FALSE 0 +#define TRUE 1 + + +#endif /* MODBUS_RTU_PRIVATE_H */ + + + + + + + + diff -r 000000000000 -r ae252e0fd9b8 mb_slave.c --- /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 . + * + * This code is made available on the understanding that it will not be + * used in safety-critical situations without a full and competent review. + */ + + +/* mb_slave.c */ + +#include /* File control definitions */ +#include /* Standard input/output */ +#include +#include +#include /* POSIX terminal control definitions */ +#include /* Time structures for select() */ +#include /* POSIX Symbolic Constants */ +#include /* Error definitions */ + +#include /* required for htons() and ntohs() */ +#include "mb_layer1.h" +#include "mb_slave.h" +#include "mb_slave_private.h" + +/* #define DEBUG */ /* uncomment to see the data sent and received */ + + +#define modbus_write fptr_[layer1_fin].modbus_write +#define modbus_read fptr_[layer1_fin].modbus_read +#define modbus_init fptr_[layer1_fin].modbus_init +#define modbus_done fptr_[layer1_fin].modbus_done +#define modbus_connect fptr_[layer1_fin].modbus_connect +#define modbus_listen fptr_[layer1_fin].modbus_listen +#define modbus_close fptr_[layer1_fin].modbus_close +#define modbus_silence_init fptr_[layer1_fin].modbus_silence_init +#define modbus_get_min_timeout fptr_[layer1_fin].modbus_get_min_timeout + +/* the lower two bits of ttyfd are used to store the index to layer1 function pointers */ +/* layer1_fin index to fptr_[] is in lowest 2 bits of fd */ +#define get_ttyfd() int layer1_fin = fd & 3; int ttyfd = fd / 4;\ + if (fd < 0) {ttyfd = fd; layer1_fin = 0; /* use modbusTCP */} + + + + +/******************************************/ +/******************************************/ +/** **/ +/** Global Variables... **/ +/** **/ +/******************************************/ +/******************************************/ +/* The layer 1 (RTU, ASCII, TCP) implementations will be adding some + * header and tail bytes (e.g. CRC) to the packet we build here. Since + * layer1 will re-use the same buffer allocated in this slave layer + * (so as not to continuosly copy the same info from buffer to buffer), + * we need to allocate more bytes than those strictly required for this + * slave layer. Therefore, the extra_bytes parameter. + * + * Note that we add one more extra byte to the response buffer. + * This is because some response packets will not be starting off + * at byte 0, but rather at byte 1 of the buffer. This is in order + * to guarantee that the data that is sent on the buffer is aligned + * on even bytes (the 16 bit words!). This will allow the application + * (layer above the one implemented in this file - i.e. the callback + * functions) to reference this memory as an u16 *, without producing + * 'bus error' messages in some embedded devices that do not allow + * acessing u16 on odd numbered addresses. + */ +static int buff_extra_bytes_; +#define RESP_BUFFER_SIZE (MAX_L2_FRAME_LENGTH + buff_extra_bytes_ + 1) + +/******************************************/ +/******************************************/ +/** **/ +/** Local Utility functions... **/ +/** **/ +/******************************************/ +/******************************************/ + + +/* + * Function to determine next transaction id. + * + * We use a library wide transaction id, which means that we + * use a new transaction id no matter what slave to which we will + * be sending the request... + */ +static inline u16 next_transaction_id(void) { + static u16 next_id = 0; + return next_id++; +} + + +/* + * Functions to convert u16 variables + * between network and host byte order + * + * NOTE: Modbus uses MSByte first, just like + * tcp/ip, so we could be tempted to use the htons() and + * ntohs() functions to guarantee code portability. + * + * However, on some embedded systems running Linux + * these functions only work if the 16 bit words are + * stored on even addresses. This is not always the + * case in our code, so we have to define our own + * conversion functions... + */ + +/* if using gcc, use it to determine byte order... */ +#ifndef __BYTE_ORDER +#if defined(__GNUC__) + /* We have GCC, which should define __LITTLE_ENDIAN__ */ +# if defined(__LITTLE_ENDIAN__) +# define __BYTE_ORDER __LITTLE_ENDIAN +# else +# define __BYTE_ORDER __BIG_ENDIAN +# endif +#endif /* __GNUC__ */ +#endif /* __BYTE_ORDER */ + + +/* If we still don't know byte order, try to get it from */ +#ifndef __BYTE_ORDER +#include +#endif + + +#ifndef __BYTE_ORDER +# ifdef BYTE_ORDER +# if BYTE_ORDER == LITTLE_ENDIAN +# define __BYTE_ORDER __LITTLE_ENDIAN +# else +# if BYTE_ORDER == BIG_ENDIAN +# define __BYTE_ORDER __BIG_ENDIAN +# endif +# endif +# endif /* BYTE_ORDER */ +#endif /* __BYTE_ORDER */ + + + + + +#ifdef __BYTE_ORDER +# if __BYTE_ORDER == __LITTLE_ENDIAN + +/**************************************************************/ +/* u16 conversion functions to use on little endian platforms */ +/**************************************************************/ + +static inline u16 mb_hton(u16 w) { + register u16 tmp; + tmp = (w & 0x00FF); + tmp = ((w & 0xFF00) >> 0x08) | (tmp << 0x08); + return(tmp); +} +#define mb_ntoh(a) mb_hton(a) + +static inline void mb_hton_count(u16 *w, int count) { + int i; + for (i = 0; i < count; i++) { + /* swap the bytes around... + * a = a ^ b; + * b = a ^ b; + * a = a ^ b; + */ + ((u8 *)(w+i))[0] ^= ((u8 *)(w+i))[1]; + ((u8 *)(w+i))[1] ^= ((u8 *)(w+i))[0]; + ((u8 *)(w+i))[0] ^= ((u8 *)(w+i))[1]; + } +} +#define mb_ntoh_count(w, count) mb_hton_count(w, count) + + + +# else +# if __BYTE_ORDER == __BIG_ENDIAN +/***********************************************************/ +/* u16 conversion functions to use on big endian platforms */ +/***********************************************************/ + + /* We do not need to swap the bytes around! */ +#define mb_ntoh(val) (val) +#define mb_hton(val) (val) +#define mb_hton_count(w, count) /* empty ! */ +#define mb_ntoh_count(w, count) /* empty ! */ + + +# else + +/********************************************************/ +/* u16 conversion functions to use on generic platforms */ +/********************************************************/ + + /* We don't know the byte order, so we revert to the + * standard htons() and ntohs() ... + */ +static inline u16 mb_hton(u16 h_value) + {return htons(h_value); /* return h_value; */} + +static inline u16 mb_ntoh(u16 m_value) + {return ntohs(m_value); /* return m_value; */} + +static inline void mb_hton_count(u16 *w, int count) + {int i; for (i = 0; i < count; i++) {w[i] = mb_hton(w[i]);}} + +static inline void mb_ntoh_count(u16 *w, int count) + {int i; for (i = 0; i < count; i++) {w[i] = mb_ntoh(w[i]);}} + +# endif +# endif +#endif /* __BYTE_ORDER */ + + + + +/* Safe versions of the conversion functions! + * + * Note that these functions always work, whatever the endiannes + * of the machine that executes it! + * + * It is also safe because the resulting value may be stored + * on an odd address even on machines that do not allow directly + * accessing u16 bit words on odd addresses. + */ +static inline int mb_hton_safe(u16 from, u16 *to_ptr) { + ((u8 *)to_ptr)[1] = (from & 0x00FF); + ((u8 *)to_ptr)[0] = ((from & 0xFF00) >> 0x08); + return 0; +} + +#define mb_ntoh_safe(a, b) mb_hton_safe(a, b) + + +/* return Most Significant Byte of value; */ +static inline u8 msb(u16 value) + {return (value >> 8) & 0xFF;} + +/* return Least Significant Byte of value; */ +static inline u8 lsb(u16 value) + {return value & 0xFF;} + +#define u16_v(char_ptr) (*((u16 *)(&(char_ptr)))) + + + + + + + + + +/***********************************************/ +/***********************************************/ +/** **/ +/** Handle requests from master/client **/ +/** **/ +/***********************************************/ +/***********************************************/ + + +/* Handle functions 0x01 and 0x02 */ +typedef int (*read_bits_callback_t)(void *arg, u16 start_addr, u16 bit_count, u8 *data_bytes); +static int handle_read_bits (u8 *query_packet, + u8 **resp_packet_ptr, + u8 *error_code, + read_bits_callback_t read_bits_callback, + void *callback_arg + ) { + u16 start_addr, count; + int res; + u8 *resp_packet; + + /* If no callback, handle as if function is not supported... */ + if (read_bits_callback == NULL) + {*error_code = ERR_ILLEGAL_FUNCTION; return -1;} + + /* in oprder for the data in this packet to be aligned on even numbered addresses, this + * response packet will start off at an odd numbered byte... + * We therefore add 1 to the address where the packet starts. + */ + (*resp_packet_ptr)++; + resp_packet = *resp_packet_ptr; + + /* NOTE: + * Modbus uses high level addressing starting off from 1, but + * this is sent as 0 on the wire! + * We could expect the user to specify high level addressing + * starting at 1, and do the conversion to start off at 0 here. + * However, to do this we would then need to use an u32 data type + * to correctly hold the address supplied by the user (which could + * correctly be 65536, which does not fit in an u16), which would + * in turn require us to check whether the address supplied by the user + * is correct (i.e. <= 65536). + * I decided to go with the other option of using an u16, and + * requiring the user to use addressing starting off at 0! + */ + /* start_addr = mb_ntoh(u16_v(query_packet[2])) + 1; */ + mb_ntoh_safe(u16_v(query_packet[2]), &start_addr); + mb_ntoh_safe(u16_v(query_packet[4]), &count); + + #ifdef DEBUG + printf("handle_read_input_bits() called. slave=%d, function=%d, start_addr=%d, count=%d\n", + query_packet[0], query_packet[1], start_addr, count); + #endif + + if ((count > MAX_READ_BITS) || (count < 1)) + {*error_code = ERR_ILLEGAL_DATA_VALUE; return -1;} + + /* Remember, we are using addressing starting off at 0, in the start_addr variable! */ + /* This means that he highest acceptable address is 65535, when count=1 .... */ + /* Note the use of 65536 in the comparison will force automatic upgrade of u16 variables! */ + /* => start_addr + count will nver overflow the u16 type! */ + if (start_addr + count > 65536) + {*error_code = ERR_ILLEGAL_DATA_ADDRESS; return -1;} + + /* start building response frame... */ + resp_packet[0] = query_packet[0]; /* slave */ + resp_packet[1] = query_packet[1]; /* function (either 0x01 or 0x02 ! */ + resp_packet[2] = (count + 7) / 8; /* number of data bytes = ceil(count/8) */ + + res = read_bits_callback(callback_arg, start_addr, count, &(resp_packet[3])); + if (res == -2) {*error_code = ERR_ILLEGAL_DATA_ADDRESS; return -1;} + if (res < 0) {*error_code = ERR_SLAVE_DEVICE_FAILURE; return -1;} + + return resp_packet[2] + 3; /* packet size is data length + 3 bytes -> slave, function, count */ +} + + + +/* Handle function 0x01 */ +int handle_read_output_bits (u8 *query_packet, u8 **resp_packet_ptr, u8 *error_code, mb_slave_callback_t *callbacks) + {return handle_read_bits(query_packet, resp_packet_ptr, error_code, callbacks->read_outbits, callbacks->arg);} + +/* Handle function 0x02 */ +int handle_read_input_bits (u8 *query_packet, u8 **resp_packet_ptr, u8 *error_code, mb_slave_callback_t *callbacks) + {return handle_read_bits(query_packet, resp_packet_ptr, error_code, callbacks->read_inbits, callbacks->arg);} + + + + +/* Handle functions 0x03 and 0x04 */ +typedef int (*read_words_callback_t)(void *arg, u16 start_addr, u16 word_count, u16 *data_words); +static int handle_read_words (u8 *query_packet, + u8 **resp_packet_ptr, + u8 *error_code, + read_words_callback_t read_words_callback, + void *callback_arg + ) { + u16 start_addr, count; + int res; + u8 *resp_packet; + + /* If no callback, handle as if function is not supported... */ + if (read_words_callback == NULL) + {*error_code = ERR_ILLEGAL_FUNCTION; return -1;} + + /* See equivalent comment in handle_read_bits() */ + (*resp_packet_ptr)++; + resp_packet = *resp_packet_ptr; + + /* See equivalent comment in handle_read_bits() */ + mb_ntoh_safe(u16_v(query_packet[2]), &start_addr); + mb_ntoh_safe(u16_v(query_packet[4]), &count); + + #ifdef DEBUG + printf("handle_read_output_words() called. slave=%d, function=%d, start_addr=%d, count=%d\n", + query_packet[0], query_packet[1], start_addr, count); + #endif + + if ((count > MAX_READ_REGS) || (count < 1)) + {*error_code = ERR_ILLEGAL_DATA_VALUE; return -1;} + + /* See equivalent comment in handle_read_bits() */ + if (start_addr + count > 65536) + {*error_code = ERR_ILLEGAL_DATA_ADDRESS; return -1;} + + /* start building response frame... */ + resp_packet[0] = query_packet[0]; /* slave */ + resp_packet[1] = query_packet[1]; /* function code, either 0x03 or 0x04 !!!*/ + resp_packet[2] = count * 2; /* number of bytes of data... */ + + res = read_words_callback(callback_arg, start_addr, count, (u16 *)&(resp_packet[3])); + if (res == -2) {*error_code = ERR_ILLEGAL_DATA_ADDRESS; return -1;} + if (res < 0) {*error_code = ERR_SLAVE_DEVICE_FAILURE; return -1;} + + /* convert all data from host to network byte order. */ + mb_hton_count((u16 *)&(resp_packet[3]), count); + + return resp_packet[2] + 3; /* packet size is data length + 3 bytes -> slave, function, count */ +} + + + + +/* Handle function 0x03 */ +int handle_read_output_words (u8 *query_packet, u8 **resp_packet_ptr, u8 *error_code, mb_slave_callback_t *callbacks) + {return handle_read_words(query_packet, resp_packet_ptr, error_code, callbacks->read_outwords, callbacks->arg);} + +/* Handle function 0x04 */ +int handle_read_input_words (u8 *query_packet, u8 **resp_packet_ptr, u8 *error_code, mb_slave_callback_t *callbacks) + {return handle_read_words(query_packet, resp_packet_ptr, error_code, callbacks->read_inwords, callbacks->arg);} + + + +/* Handle function 0x05 */ +int handle_write_output_bit (u8 *query_packet, u8 **resp_packet_ptr, u8 *error_code, mb_slave_callback_t *callbacks) { + u16 start_addr; + int res; + u8 *resp_packet; + + /* If no callback, handle as if function is not supported... */ + if (callbacks->write_outbits == NULL) + {*error_code = ERR_ILLEGAL_FUNCTION; return -1;} + + resp_packet = *resp_packet_ptr; + + /* See equivalent comment in handle_read_bits() */ + mb_ntoh_safe(u16_v(query_packet[2]), &start_addr); + + #ifdef DEBUG + printf("handle_write_output_bit() called. slave=%d, function=%d, start_addr=%d\n", + query_packet[0], query_packet[1], start_addr); + #endif + + // byte 5 Must be 0x00, byte 4 must be 0x00 or 0xFF !! + if ( (query_packet[5] != 0) || + ((query_packet[4] != 0) && (query_packet[4] != 0xFF))) + {*error_code = ERR_ILLEGAL_DATA_VALUE; return -1;} + + /* Address will always be valid, no need to check! */ + // if (start_addr > 65535) {*error_code = ERR_ILLEGAL_DATA_ADDRESS; return -1;} + + /* start building response frame... */ + resp_packet[0] = query_packet[0]; /* slave */ + resp_packet[1] = query_packet[1]; /* function */ + resp_packet[2] = query_packet[2]; /* start address - hi byte */ + resp_packet[3] = query_packet[3]; /* start address - lo byte */ + resp_packet[4] = query_packet[4]; /* value: 0x00 or 0xFF */ + resp_packet[5] = query_packet[5]; /* value: must be 0x00 */ + + res = (callbacks->write_outbits)(callbacks->arg, start_addr, 1, &(query_packet[4])); + if (res == -2) {*error_code = ERR_ILLEGAL_DATA_ADDRESS; return -1;} + if (res < 0) {*error_code = ERR_SLAVE_DEVICE_FAILURE; return -1;} + + return 6; /* response packet size, including slave id in byte 0 */ +} + + + +/* Handle function 0x06 */ +int handle_write_output_word (u8 *query_packet, u8 **resp_packet_ptr, u8 *error_code, mb_slave_callback_t *callbacks) { + u16 start_addr; + int res; + u8 *resp_packet; + + /* If no callback, handle as if function is not supported... */ + if (callbacks->write_outwords == NULL) + {*error_code = ERR_ILLEGAL_FUNCTION; return -1;} + + resp_packet = *resp_packet_ptr; + + /* See equivalent comment in handle_read_bits() */ + mb_ntoh_safe(u16_v(query_packet[2]), &start_addr); + + #ifdef DEBUG + printf("handle_write_output_word() called. slave=%d, function=%d, start_addr=%d\n", + query_packet[0], query_packet[1], start_addr); + #endif + + /* Address will always be valid, no need to check! */ + // if (start_addr > 65535) {*error_code = ERR_ILLEGAL_DATA_ADDRESS; return -1;} + + /* start building response frame... */ + resp_packet[0] = query_packet[0]; /* slave */ + resp_packet[1] = query_packet[1]; /* function */ + resp_packet[2] = query_packet[2]; /* start address - hi byte */ + resp_packet[3] = query_packet[3]; /* start address - lo byte */ + resp_packet[4] = query_packet[4]; /* value - hi byte */ + resp_packet[5] = query_packet[5]; /* value - lo byte */ + + /* convert data from network to host byte order */ + mb_ntoh_count((u16 *)&(query_packet[4]), 1); + + res = (callbacks->write_outwords)(callbacks->arg, start_addr, 1, (u16 *)&(query_packet[4])); + if (res == -2) {*error_code = ERR_ILLEGAL_DATA_ADDRESS; return -1;} + if (res < 0) {*error_code = ERR_SLAVE_DEVICE_FAILURE; return -1;} + + return 6; /* packet size is 6 -> slave, function, addr(2), value(2) */ +} + + + +/* Handle function 0x0F */ +int handle_write_output_bits (u8 *query_packet, u8 **resp_packet_ptr, u8 *error_code, mb_slave_callback_t *callbacks) { + u16 start_addr, count; + int res; + u8 *resp_packet; + + /* If no callback, handle as if function is not supported... */ + if (callbacks->write_outbits == NULL) + {*error_code = ERR_ILLEGAL_FUNCTION; return -1;} + + resp_packet = *resp_packet_ptr; + + /* See equivalent comment in handle_read_bits() */ + mb_ntoh_safe(u16_v(query_packet[2]), &start_addr); + mb_ntoh_safe(u16_v(query_packet[4]), &count); + + #ifdef DEBUG + printf("handle_write_output_bits() called. slave=%d, function=%d, start_addr=%d, count=%d\n", + query_packet[0], query_packet[1], start_addr, count); + #endif + + if ((count > MAX_WRITE_COILS) || (count < 1) || ((count+7)/8 != query_packet[6]) ) + {*error_code = ERR_ILLEGAL_DATA_VALUE; return -1;} + + /* See equivalent comment in handle_read_bits() */ + if (start_addr + count > 65536) + {*error_code = ERR_ILLEGAL_DATA_ADDRESS; return -1;} + + /* start building response frame... */ + resp_packet[0] = query_packet[0]; /* slave */ + resp_packet[1] = query_packet[1]; /* function */ + resp_packet[2] = query_packet[2]; /* start address - hi byte */ + resp_packet[3] = query_packet[3]; /* start address - lo byte */ + resp_packet[4] = query_packet[4]; /* count - hi byte */ + resp_packet[5] = query_packet[5]; /* count - lo byte */ + + res = (callbacks->write_outbits)(callbacks->arg, start_addr, count, &(query_packet[7])); + if (res == -2) {*error_code = ERR_ILLEGAL_DATA_ADDRESS; return -1;} + if (res < 0) {*error_code = ERR_SLAVE_DEVICE_FAILURE; return -1;} + + return 6; /* packet size is 6 -> slave, function, addr(2), count(2) */ +} + + + + +/* Handle function 0x10 */ +int handle_write_output_words(u8 *query_packet, u8 **resp_packet_ptr, u8 *error_code, mb_slave_callback_t *callbacks) { + u16 start_addr, count; + int res; + u8 *resp_packet; + + /* If no callback, handle as if function is not supported... */ + if (callbacks->write_outwords == NULL) + {*error_code = ERR_ILLEGAL_FUNCTION; return -1;} + + resp_packet = *resp_packet_ptr; + + /* See equivalent comment in handle_read_bits() */ + mb_ntoh_safe(u16_v(query_packet[2]), &start_addr); + mb_ntoh_safe(u16_v(query_packet[4]), &count); + + if ((count > MAX_WRITE_REGS) || (count < 1) || (count*2 != query_packet[6]) ) + {*error_code = ERR_ILLEGAL_DATA_VALUE; return -1;} + + /* See equivalent comment in handle_read_bits() */ + if (start_addr + count > 65536) + {*error_code = ERR_ILLEGAL_DATA_ADDRESS; return -1;} + + /* start building response frame... */ + resp_packet[0] = query_packet[0]; /* slave */ + resp_packet[1] = query_packet[1]; /* function */ + resp_packet[2] = query_packet[2]; /* start address - hi byte */ + resp_packet[3] = query_packet[3]; /* start address - lo byte */ + resp_packet[4] = query_packet[4]; /* count - hi byte */ + resp_packet[5] = query_packet[5]; /* count - lo byte */ + + /* convert all data from network to host byte order */ + mb_ntoh_count((u16 *)&(query_packet[7]), count); + + res = (callbacks->write_outwords)(callbacks->arg, start_addr, count, (u16 *)&(query_packet[7])); + if (res == -2) {*error_code = ERR_ILLEGAL_DATA_ADDRESS; return -1;} + if (res < 0) {*error_code = ERR_SLAVE_DEVICE_FAILURE; return -1;} + + return 6; /* packet size is 6 -> slave, function, addr(2), count(2) */ +} + + + + + + + + +/***********************************************/ +/***********************************************/ +/** **/ +/** initialise / shutdown the library **/ +/** **/ +/***********************************************/ +/***********************************************/ + +int mb_slave_init__(int extra_bytes) { + buff_extra_bytes_ = extra_bytes; + return 0; +} + + +int mb_slave_done__(void) + {return 0;} + + +#if 0 +int mb_slave_init(int nd_count) { + int extra_bytes; + + #ifdef DEBUG + fprintf( stderr, "mb_slave_init()\n"); + fprintf( stderr, "creating %d nodes\n", nd_count); + #endif + + /* initialise layer 1 library */ + if (modbus_init(nd_count, DEF_OPTIMIZATION, &extra_bytes) < 0) + goto error_exit_0; + + /* initialise this library */ + if (mb_slave_init__(extra_bytes) < 0) + goto error_exit_1; + + return 0; + +error_exit_1: + modbus_done(); +error_exit_0: + return -1; +} + + +int mb_slave_done(void) { + mb_slave_done__(void) + return modbus_done(); +} +#endif + + + +/***********************************************/ +/***********************************************/ +/** **/ +/** open/close slave connection **/ +/** **/ +/***********************************************/ +/***********************************************/ + +/* Create a new slave/server */ +/* NOTE: We use the lower 2 bits of the returned node id to identify which + * layer1 implementation to use. + * 0 -> TCP + * 1 -> RTU + * 2 -> ASCII + * 4 -> unused + * The node id used by the layer1 is shifted left 2 bits + * before returning the node id to the caller! + */ +int mb_slave_new(node_addr_t node_addr) { + int res = -1; + #ifdef DEBUG + fprintf( stderr, "mb_slave_connect()\n"); + #endif + + /* call layer 1 library */ + switch(node_addr.naf) { + case naf_tcp: + res = modbus_tcp_listen(node_addr); + if (res >= 0) res = res*4 + 0 /* offset into fptr_ with TCP functions */; + return res; + case naf_rtu: + res = modbus_rtu_listen(node_addr); + if (res >= 0) res = res*4 + 1 /* offset into fptr_ with RTU functions */; + return res; + case naf_ascii: + res = modbus_ascii_listen(node_addr); + if (res >= 0) res = res*4 + 2 /* offset into fptr_ with ASCII functions */; + return res; + } + + return -1; +} + + + + +int mb_slave_close(int fd) { + #ifdef DEBUG + fprintf( stderr, "mb_slave_close(): nd = %d\n", fd); + #endif + get_ttyfd(); /* declare the ttyfd variable!! */ + /* call layer 1 library */ + /* will call one of modbus_tcp_close(), modbus_rtu_close(), modbus_ascii_close() */ + return modbus_close(ttyfd); +} + + + + + +/***********************************************/ +/***********************************************/ +/** **/ +/** Run the slave **/ +/** **/ +/***********************************************/ +/***********************************************/ + +/* Execute infinite loop waiting and replying to requests coming from clients/master + * This function enters an infinite loop wating for new connection requests, + * and for modbus requests over previoulsy open connections... + * + * The frames are read from: + * - the node descriptor nd, if nd >= 0 + * When using TCP, if the referenced node nd was created to listen for new connections + * [mb_slave_listen()], then this function will also reply to Modbus data requests arriving + * on other nodes that were created as a consequence of accepting connections requests to + * the referenced node nd. + * All other nodes are ignored! + * + * - any valid and initialised TCP node descriptor, if nd = -1 + * In this case, will also accept connection requests arriving from a previously + * created node to listen for new connection requests [mb_slave_listen() ]. + * NOTE: (only avaliable if using TCP) + * + * slaveid identifies the address (RTU and ASCII) or slaveid (TCP) that we implement. + * Any requests that we receive sent with a slaveid different + * than the one specified, and also different to 0, will be silently ignored! + * Whatever the slaveid specified, we always reply to requests + * to slaveid 0 (the modbus broadcast address). + * Calling this function with a slaveid of 0 means to ignore this + * parameter and to reply to all requests (whatever the slaveid + * used in the request). This should mostly be used by TCP servers... + */ + +int mb_slave_run(int fd, mb_slave_callback_t callback_functions, u8 slaveid) { + int byte_count; + u16 transaction_id; + int nd; + u8 function, error_code = 0; + int resp_length; + u8 *query_packet = NULL; + u8 *resp_packet; + u8 resp_buffer_[RESP_BUFFER_SIZE]; + u8 slave; + + get_ttyfd(); /* declare the ttyfd variable!! */ + + #ifdef DEBUG + fprintf(stderr,"[%lu] mb_slave_run(): Called... fd=%d, ttyfd=%d\n", pthread_self(), fd, ttyfd); + #endif + + while(1) { + nd = ttyfd; + /* will call one of modbus_tcp_read(), modbus_rtu_read(), modbus_ascii_read() */ + do { + byte_count = modbus_read(&nd, /* node descriptor */ + &query_packet, /* u8 **recv_data_ptr, */ + &transaction_id, /* u16 *transaction_id, */ + NULL, /* const u8 *send_data, */ + 0, /* int send_length, */ + NULL /* wait indefenitely */ /* const struct timespec *recv_timeout); */ + ); + } while (byte_count <= 2); + + #ifdef DEBUG + {/* display the hex code of each character received */ + int i; + printf("[%lu] mb_slave_run() received %d bytes (ptr=%p): \n", pthread_self(), byte_count, query_packet); + for (i=0; i < byte_count; i++) + printf("<0x%2X>", query_packet[i]); + printf("\n"); + } + #endif + + slave = query_packet[0]; + function = query_packet[1]; + + /* We only reply if: + * - request was sent to broadcast address (slave == 0) + * OR - we were asked to reply to every request (slaveid == 0) + * OR - request matches the slaveid we were asked to accept (slave == slaveid) + * + * Otherwise, silently ignore the received request!!! + */ + if ((slaveid == 0) || (slave == 0) || (slave == slaveid)) { + resp_packet = resp_buffer_; + + switch(function) { + case 0x01: resp_length = handle_read_output_bits (query_packet, &resp_packet, &error_code, &callback_functions); break; + case 0x02: resp_length = handle_read_input_bits (query_packet, &resp_packet, &error_code, &callback_functions); break; + case 0x03: resp_length = handle_read_output_words (query_packet, &resp_packet, &error_code, &callback_functions); break; + case 0x04: resp_length = handle_read_input_words (query_packet, &resp_packet, &error_code, &callback_functions); break; + case 0x05: resp_length = handle_write_output_bit (query_packet, &resp_packet, &error_code, &callback_functions); break; + case 0x06: resp_length = handle_write_output_word (query_packet, &resp_packet, &error_code, &callback_functions); break; + case 0x0F: resp_length = handle_write_output_bits (query_packet, &resp_packet, &error_code, &callback_functions); break; + case 0x10: resp_length = handle_write_output_words(query_packet, &resp_packet, &error_code, &callback_functions); break; + /* return exception code 0x01 -> function not supported! */ + default : resp_length = -1; error_code = 0x01; break; + }; /* switch(function) */ + + if (resp_length < 0) { + /* return error... */ + /* build exception response frame... */ + resp_packet = resp_buffer_; + resp_packet[0] = query_packet[0]; /* slave */ + resp_packet[1] = query_packet[1] | 0x80; /* function code with error bit activated! */ + resp_packet[2] = error_code; + resp_length = 3; + } + modbus_write(nd, resp_packet, resp_length, transaction_id, NULL /*transmit_timeout*/); + }; /* if not ignore request */ + }; /* while(1) */ + + /* humour the compiler... */ + return 0; +} + + + + + + + + + + + + + diff -r 000000000000 -r ae252e0fd9b8 mb_slave.h --- /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 . + * + * This code is made available on the understanding that it will not be + * used in safety-critical situations without a full and competent review. + */ + + +/* mb_slave.h */ + + +#ifndef MODBUS_SLAVE_H +#define MODBUS_SLAVE_H + +#include /* struct timespec data structure */ + +#include "mb_types.h" /* get the data types */ +#include "mb_addr.h" /* get definition of common variable types and error codes */ + + + + +/* Initialise the Modbus Library to work as Slave/Server only */ +int mb_slave_init(int nd_count); +/* Shut down the Modbus Library */ +int mb_slave_done(void); + + + + +/* Create a new slave/server... + * + * This function creates a new node used to: + * - accept connection requests (TCP version) + * - receive frames from masters (RTU and ASCII versions) + * + * The type of address (naf_tcp, naf_rtu, naf_ascii) will specify the lower + * layer of modbus that will be used by the newly opened node. + */ +int mb_slave_new(node_addr_t node_addr); +/* close a node crreated by mb_slave_new() */ +int mb_slave_close(int nd); + + + + +/***********************************************/ +/***********************************************/ +/** **/ +/** Run the slave **/ +/** **/ +/***********************************************/ +/***********************************************/ + + +/* The following functions must return: + * -2 on attempt to read invalid address + * -1 on all other errors... + * 0 on success. + * + * Start_addr may start from 0, to 65535! + * In other words, we use 0 based addressing! + */ +typedef struct { + int (*read_inbits) (void *arg, u16 start_addr, u16 bit_count, u8 *data_bytes); /* bits are packed into bytes... */ + int (*read_outbits) (void *arg, u16 start_addr, u16 bit_count, u8 *data_bytes); /* bits are packed into bytes... */ + int (*write_outbits) (void *arg, u16 start_addr, u16 bit_count, u8 *data_bytes); /* bits are packed into bytes... */ + int (*read_inwords) (void *arg, u16 start_addr, u16 word_count, u16 *data_words); + int (*read_outwords) (void *arg, u16 start_addr, u16 word_count, u16 *data_words); + int (*write_outwords)(void *arg, u16 start_addr, u16 word_count, u16 *data_words); + void *arg; + } mb_slave_callback_t; + +/* Execute the Slave and process requests coming from masters... + * This function enters an infinite loop wating for new connection requests, + * and for modbus requests over previoulsy open connections... + * + * The frames are read from: + * - the node descriptor nd, if nd >= 0 + * When using TCP, if the referenced node nd was created to listen for new connections + * [mb_slave_listen()], then this function will also reply to Modbus data requests arriving + * on other nodes that were created as a consequence of accepting connections requests to + * the referenced node nd. + * All other nodes are ignored! + * + * - any valid and initialised TCP node descriptor, if nd = -1 + * In this case, will also accept connection requests arriving from a previously + * created node to listen for new connection requests [mb_slave_listen() ]. + * NOTE: (only avaliable if using TCP) + * + * slaveid identifies the address (RTU and ASCII) or slaveid (TCP) that we implement. + * Any requests that we receive sent with a slaveid different + * than the one specified, and also different to 0, will be silently ignored! + * Whatever the slaveid specified, we always reply to requests + * to slaveid 0 (the modbus broadcast address). + * Calling this function with a slaveid of 0 means to ignore this + * parameter and to reply to all requests (whatever the slaveid + * used in the request). This should mostly be used by TCP servers... + */ +int mb_slave_run(int nd, mb_slave_callback_t callback_functions, u8 slaveid); + + + + +#endif /* MODBUS_SLAVE_H */ + + + + + + + + diff -r 000000000000 -r ae252e0fd9b8 mb_slave_and_master.c --- /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 . + * + * 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 /* File control definitions */ +#include /* Standard input/output */ +#include +#include +#include /* POSIX terminal control definitions */ +#include /* Time structures for select() */ +#include /* POSIX Symbolic Constants */ +#include /* Error definitions */ + +#include "mb_layer1.h" +#include "mb_slave_private.h" +#include "mb_master_private.h" +#include "mb_slave.h" +#include "mb_master.h" + +//#define DEBUG /* uncomment to see the data sent and received */ + + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + + + + +layer1_funct_ptr_t fptr_[4] = { + { /* WARNING: TCP functions MUST be the first, as we have this hardcoded in the code! */ + /* more specifically, in the get_ttyfd() macro in mb_slave.c */ + /* in the mb_slave_new() function in mb_slave.c */ + /* in the mb_master_connect() function in mb_master.c */ + &modbus_tcp_write + ,&modbus_tcp_read + ,&modbus_tcp_init + ,&modbus_tcp_done + ,&modbus_tcp_connect + ,&modbus_tcp_listen + ,&modbus_tcp_close + ,&modbus_tcp_silence_init + ,&modbus_tcp_get_min_timeout + },{ + &modbus_rtu_write + ,&modbus_rtu_read + ,&modbus_rtu_init + ,&modbus_rtu_done + ,&modbus_rtu_connect + ,&modbus_rtu_listen + ,&modbus_rtu_close + ,&modbus_rtu_silence_init + ,&modbus_rtu_get_min_timeout + },{ + &modbus_ascii_write + ,&modbus_ascii_read + ,&modbus_ascii_init + ,&modbus_ascii_done + ,&modbus_ascii_connect + ,&modbus_ascii_listen + ,&modbus_ascii_close + ,&modbus_ascii_silence_init + ,&modbus_ascii_get_min_timeout + },{ + NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL + } +}; + + + + + + +/************************************************************************ + + initialise / shutdown the library + + These functions sets up/shut down the library state + (allocate memory for buffers, initialise data strcutures, etc) + +**************************************************************************/ +#define max(a,b) (((a)>(b))?(a):(b)) + +int mb_slave_and_master_init(int nd_count_tcp, int nd_count_rtu, int nd_count_ascii) { + int extra_bytes, extra_bytes_tcp, extra_bytes_rtu, extra_bytes_ascii; + +#ifdef DEBUG + fprintf( stderr, "mb_slave_and_master_init()\n"); + fprintf( stderr, "creating %d nodes\n", nd_count); +#endif + + /* initialise layer 1 library */ + if (modbus_tcp_init (nd_count_tcp, DEF_OPTIMIZATION, &extra_bytes_tcp ) < 0) + goto error_exit_0; + if (modbus_rtu_init (nd_count_rtu, DEF_OPTIMIZATION, &extra_bytes_rtu ) < 0) + goto error_exit_1; + if (modbus_ascii_init(nd_count_ascii, DEF_OPTIMIZATION, &extra_bytes_ascii) < 0) + goto error_exit_2; + extra_bytes= max(extra_bytes_tcp, extra_bytes_rtu); + extra_bytes= max(extra_bytes , extra_bytes_ascii); + + /* initialise master and slave libraries... */ + if (mb_slave_init__(extra_bytes) < 0) + goto error_exit_3; + if (mb_master_init__(extra_bytes) < 0) + goto error_exit_4; + return 0; + +/* +error_exit_3: + modbus_master_done(); +*/ +error_exit_4: + mb_slave_done__(); +error_exit_3: + modbus_ascii_done(); +error_exit_2: + modbus_rtu_done(); +error_exit_1: + modbus_tcp_done(); +error_exit_0: + return -1; +} + + + + +int mb_slave_and_master_done(void) { + int res = 0; + res |= mb_slave_done__ (); + res |= mb_master_done__ (); + res |= modbus_ascii_done(); + res |= modbus_rtu_done (); + res |= modbus_tcp_done (); + return res; +} + diff -r 000000000000 -r ae252e0fd9b8 mb_slave_and_master.h --- /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 . + * + * This code is made available on the understanding that it will not be + * used in safety-critical situations without a full and competent review. + */ + + + + +#include "mb_slave.h" +#include "mb_master.h" + + +/************************************************************************ + + initialise / shutdown the library + + These functions sets up/shut down the library state + (allocate memory for buffers, initialise data strcutures, etc) + +**************************************************************************/ + + +int mb_slave_and_master_init(int nd_count_tcp, int nd_count_rtu, int nd_count_ascii); + +int mb_slave_and_master_done(void); + diff -r 000000000000 -r ae252e0fd9b8 mb_slave_private.h --- /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 . + * + * This code is made available on the understanding that it will not be + * used in safety-critical situations without a full and competent review. + */ + + + +#ifndef MODBUS_SLAVE_PRIVATE_H +#define MODBUS_SLAVE_PRIVATE_H + +#include "mb_slave.h" +#include "mb_util.h" + + +#define DEF_LAYER2_SEND_RETRIES 1 + +#define DEF_IGNORE_ECHO 0 + +#define DEF_OPTIMIZATION optimize_speed + + + + +int mb_slave_init__(int extra_bytes); +int mb_slave_done__(void); + + +#endif /* MODBUS_SLAVE_PRIVATE_H */ + + + + + + + + diff -r 000000000000 -r ae252e0fd9b8 mb_tcp.c --- /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 . + * + * 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 /* File control definitions */ +#include /* Standard input/output */ +#include +#include +#include /* POSIX terminal control definitions */ +#include /* Time structures for select() */ +#include /* POSIX Symbolic Constants */ +#include +#include /* Error definitions */ +#include /* clock_gettime() */ +#include +#include +#include /* required for htons() and ntohs() */ +#include /* TCP level socket options */ +#include /* IP level socket options */ + +#include +#include /* sched_yield() */ + + + +#include "sin_util.h" /* internet socket utility functions... */ +#include "mb_layer1.h" /* The public interface this file implements... */ +#include "mb_tcp_private.h" + + + +/************************************/ +/** **/ +/** Include common code... **/ +/** **/ +/************************************/ + +#include "mb_time_util.h" + + +//#define ERRMSG +#define ERRMSG_HEAD "Modbus/TCP: " + + +// #define DEBUG /* uncomment to see the data sent and received */ + + +#ifdef DEBUG +#ifndef ERRMSG +#define ERRMSG +#endif +#endif + + + +/**************************************************************/ +/**************************************************************/ +/**** ****/ +/**** ****/ +/**** Forward Declarations ****/ +/**** and Defaults ****/ +/**** ****/ +/**************************************************************/ +/**************************************************************/ + + + /* A Node Descriptor metadata, + * Due to the fact that modbus TCP is connection oriented, + * and that if the client detects an error the connection + * must be shut down and re-established automatically, + * the modbus TCP layer needs to keep the address of the remote server. + * + * We do this by implementing a node descriptor table, in which each + * entry will have the remote address, and the file descriptor + * of the socket currently in use. + * + * We do not pass the file descriptor up to the next higher layer. We + * send them the node descriptor instead... + */ +#define MB_MASTER_NODE 12 +#define MB_LISTEN_NODE 14 +#define MB_SLAVE_NODE 16 +#define MB_FREE_NODE 18 +typedef sa_family_t nd_type_t; + +typedef struct { + int fd; /* socket descriptor == file descriptor */ + /* NOTE: + * Modbus TCP says that on error, we should close + * a connection and retry with a new connection. + * Since it takes time for a socket to close + * a connection if the remote server is down, + * we close the connection on the socket, close the + * socket itself, and create a new one for the new + * connection. There will be times when the node will + * not have any valid socket, and it will have to + * be created on the fly. + * When the node does not have a valid socket, + * fd will be set to -1 + */ + int node_type; /* What kind of use we are giving to this node... + * If node_type == MB_MASTER_NODE + * The node descriptor was initialised by the + * modbus_connect() function. + * The node descriptor is being used by a master + * device, and the addr contains the address of the slave. + * Remember that in this case fd may be >= 0 while + * we have a valid connection, or it may be < 0 when + * the connection needs to be reset. + * If node_type == MB_LISTEN_NODE + * The node descriptor was initialised by the + * modbus_listen() function. + * The node is merely used to accept() new connection + * requests. The new slave connections will use another + * node to transfer data. + * In this case fd must be >= 0. + * fd < 0 is an ilegal state and should never occur. + * If node_type == MB_SLAVE_NODE + * The node descriptor was initialised when a new + * connection request arrived on a MB_LISTEN type node. + * The node descriptor is being used by a slave device, + * and is currently being used to connect to a master. + * In this case fd must be >= 0. + * fd < 0 is an ilegal state and should never occur. + * If node_type == FREE_ND + * The node descriptor is currently not being used. + * In this case fd is set to -1, but is really irrelevant. + */ + struct sockaddr_in addr; /* The internet adress we are using. + * If node_type == MB_MASTER_NODE + * addr will be the address of the remote slave + * If node_type == MB_LISTEN_NODE + * addr will be the address of the local listening port and network interface + * If node_type == MB_SLAVE_NODE + * addr will be the address of the local port and network interface + * of the connection to the specific client. + */ + int listen_node; /* When a slave accepts a connection through a MB_LISTEN_NODE, it will + * will use an empty node for the new connection, and configure this new node + * to use the type MB_SLAVE_NODE. + * The listen_node entry is only used by nodes of type MB_SLAVE_NODE. + * In this case, listen_node will be the node of type MB_LISTEN_NODE through + * which the connection request came through... + */ + int close_on_silence; /* A flag used only by Master Nodes. + * When (close_on_silence > 0), then the connection to the + * slave device will be shut down whenever the + * modbus_tcp_silence_init() function is called. + * Remember that the connection will be automatically + * re-established the next time the user wishes to communicate + * with the same slave (using this same node descripto). + * If the user wishes to comply with the sugestion + * in the OpenModbus Spec, (s)he should set this flag + * if a silence interval longer than 1 second is expected. + */ + int print_connect_error; /* flag to guarantee we only print an error the first time we + * attempt to connect to a emote server. + * Stops us from generting a cascade of errors while the slave + * is down. + * Flag will get reset every time we successfully + * establish a connection, so a message is once again generated + * on the next error. + */ + u8 *recv_buf; /* This node's receive buffer + * The library supports multiple simultaneous connections, + * and may need to receive multiple frames through mutiple nodes concurrently. + * To make the library thread-safe, we use one buffer for each node. + */ +} nd_entry_t; + + +/* please make sure to lock the node table mutex before calling this function */ +static int nd_entry_init(nd_entry_t *nde) { + nde->addr.sin_family = AF_INET ; + nde->node_type = MB_FREE_NODE; + nde->fd = -1; /* not currently connected... */ + /* initialise recv buffer */ + nde->recv_buf = malloc(sizeof(u8) * RECV_BUFFER_SIZE); + if (nde->recv_buf == NULL) + return -1; + return 0; +} + +/* please make sure to lock the node table mutex before calling this function */ +static int nd_entry_done(nd_entry_t *nde) { + free(nde->recv_buf); + return 0; +} + + + +typedef struct { + /* the array of node descriptors, and current size... */ + nd_entry_t *node; /* array of node entries. if NULL => node table not initialized */ + int node_count; /* total number of nodes in the node[] array */ + int free_node_count; /* number of free nodes in the node[] array */ + pthread_mutex_t mutex; +} nd_table_t; + + + +static int nd_table_done(nd_table_t *ndt) { + int count; + + if (ndt->node == NULL) + return 0; + + /* lock the mutex */ + while (pthread_mutex_lock(&ndt->mutex) != 0) sched_yield(); + + /* initialise the state of each node in the array... */ + for (count = 0; count < ndt->node_count; count++) { + nd_entry_done(&ndt->node[count]); + } /* for() */ + + free(ndt->node); + pthread_mutex_unlock (&ndt->mutex); + pthread_mutex_destroy(&ndt->mutex); + *ndt = (nd_table_t){.node=NULL, .node_count=0, .free_node_count=0}; + + return 0; +} + + + + +#if 1 +/* nd_table_init() + * Version 1 of the nd_table_init() function. + * If called more than once, 2nd and any subsequent calls will + * be interpreted as a request to confirm that it was already correctly + * initialized with the requested number of nodes. + */ +static int nd_table_init(nd_table_t *ndt, int nd_count) { + int count; + + if (ndt->node != NULL) { + /* this function has already been called, and the node table is already initialised */ + return (ndt->node_count == nd_count)?0:-1; + } + + /* initialise the node table mutex... */ + pthread_mutex_init(&ndt->mutex, NULL); + if (pthread_mutex_lock(&ndt->mutex) != 0) { +#ifdef DEBUG + perror("pthread_mutex_lock()"); + fprintf(stderr, "[%lu] Unable to lock newly crated mutex while creating new node table!\n", pthread_self()); +#endif + pthread_mutex_destroy(&ndt->mutex); + return -1; + } + + /* initialise the node descriptor metadata array... */ + ndt->node = malloc(sizeof(nd_entry_t) * nd_count); + if (ndt->node == NULL) { +#ifdef DEBUG + perror("malloc()"); + fprintf(stderr, "[%lu] Out of memory: error initializing node address buffer\n", pthread_self()); +#endif +#ifdef ERRMSG + perror("malloc()"); + fprintf(stderr, ERRMSG_HEAD "Out of memory. Error initializing node address buffer\n"); +#endif + pthread_mutex_unlock (&ndt->mutex); + pthread_mutex_destroy(&ndt->mutex); + return -1; + } + + /* initialise the state of each node in the array... */ + for (count = 0; count < nd_count; count++) { + if (nd_entry_init(&ndt->node[count]) < 0) { + pthread_mutex_unlock(&ndt->mutex); + nd_table_done(ndt); + return -1; + } + ndt->node_count = count+1; + ndt->free_node_count = count+1; + } /* for() */ + + ndt->node_count = nd_count; + ndt->free_node_count = nd_count; + + pthread_mutex_unlock(&ndt->mutex); + return nd_count; /* number of succesfully created nodes! */ +} + + +#else +/* nd_table_init() + * Version 2 of the nd_table_init() function. + * If called more than once, 2nd and any subsequent calls will + * be interpreted as a request to reserve an extra new_nd_count + * number of nodes. This will be done using realloc(). + */ +static int nd_table_init(nd_table_t *ndt, int new_nd_count) { + int count; + + if (ndt->node == NULL) { + /* Node table nt yet initialized => we must initialise the node table mutex... */ + pthread_mutex_init(&ndt->mutex, NULL); + } + + /* lock the mutex */ + while (pthread_mutex_lock(&ndt->mutex) != 0) sched_yield(); + + /* initialise the node descriptor metadata array... */ + ndt->node = realloc(ndt->node, sizeof(nd_entry_t) * (ndt->node_count + new_nd_count)); + if (ndt->node == NULL) { +#ifdef DEBUG + perror("malloc()"); + fprintf(stderr, "[%lu] Out of memory: error initializing node address buffer\n", pthread_self()); +#endif +#ifdef ERRMSG + perror("malloc()"); + fprintf(stderr, ERRMSG_HEAD "Out of memory. Error initializing node address buffer\n"); +#endif + pthread_mutex_unlock (&ndt->mutex); + pthread_mutex_destroy(&ndt->mutex); + return -1; + } + + /* initialise the state of each newly added node in the array... */ + for (count = ndt->node_count; count < ndt->node_count + new_nd_count; count++) { + if (nd_entry_init(&ndt->node[count]) < 0) { + pthread_mutex_unlock(&ndt->mutex); + return -1; + } + } /* for() */ + ndt->node_count += new_nd_count; + ndt->free_node_count += new_nd_count; + + pthread_mutex_unlock(&ndt->mutex); + return new_nd_count; /* number of succesfully created nodes! */ +} +#endif + + +static int nd_table_get_free_node(nd_table_t *ndt, nd_type_t nd_type) { + int count; + + /* lock the mutex */ + while (pthread_mutex_lock(&ndt->mutex) != 0) sched_yield(); + + /* check for free nodes... */ + if (ndt->free_node_count <= 0) { + /* no free nodes... */ + pthread_mutex_unlock(&ndt->mutex); + return -1; + } + + /* Decrement the free node counter...*/ + ndt->free_node_count--; + + /* search for a free node... */ + for (count = 0; count < ndt->node_count; count++) { + if(ndt->node[count].node_type == MB_FREE_NODE) { + /* found one!! Allocate it to the new type! */ + ndt->node[count].node_type = nd_type; + pthread_mutex_unlock(&ndt->mutex); + return count; + } + } /* for() */ + + /* Strange... We should have free nodes, but we didn't finda any! */ + /* Let's try to get into a consistent state, and return an error! */ + ndt->free_node_count = 0; + pthread_mutex_unlock(&ndt->mutex); + return -1; +} + + + +static void nd_table_close_node(nd_table_t *ndt, int nd) { + + /* lock the mutex */ + while (pthread_mutex_lock(&ndt->mutex) != 0) sched_yield(); + + if(ndt->node[nd].node_type == MB_FREE_NODE) { + /* Node already free... */ + pthread_mutex_unlock(&ndt->mutex); + return; + } + + /* Increment the free node counter...*/ + ndt->free_node_count++; + /* Mark the node as being free. */ + ndt->node[nd].node_type = MB_FREE_NODE; + + pthread_mutex_unlock(&ndt->mutex); + return; +} + + + +/**************************************************************/ +/**************************************************************/ +/**** ****/ +/**** ****/ +/**** Global Library State ****/ +/**** ****/ +/**** ****/ +/**************************************************************/ +/**************************************************************/ + + + /* The node descriptor table... */ + /* NOTE: The node_table_ Must be initialized correctly here! */ +static nd_table_t nd_table_ = {.node=NULL, .node_count=0, .free_node_count=0}; + + +/**************************************************************/ +/**************************************************************/ +/**** ****/ +/**** ****/ +/**** Local Utility functions... ****/ +/**** ****/ +/**** ****/ +/**************************************************************/ +/**************************************************************/ + + +#define min(a,b) ((ab)?a:b) + +/************************************/ +/** **/ +/** Configure socket for Modbus **/ +/** **/ +/************************************/ + + +static int configure_socket(int socket_id) { + + /* configure the socket */ + /* Set it to be non-blocking. This is safe because we always use select() before reading from it! + * It is also required for the connect() call. The default timeout in the TCP stack is much too long + * (typically blocks for 128 s ??) when the connect does not succedd imediately! + */ + if (fcntl(socket_id, F_SETFL, O_NONBLOCK) < 0) { +#ifdef ERRMSG + perror("fcntl()"); + fprintf(stderr, ERRMSG_HEAD "Error configuring socket 'non-blocking' option.\n"); +#endif + return -1; + } + + /* configure the socket */ + /* set the TCP no delay flag. */ + {int bool_opt = 1; + if (setsockopt(socket_id, SOL_TCP, TCP_NODELAY, + (const void *)&bool_opt, sizeof(bool_opt)) + < 0) { +#ifdef ERRMSG + perror("setsockopt()"); + fprintf(stderr, ERRMSG_HEAD "Error configuring socket 'TCP no delay' option.\n"); +#endif + return -1; + } + } + + /* set the IP low delay option. */ + {int priority_opt = IPTOS_LOWDELAY; + if (setsockopt(socket_id, SOL_IP, IP_TOS, + (const void *)&priority_opt, sizeof(priority_opt)) + < 0) { +#ifdef ERRMSG + perror("setsockopt()"); + fprintf(stderr, ERRMSG_HEAD "Error configuring socket 'IP low delay' option.\n"); +#endif + return -1; + } + } + +#if 0 + /* send buffer */ + /* NOTE: For slave devices, that may be receiving multiple + * requests before they have a chance to reply to the first, + * it probably is a good idea to have a large receive buffer. + * So it is best to leave it with the default configuration, as it is + * larger than the largest Modbus TCP frame. + * + * For the send buffer, a smaller buffer should suffice. + * However, it probably does not make sense to + * waste time asking for a smaller buffer, since the larger + * default buffer has already been allocated (the socket has already + * been created!) + * + * We might just as well leave out the configuration of the socket + * buffer size... + */ +#define SOCK_BUF_SIZE 300 /* The size proposed in the Modbus TCP spec. */ + {int sock_buf_size; + sock_buf_size = SOCK_BUF_SIZE; + if (setsockopt(socket_id, SOL_SOCKET, SO_SNDBUF, + (const void *)&sock_buf_size, sizeof(sock_buf_size)) + < 0) + return -1; + /* recv buffer */ + sock_buf_size = SOCK_BUF_SIZE; + if (setsockopt(socket_id, SOL_SOCKET, SO_RCVBUF, + (const void *)&sock_buf_size, sizeof(sock_buf_size)) + < 0) + return -1; + } +#endif + + return 0; +} + + +/************************************/ +/** **/ +/** Connect socket to remote host **/ +/** **/ +/************************************/ + +/* This function will create a new socket, and connect it to a remote host... */ +static inline int open_connection(int nd, const struct timespec *timeout) { + int socket_id, con_res; + +#ifdef DEBUG + printf("[%lu] open_connection(): called, nd = %d\n", pthread_self(), nd); +#endif + + if (nd_table_.node[nd].fd >= 0) + /* nd already connected) */ + return nd_table_.node[nd].fd; + + if (nd_table_.node[nd].addr.sin_family != AF_INET) + /* invalid remote address, or invalid nd */ + return -1; + + /* lets try to connect... */ + /* create the socket */ + if ((socket_id = socket(PF_INET, DEF_TYPE, 0 /* protocol_num */)) < 0) { +#ifdef DEBUG + perror("socket()"); + fprintf(stderr, "[%lu] Error creating socket\n", pthread_self()); +#endif +#ifdef ERRMSG + perror("socket()"); + fprintf(stderr, ERRMSG_HEAD "Error creating socket\n"); +#endif + return -1; + } + + /* configure the socket - includes setting non-blocking option! */ + if (configure_socket(socket_id) < 0) { + close(socket_id); + return -1; + }; + + /* establish the connection to remote host */ + con_res = connect(socket_id, + (struct sockaddr *)&(nd_table_.node[nd].addr), + sizeof(nd_table_.node[nd].addr)); + + /* The following condition is not strictly necessary + * (we could let the code fall through) + * but it does make the code easier to read/understand... + */ + if (con_res >= 0) + goto success_exit; /* connected succesfully on first try! */ + + if (con_res < 0) { + if ((errno != EINPROGRESS) && (errno != EALREADY)) + goto error_exit; /* error in connection request! */ + + /* connection request is ongoing */ + /* EINPROGRESS -> first call to connect, EALREADY -> subsequent calls to connect */ + /* Must wait for connect to complete at most 'timeout' seconds */ + {fd_set fdset; + int res, so_error; + socklen_t len; + struct timespec end_time, *et_ptr; + + et_ptr = NULL; + if (timeout != NULL) { + et_ptr = &end_time; + *et_ptr = timespec_add_curtime(*timeout); + } + + FD_ZERO(&fdset); + FD_SET(socket_id, &fdset); + + res = my_select(socket_id+1, NULL, &fdset, et_ptr); + if (res < 0) goto error_exit; /* error on call to select */ + if (res == 0) goto error_exit; /* timeout */ + /* (res > 0) -> connection attemt completed. May have been success or failure! */ + + len = sizeof(so_error); + res = getsockopt(socket_id, SOL_SOCKET, SO_ERROR, &so_error, &len); + if (res < 0) goto error_exit; /* error on call to getsockopt */ + if (so_error != 0) goto error_exit; /* error on connection attempt */ + goto success_exit; /* succesfully completed connection attempt! */ + /* goto sucess_exit is not strcitly necessary - we could let the code fall through! */ + } + } + +success_exit: + nd_table_.node[nd].fd = socket_id; + /* Succesfully established connection => print a message next time we have error. */ + nd_table_.node[nd].print_connect_error = 1; + +#ifdef DEBUG + printf("[%lu] open_connection(): returning...\n", pthread_self()); +#endif + return socket_id; + +error_exit: +#ifdef ERRMSG + if (nd_table_.node[nd].print_connect_error > 0) { + perror("connect()"); + fprintf(stderr, ERRMSG_HEAD "Error establishing socket connection.\n"); + /* do not print more error messages for this node... */ + nd_table_.node[nd].print_connect_error = 0; + } +#endif + close(socket_id); + return -1; +} + + +/* This function will accept a new connection request, and attribute it to a new node... */ +static inline int accept_connection(int nd) { + int socket_id, new_nd; + +#ifdef DEBUG + printf("[%lu] accept_connection(): called, nd = %d\n", pthread_self(), nd); +#endif + + /* NOTE: We MUST accccept8) all connection requests, even if no new node is available. + * => We first accept the connection request, and only later look for a node. + * If no node is free/available for this new connections request, the + * connection will be accepted and immediately closed. + * Reason: + * When the library is used for a Modbus/TCP server and no free node is + * available, if we do not accept() all newly arrived connection requests + * we would enter an infinite loop calling + * - select() (in modbus_tcp_read()) + * - and accept_connection(). + * Note that select() will continue to return immediately if the + * connection request is not accept()ted! + */ + /* lets accept new connection request... */ + if ((socket_id = accept(nd_table_.node[nd].fd, NULL, NULL)) < 0) { +#ifdef ERRMSG + perror("accept()"); + fprintf(stderr, ERRMSG_HEAD "Error while waiting for connection request from new client\n"); +#endif + /* error establishing new connection... */ + return -1; + } + + /* find a free node */ + if ((new_nd = nd_table_get_free_node(&nd_table_, MB_SLAVE_NODE)) < 0) { + /* no available free nodes for the new connection... */ + close(socket_id); + return -1; + } + + /* configure the socket - includes setting the non-blocking option! */ + if (configure_socket(socket_id) < 0) { + nd_table_close_node(&nd_table_, new_nd); /* first free up the un-used node. */ + close(socket_id); + return -1; + } + + /* set up the node entry and update the fd sets */ + nd_table_.node[new_nd].fd = socket_id; + nd_table_.node[new_nd].listen_node = nd; + +#ifdef DEBUG + printf("[%lu] accept_connection(): returning new_nd = %d\n", pthread_self(), new_nd); +#endif + return new_nd; +} + + +static inline void close_connection(int nd) { + if (nd_table_.node[nd].fd >= 0) { + /* disconnect the tcp connection */ + shutdown(nd_table_.node[nd].fd, SHUT_RDWR); +#ifdef ERRMSG + int res = +#endif + close(nd_table_.node[nd].fd); +#ifdef ERRMSG + if (res < 0) { + perror("close()"); + fprintf(stderr, ERRMSG_HEAD "Error closing socket\n"); + } +#endif + nd_table_.node[nd].fd = -1; + } + + if (nd_table_.node[nd].node_type == MB_SLAVE_NODE) { + /* If it is a slave node, we will not be receiving any more data over this disconnected node, + * (MB_SLAVE_NODE do not get re-connected!), so we free the node... + */ + nd_table_close_node(&nd_table_, nd); + } +} + + + +/************************************/ +/** **/ +/** Data format conversion **/ +/** **/ +/************************************/ + +/* + * Functions to convert u16 variables + * between network and host byte order + * + * NOTE: Modbus uses MSByte first, just like + * tcp/ip, so we use the htons() and + * ntoh() functions to guarantee + * code portability. + */ + +static inline u16 mb_hton(u16 h_value) { +/* return h_value; */ + return htons(h_value); +} + +static inline u16 mb_ntoh(u16 m_value) { +/* return m_value; */ + return ntohs(m_value); +} + +static inline u8 msb(u16 value) { +/* return Most Significant Byte of value; */ + return (value >> 8) & 0xFF; +} + +static inline u8 lsb(u16 value) { +/* return Least Significant Byte of value; */ + return value & 0xFF; +} + +#define u16_v(char_ptr) (*((u16 *)(&(char_ptr)))) + + +/************************************/ +/** **/ +/** Build/Check a frame header **/ +/** **/ +/************************************/ + +/* A modbus TCP frame header has 6 bytes... + * header[0-1] -> transaction id + * header[2-3] -> must be 0 + * header[4-5] -> frame data length (must be <= 255) + */ +#if TCP_HEADER_LENGTH < 6 +#error This code assumes a header size of 6 bytes, but TCP_HEADER_LENGTH < 6 +#endif + +static inline void build_header(u8 *header, + u16 transaction_id, + u16 byte_count) +{ + u16_v(header[0]) = mb_hton(transaction_id); + header[2] = 0; + header[3] = 0; + u16_v(header[4]) = mb_hton(byte_count); +} + + +static inline int check_header(u8 *header, + u16 *transaction_id, + u16 *byte_count) +{ + if ((header[2] != 0) || (header[3] != 0)) + return -1; + + *transaction_id = mb_ntoh(*(u16 *)(header + 0)); + *byte_count = mb_ntoh(*(u16 *)(header + 4)); + + if (*byte_count > MAX_L2_FRAME_LENGTH) + return -1; + + return 0; +} + + + + + +/**************************************************************/ +/**************************************************************/ +/**** ****/ +/**** ****/ +/**** Sending of Modbus TCP Frames ****/ +/**** ****/ +/**** ****/ +/**************************************************************/ +/**************************************************************/ + +// pthread_mutex_t sendmsg_mutex = PTHREAD_MUTEX_INITIALIZER; + +/* NOTE: this function MUST be thread safe!! */ +int modbus_tcp_write(int nd, /* node descriptor */ + u8 *data, + size_t data_length, + u16 transaction_id, + const struct timespec *transmit_timeout + ) +{ +#define data_vector_size 2 + + u8 header[TCP_HEADER_LENGTH]; + struct iovec data_vector[data_vector_size] = { + {(void *)header, TCP_HEADER_LENGTH}, + {NULL, 0}}; + struct msghdr msg = {NULL, 0, data_vector, data_vector_size, NULL, 0, 0}; + int res, bytes_sent; + +#ifdef DEBUG + printf("[%lu] modbus_tcp_write(): called... nd=%d\n", pthread_self(), nd); +#endif + + if ((nd >= nd_table_.node_count) || (nd < 0)) + /* invalid node descriptor... */ + return -1; + +#ifdef DEBUG +// printf("[%lu] locking mutex...\n", pthread_self()); +#endif +// while (pthread_mutex_lock(&sendmsg_mutex) != 0); + + /************************* + * prepare the header... * + *************************/ + build_header(header, transaction_id, data_length); +#ifdef DEBUG +/* Print the hex value of each character that is about to be + * sent over the bus. + */ + { int i; + printf("modbus_tcp_write(): sending data...\n"); + for(i = 0; i < TCP_HEADER_LENGTH; i++) + printf("[0x%2X]", header[i]); + for(i = 0; i < data_length; i++) + printf("[0x%2X]", data[i]); + printf("\n"); + } +#endif + + /****************************************** + * do we need to re-establish connection? * + ******************************************/ + if (open_connection(nd, transmit_timeout) < 0) { +#ifdef DEBUG + fprintf(stderr, "[%lu] modbus_tcp_write(): could not establish connection...\n", pthread_self()); +#endif +#ifdef ERRMSG + fprintf(stderr, ERRMSG_HEAD "could not establish connection...\n"); +#endif + return -1; + } + + /********************** + * write to output... * + **********************/ + /* TWO ALTERNATIVE IMPLEMENTATIONS !!! */ +#if 0 + /* write header */ + bytes_sent = 0; + while (1) { + res = write(nd_table_.node[nd].fd, header+bytes_sent, TCP_HEADER_LENGTH-bytes_sent); + if (res < 0) { + if ((errno != EAGAIN ) && (errno != EINTR )) { + /* error sending message... */ + close_connection(nd); + return -1; + } else { + continue; + } + } else { + /* res >= 0 */ + bytes_sent += res; + if (bytes_sent >= TCP_HEADER_LENGTH) { + break; + } + } + } + + /* write data */ + bytes_sent = 0; + while (1) { + res = write(nd_table_.node[nd].fd, data+bytes_sent, data_length-bytes_sent); + if (res < 0) { + if ((errno != EAGAIN ) && (errno != EINTR )) { + /* error sending message... */ + close_connection(nd); + return -1; + } else { + continue; + } + } else { + /* res >= 0 */ + bytes_sent += res; + if (bytes_sent >= data_length) { + /* query succesfully sent! */ +#ifdef DEBUG + printf("[%lu] modbus_tcp_write(): sent %d bytes\n", pthread_self(), TCP_HEADER_LENGTH+data_length); +#endif + return data_length; + } + } + } + + /********************** + * write to output... * + **********************/ +#else + /* We are optimising for the most likely case, and in doing that + * we are making the least likely case have worse behaviour! + * Read on for an explanation... + * + * - The optimised behaviour for the most likely case: + * We have set the NO_DELAY flag on the socket, so the IP datagram + * is not delayed and is therefore sent as soon as any data is written to + * the socket. + * In order to send the whole message in a single IP datagram, we have to + * write both the the header and the data with a single call to write() + * In order to not to have to copy the data around just to add the + * message header, we use sendmsg() instead of write()! + * + * - The worse behaviour for the least likely case: + * If for some reason only part of the data is sent with the first call to + * write(), a datagram is sent right away, and the subsequent data will + * be sent in another datagram. :-( + */ + /* NOTE: since snedmsg() is not thread safe, we use a mutex to protect access to this function... */ + + data_vector[data_vector_size - 1].iov_base = data; + data_vector[data_vector_size - 1].iov_len = data_length; + data_vector[ 0].iov_base = header; + data_vector[ 0].iov_len = TCP_HEADER_LENGTH; + bytes_sent = 0; + while (1) { + int sendmsg_errno; + /* Please see the comment just above the main loop!! */ + res = sendmsg(nd_table_.node[nd].fd, &msg, 0); + sendmsg_errno = errno; + if (res < 0) { + if ((sendmsg_errno != EAGAIN ) && (sendmsg_errno != EINTR )) { + /* error sending message... */ + close_connection(nd); + return -1; + } else { + continue; + } + } else { + /* res >= 0 */ + bytes_sent += res; + if (bytes_sent >= data_length + TCP_HEADER_LENGTH) { + /* query succesfully sent! */ +#ifdef DEBUG + printf("[%lu] modbus_tcp_write(): sent %d bytes\n", pthread_self(), bytes_sent); +#endif +// pthread_mutex_unlock(&sendmsg_mutex); +#ifdef DEBUG +// printf("[%lu] unlocked mutex...\n", pthread_self()); +#endif + return data_length; + } + + /* adjust the data_vector... */ + if (res < data_vector[0].iov_len) { + u8* tmp = data_vector[0].iov_base; + tmp += res; + data_vector[0].iov_len -= res; + data_vector[0].iov_base = tmp; + } else { + u8* tmp = data_vector[1].iov_base; + tmp += res; + res -= data_vector[0].iov_len; + data_vector[0].iov_len = 0; + data_vector[1].iov_len -= res; + data_vector[1].iov_base = tmp; + } + } + } /* while (1) */ +#endif + + /* humour the compiler... */ +// pthread_mutex_unlock(&sendmsg_mutex); +#ifdef DEBUG +// printf("[%lu] unlocked mutex...\n", pthread_self()); +#endif + return -1; +} + + + +/**************************************************************/ +/**************************************************************/ +/**** ****/ +/**** ****/ +/**** Receiving Modbus TCP Frames ****/ +/**** ****/ +/**** ****/ +/**************************************************************/ +/**************************************************************/ + + +/* A helper function to modbus_tcp_read() + * + * WARNING: The semantics of this function are not what you would expect! + * + * if (data_already_available != 0) + * It assumes that select() has already been called before + * this function got called, and we are therefore guaranteed + * to have at least one byte to read off the socket (the fd). + * + * if (data_already_available == 0) + * it starts off by calling select()! + * + * + * NOTE: Ususal select semantics for (a: end_time == NULL) and + * (b: *end_time == 0) also apply. + * + * (a) Indefinite timeout + * (b) Try once, and and quit if no data available. + */ +/* RETURNS: number of bytes read + * -1 read error! + * -2 timeout + */ +static int read_bytes(int fd, + u8 *data, + int max_data_count, + const struct timespec *end_time, + int data_already_available) +{ + fd_set rfds; + int res, data_count; + + data_count = 0; + + while (data_count < max_data_count) { + /*============================* + * wait for data availability * + *============================*/ + if (data_already_available == 0) { + int sel_res; + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + sel_res = my_select(fd + 1, &rfds, NULL, end_time); + if (sel_res < 0) + return -1; + if (sel_res == 0) + /* timeout! */ + return -2; + } + + /*============================* + * read the available data... * + *============================*/ + res = read(fd, data + data_count, max_data_count - data_count); + if (res == 0) { + /* We are guaranteed to have data to read off the fd since we called + * select(), but read() returned 0 bytes. + * This means that the remote process has closed down the connection, + * so we return 0. + */ + return 0; + } + + if (res < 0) { + if (errno != EINTR) + return -1; + else + res = 0; + } +#ifdef DEBUG + {/* display the hex code of each character received */ + int i; + for (i=0; i < res; i++) + printf("<0x%2X>", *(data + data_count + i)); + } +#endif + data_count += res; + data_already_available = 0; + } /* while ()*/ + + /* data read succesfully... */ + return data_count; +} + + + +/***************************************/ +/** **/ +/** Read a Modbus TCP frame **/ +/** off a single identified node. **/ +/** **/ +/***************************************/ + +/* This private function will read a Modbus TCP frame off a single identified node + * that we know before hand that has data ready to be read off it. The data may or may not be + * a valid Modbus TCP frame. It is up to this function to figure that out. + */ +/* NOTES: + * - We re-use the recv_buf_ to load the frame header, so we have to make + * sure that the buffer is large enough to take it... + */ + /* RETURNS: number of bytes read + * -1 on read from file/node error + * -2 on timeout + */ +#if RECV_BUFFER_SIZE < TCP_HEADER_LENGTH +#error The receive buffer is smaller than the frame header length. +#endif + +static int modbus_tcp_read_frame(int nd, + u16 *transaction_id, + struct timespec *ts_ptr) { + int fd, res; + u16 frame_length; + +#ifdef DEBUG + printf("[%lu] modbus_tcp_read_frame(): reading off nd=%d\n", pthread_self(), nd); +#endif + /*=========================* + * read a Modbus TCP frame * + *=========================*/ + /* assume error... */ + fd = nd_table_.node[nd].fd; + + /*-------------* + * read header * + *-------------*/ + if ((res = read_bytes(fd, nd_table_.node[nd].recv_buf, TCP_HEADER_LENGTH, ts_ptr, 1)) != TCP_HEADER_LENGTH) { +#ifdef DEBUG + printf("[%lu] modbus_tcp_read_frame(): frame with insuficient bytes for a valid header...\n", pthread_self()); +#endif + if (res < 0) return res; + return -1; + } + + /* let's check for header consistency... */ + if (check_header(nd_table_.node[nd].recv_buf, transaction_id, &frame_length) < 0) { +#ifdef DEBUG + printf("[%lu] modbus_tcp_read_frame(): frame with non valid header...\n", pthread_self()); +#endif + return -1; + } + + /*-----------* + * read data * + *-----------*/ + if ((res = read_bytes(fd, nd_table_.node[nd].recv_buf, frame_length, ts_ptr, 0)) != frame_length) { +#ifdef DEBUG + printf("[%lu] modbus_tcp_read_frame(): frame with non valid frame length...\n", pthread_self()); +#endif + if (res < 0) return res; + return -1; + } + + /* frame received succesfully... */ +#ifdef DEBUG + printf("\n"); +#endif + return frame_length; +} + + + + +/***************************************/ +/** **/ +/** Read a Modbus TCP frame **/ +/** OR Accept connection requests **/ +/** off possibly multiple node... **/ +/** **/ +/***************************************/ + +/* The public function that reads a valid modbus frame. + * The frame is read from...: + * - if (nd >= 0) and (nd is of type MB_MASTER_NODE or MB_SLAVE_NODE) + * The frame is read from the node descriptor nd + * - if (nd >= 0) and (nd is of type MB_LISTEN_NODE) + * The frame is read from the all node descriptors of type MB_SLAVE_NODE that were + * opened as a consequence of a connection request to the nd slave. + * In this case, new connection requests to nd will also be accepted! + * - if (nd == -1) + * The frame is read from any valid and initialised node descriptor. + * In this case, new connection requests to any nd of type MB_LISTEN_NODE will also be accepted! + * In this case, the node where the data is eventually read from is returned in *nd. + * + * The send_data and send_length parameters are ignored... + * (However, these parameters must stay in order to keep the function + * interface identical to the ASCII and RTU versons!) + * + * return value: The length (in bytes) of the valid frame, + * -1 on error + * + * NOTE: Ususal select semantics for (a: recv_timeout == NULL) and + * (b: *recv_timeout == 0) also apply. + * + * (a) Indefinite timeout + * (b) Try once, and and quit if no data available. + */ + + /* RETURNS: number of bytes read + * -1 on read from file/node error + * -2 on timeout + */ +int modbus_tcp_read(int *nd, /* node descriptor */ + u8 **recv_data_ptr, + u16 *transaction_id, + const u8 *send_data, /* ignored ! */ + int send_length, /* ignored ! */ + const struct timespec *recv_timeout) { + + struct timespec end_time, *ts_ptr; + u8 *local_recv_data_ptr; + u16 local_transaction_id = 0; + +#ifdef DEBUG + printf("[%lu] modbus_tcp_read(): called... nd=%d\n", pthread_self(), *nd); +#endif + + if (nd == NULL) + return -1; + + if (*nd >= nd_table_.node_count) + /* invalid *nd */ + /* remember that *nd < 0 is valid!! */ + return -1; + + if (recv_data_ptr == NULL) + recv_data_ptr = &local_recv_data_ptr; + if (transaction_id == NULL) + transaction_id = &local_transaction_id; + + /* We will potentially call read() multiple times to read in a single frame. + * We therefore determine the absolute time_out, and use this as a parameter + * for each call to read_bytes() instead of using a relative timeout. + * + * NOTE: see also the timeout related comment in the read_bytes() function! + */ + ts_ptr = NULL; + if (recv_timeout != NULL) { + ts_ptr = &end_time; + *ts_ptr = timespec_add_curtime(*recv_timeout); + } + + /* If we must read off a single node... */ + if (*nd >= 0) + /* but the node does not have a valid fd */ + if ((nd_table_.node[*nd].node_type == MB_FREE_NODE) || + (nd_table_.node[*nd].fd < 0)) + /* then we return an error... */ + return -1; + + /* We will loop forever... + * We jump out of the loop and return from the function as soon as: + * - we receive a valid modbus message; + * OR + * - we time out. + * + * NOTE: This loop will close connections through which we receive invalid frames. + * This means that the set of nodes through which we may receive data may change with each + * loop iteration. => We need to re-calculate the fds in each loop iteration! + */ + + while (1) { + int nd_count, fd_high; + fd_set rfds; + + /* We prepare our fd sets here so we can later call select() */ + FD_ZERO(&rfds); + fd_high = -1; + + for (nd_count = 0; nd_count < nd_table_.node_count; nd_count++) { + if (nd_table_.node[nd_count].node_type != MB_FREE_NODE) + { + if ((*nd < 0) // we select from all nodes + || (*nd == nd_count) // we select from this specific node + // we are listening on a MB_LISTEN_NODE, so we must also receive requests sent to slave nodes + // whose connection requests arrived through this MB_LISTEN_NDODE + || ((nd_table_.node[nd_count].node_type == MB_SLAVE_NODE) && (nd_table_.node[nd_count].listen_node == *nd))) + { + /* check if valid fd */ + if (nd_table_.node[nd_count].fd >= 0) { + /* Add the descriptor to the fd set... */ + FD_SET(nd_table_.node[nd_count].fd, &rfds); + fd_high = max(fd_high, nd_table_.node[nd_count].fd); + } + } + } + } /* for(;;) */ + +#ifdef DEBUG + printf("[%lu] modbus_tcp_read(): while(1) looping. fd_high = %d, nd=%d\n", pthread_self(), fd_high, *nd); +#endif + + if (fd_high == -1) + /* we will not be reading from any node! */ + return -1; + + /* We now call select and wait for activity on the nodes we are listening to */ + { int sel_res = my_select(fd_high + 1, &rfds, NULL, ts_ptr); + if (sel_res < 0) + return -1; + if (sel_res == 0) + /* timeout! */ + return -2; + } + + /* figure out which nd is ready to be read... */ + for (nd_count = 0; nd_count < nd_table_.node_count; nd_count++) { + if ((nd_table_.node[nd_count].node_type != MB_FREE_NODE) && + (nd_table_.node[nd_count].fd >= 0)) { + if (FD_ISSET(nd_table_.node[nd_count].fd, &rfds)) { + /* Found the node descriptor... */ +#ifdef DEBUG + printf("[%lu] modbus_tcp_read(): my_select() returned due to activity on node nd=%d\n", pthread_self(), nd_count); +#endif + if (nd_table_.node[nd_count].node_type == MB_LISTEN_NODE) { + /* We must accept a new connection... + * No need to check for errors. + * If one occurs, there is nothing we can do... + */ + accept_connection(nd_count); + } else { + /* it is a MB_SLAVE_NODE or a MB_MASTER_NODE */ + /* We will read a frame off this nd */ + int res; + res = modbus_tcp_read_frame(nd_count, transaction_id, ts_ptr); + if (res > 0) { + *nd = nd_count; + *recv_data_ptr = nd_table_.node[nd_count].recv_buf; + return res; + } + if (res < 0) { + /* We had an error reading the frame... + * We handle it by closing the connection, as specified by + * the modbus TCP protocol! + * + * NOTE: The error may have been a timeout, which means this function should return immediately. + * However, in this case we let the execution loop once again + * in the while(1) loop. My_select() will be called again + * and the timeout detected. The timeout error code (-2) + * will then be returned correctly! + */ +#ifdef DEBUG + printf("[%lu] modbus_tcp_read(): error reading frame. Closing connection...\n", pthread_self()); +#endif + /* We close the socket... */ + close_connection(nd_count); + } + } + /* we have found the node descriptor, so let's jump out of the for(;;) loop */ + break; + } + } + } /* for(;;) */ + + /* We were unsuccesfull reading a frame, so we try again... */ + } /* while (1) */ + + /* humour the compiler... */ + return -1; +} + + + + + +/**************************************************************/ +/**************************************************************/ +/**** ****/ +/**** ****/ +/**** Initialising and Shutting Down Library ****/ +/**** ****/ +/**** ****/ +/**************************************************************/ +/**************************************************************/ + + +/* Ugly hack... + * Beremiz will be calling modbus_tcp_init() multiple times (through modbus_init() ) + * (once for each plugin instance) + * It will also be calling modbus_tcp_done() the same number of times + * We only want to really shutdown the library the last time it is called. + * We therefore keep a counter of how many times modbus_tcp_init() is called, + * and decrement it in modbus_tcp_done() + */ +int modbus_tcp_init_counter = 0; + +/******************************/ +/** **/ +/** Load Default Values **/ +/** **/ +/******************************/ + +static void set_defaults(const char **service) { + /* Set the default values, if required... */ + if (*service == NULL) + *service = DEF_SERVICE; +} + + +/******************************/ +/** **/ +/** Initialise Library **/ +/** **/ +/******************************/ +/* returns the number of nodes succesfully initialised... + * returns -1 on error. + */ +int modbus_tcp_init(int nd_count, + optimization_t opt /* ignored... */, + int *extra_bytes) { +#ifdef DEBUG + printf("[%lu] modbus_tcp_init(): called...\n", pthread_self()); + printf("[%lu] creating %d nodes:\n", pthread_self(), nd_count); +#endif + + modbus_tcp_init_counter++; + + /* set the extra_bytes value... */ + /* Please see note before the modbus_rtu_write() function for a + * better understanding of this extremely ugly hack... This will be + * in the mb_rtu.c file!! + * + * The number of extra bytes that must be allocated to the data buffer + * before calling modbus_tcp_write() + */ + if (extra_bytes != NULL) + *extra_bytes = 0; + + if (0 == nd_count) + /* no need to initialise this layer! */ + return 0; + if (nd_count <= 0) + /* invalid node count... */ + goto error_exit_1; + + /* initialise the node table... */ + if (nd_table_init(&nd_table_, nd_count) < 0) + goto error_exit_1; + +#ifdef DEBUG + printf("[%lu] modbus_tcp_init(): %d node(s) opened succesfully\n", pthread_self(), nd_count); +#endif + return nd_count; /* number of succesfully created nodes! */ + +/* +error_exit_2: + nd_table_done(&nd_table_); +*/ +error_exit_1: + if (extra_bytes != NULL) + *extra_bytes = 0; + return -1; +} + + + + + + +/******************************/ +/** **/ +/** Open a Master Node **/ +/** **/ +/******************************/ +int modbus_tcp_connect(node_addr_t node_addr) { + int node_descriptor; + struct sockaddr_in tmp_addr; + +#ifdef DEBUG + printf("[%lu] modbus_tcp_connect(): called...\n", pthread_self()); + printf("[%lu] %s:%s\n", pthread_self(), + node_addr.addr.tcp.host, + node_addr.addr.tcp.service); +#endif + + /* Check for valid address family */ + if (node_addr.naf != naf_tcp) + /* wrong address type... */ + return -1; + + /* set the default values... */ + set_defaults(&(node_addr.addr.tcp.service)); + + /* Check the parameters we were passed... */ + if(sin_initaddr(&tmp_addr, + node_addr.addr.tcp.host, 0, + node_addr.addr.tcp.service, 0, + DEF_PROTOCOL) + < 0) { +#ifdef ERRMSG + fprintf(stderr, ERRMSG_HEAD "Error parsing/resolving address %s:%s\n", + node_addr.addr.tcp.host, + node_addr.addr.tcp.service); +#endif + return -1; + } + + /* find a free node descriptor */ + if ((node_descriptor = nd_table_get_free_node(&nd_table_, MB_MASTER_NODE)) < 0) + /* if no free nodes to initialize, then we are finished... */ + return -1; + + nd_table_.node[node_descriptor].addr = tmp_addr; + nd_table_.node[node_descriptor].fd = -1; /* not currently connected... */ + nd_table_.node[node_descriptor].close_on_silence = node_addr.addr.tcp.close_on_silence; + + if (nd_table_.node[node_descriptor].close_on_silence < 0) + nd_table_.node[node_descriptor].close_on_silence = DEF_CLOSE_ON_SILENCE; + + /* WE have never tried to connect, so print an error the next time we try to connect */ + nd_table_.node[node_descriptor].print_connect_error = 1; + +#ifdef DEBUG + printf("[%lu] modbus_tcp_connect(): returning nd=%d\n", pthread_self(), node_descriptor); +#endif + return node_descriptor; +} + + + +/******************************/ +/** **/ +/** Open a Slave Node **/ +/** **/ +/******************************/ + +int modbus_tcp_listen(node_addr_t node_addr) { + int fd, nd; + +#ifdef DEBUG + printf("[%lu] modbus_tcp_listen(): called...\n", pthread_self()); + printf("[%lu] %s:%s\n", pthread_self(), + node_addr.addr.tcp.host, + node_addr.addr.tcp.service); +#endif + + /* Check for valid address family */ + if (node_addr.naf != naf_tcp) + /* wrong address type... */ + goto error_exit_0; + + /* set the default values... */ + set_defaults(&(node_addr.addr.tcp.service)); + + /* create a socket and bind it to the appropriate port... */ + fd = sin_bindsock(node_addr.addr.tcp.host, + node_addr.addr.tcp.service, + DEF_PROTOCOL); + if (fd < 0) { +#ifdef ERRMSG + fprintf(stderr, ERRMSG_HEAD "Could not bind to socket %s:%s\n", + ((node_addr.addr.tcp.host==NULL)?"#ANY#":node_addr.addr.tcp.host), + node_addr.addr.tcp.service); +#endif + goto error_exit_0; + } + if (listen(fd, DEF_MAX_PENDING_CONNECTION_REQUESTS) < 0) + goto error_exit_0; + + /* find a free node descriptor */ + if ((nd = nd_table_get_free_node(&nd_table_, MB_LISTEN_NODE)) < 0) { + /* if no free nodes to initialize, then we are finished... */ + goto error_exit_1; + } + + /* nd_table_.node[nd].addr = tmp_addr; */ /* does not apply for MB_LISTEN_NODE */ + nd_table_.node[nd].fd = fd; /* not currently connected... */ + +#ifdef DEBUG + printf("[%lu] modbus_tcp_listen(): returning nd=%d\n", pthread_self(), nd); +#endif + return nd; + +error_exit_1: + close(fd); +error_exit_0: + return -1; +} + + + +/******************************/ +/** **/ +/** Close a node **/ +/** **/ +/******************************/ + +int modbus_tcp_close(int nd) { +#ifdef DEBUG + fprintf(stderr, "[%lu] modbus_tcp_close(): called... nd=%d\n", pthread_self(), nd); +#endif + + if ((nd < 0) || (nd >= nd_table_.node_count)) { + /* invalid nd */ +#ifdef DEBUG + fprintf(stderr, "[%lu] modbus_tcp_close(): invalid node %d. Should be < %d\n", pthread_self(), nd, nd_table_.node_count); +#endif + return -1; + } + + if (nd_table_.node[nd].node_type == MB_FREE_NODE) + /* already free node */ + return 0; + + close_connection(nd); + + nd_table_close_node(&nd_table_, nd); + + return 0; +} + + + +/**********************************/ +/** **/ +/** Close all open connections **/ +/** **/ +/**********************************/ + +int modbus_tcp_silence_init(void) { + int nd; + +#ifdef DEBUG + printf("[%lu] modbus_tcp_silence_init(): called...\n", pthread_self()); +#endif + + /* close all master connections that remain open... */ + for (nd = 0; nd < nd_table_.node_count; nd++) + if (nd_table_.node[nd].node_type == MB_MASTER_NODE) + if (nd_table_.node[nd].close_on_silence > 0) + /* node is is being used for a master device, + * and wishes to be closed... ...so we close it! + */ + close_connection(nd); + + return 0; +} + + + +/******************************/ +/** **/ +/** Shutdown the Library **/ +/** **/ +/******************************/ + +int modbus_tcp_done(void) { + int i; + + modbus_tcp_init_counter--; + if (modbus_tcp_init_counter != 0) return 0; /* ignore this request */ + + /* close all the connections... */ + for (i = 0; i < nd_table_.node_count; i++) + modbus_tcp_close(i); + + /* Free memory... */ + nd_table_done(&nd_table_); + + return 0; +} + + + + +double modbus_tcp_get_min_timeout(int baud, + int parity, + int data_bits, + int stop_bits) { + return 0; +} diff -r 000000000000 -r ae252e0fd9b8 mb_tcp_private.h --- /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 . + * + * This code is made available on the understanding that it will not be + * used in safety-critical situations without a full and competent review. + */ + + + +#ifndef MODBUS_TCP_PRIVATE_H +#define MODBUS_TCP_PRIVATE_H + + +#include "mb_util.h" + + +/* tcp port default configuration... */ +#define DEF_SERVICE "502" /* port used by modbus */ +#define DEF_PROTOCOL "tcp" /* protocol used by modbus tcp */ +#define DEF_TYPE SOCK_STREAM /* Quality of service required of the socket... */ +#define DEF_MAX_PENDING_CONNECTION_REQUESTS 5 + /* maximum number of pending connection requests + * that have not yet been accept()'ed + */ +#define DEF_CLOSE_ON_SILENCE 1 /* Used only by master nodes. + * Flag indicating whether, by default, the connection + * to the slave device should be closed whenever the + * modbus_tcp_silence_init() function is called. + * + * 0 -> do not close connection + * >0 -> close connection + * + * The spec sugests that connections that will not + * be used for longer than 1 second should be closed. + * Even though we expect most connections to have + * silence intervals much shorted than 1 second, we + * decide to use the default of shuting down the + * connections because it is safer, and most other + * implementations seem to do the same. + * If we do not close we risk using up all the possible + * connections that the slave can simultaneouly handle, + * effectively locking out every other master that + * wishes to communicate with that same slave. + */ + + /* Since the receive buffer is also re-used to store the frame header, + * we set it to the larger of the two. + */ +#if TCP_HEADER_LENGTH > MAX_L2_FRAME_LENGTH +#define RECV_BUFFER_SIZE TCP_HEADER_LENGTH +#else +#define RECV_BUFFER_SIZE MAX_L2_FRAME_LENGTH +#endif + + + + +#endif /* MODBUS_TCP_PRIVATE_H */ + + + + + + + + diff -r 000000000000 -r ae252e0fd9b8 mb_time_util.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 . + * + * This code is made available on the understanding that it will not be + * used in safety-critical situations without a full and competent review. + */ + + + + /* Time handling functions used by the modbus protocols... */ + + +#ifndef __MODBUS_TIME_UTIL_H +#define __MODBUS_TIME_UTIL_H + + +/************************************/ +/** **/ +/** Time format conversion **/ +/** **/ +/************************************/ + +/* Function to load a struct timeval correctly from a double. */ +static inline struct timeval d_to_timeval(double time) { + struct timeval tmp; + + tmp.tv_sec = time; + tmp.tv_usec = 1e6*(time - tmp.tv_sec); + return tmp; +} + + +/* Function to load a struct timespec correctly from a double. */ +static inline struct timespec d_to_timespec(double time) { + struct timespec tmp; + + tmp.tv_sec = time; + tmp.tv_nsec = 1e9*(time - tmp.tv_sec); + return tmp; +} + + + +/* Function to ... */ +static inline struct timespec timespec_dif(struct timespec ts1, struct timespec ts2) { + struct timespec ts; + + ts.tv_sec = ts1.tv_sec - ts2.tv_sec; + if(ts1.tv_nsec > ts2.tv_nsec) { + ts.tv_nsec = ts1.tv_nsec - ts2.tv_nsec; + } else { + ts.tv_nsec = 1000000000 + ts1.tv_nsec - ts2.tv_nsec; + ts.tv_sec--; + } + + if (ts.tv_sec < 0) + ts.tv_sec = ts.tv_nsec = 0; + + return ts; +} + +/* Function to ... */ +static inline struct timespec timespec_add(struct timespec ts1, struct timespec ts2) { + struct timespec ts; + + ts.tv_sec = ts1.tv_sec + ts2.tv_sec; + ts.tv_nsec = ts1.tv_nsec + ts2.tv_nsec; + ts.tv_sec += ts.tv_nsec / 1000000000; + ts.tv_nsec = ts.tv_nsec % 1000000000; + return ts; +} + +/* Function to convert a struct timespec to a struct timeval. */ +static inline struct timeval timespec_to_timeval(struct timespec ts) { + struct timeval tv; + + tv.tv_sec = ts.tv_sec; + tv.tv_usec = ts.tv_nsec/1000; + return tv; +} + + +/* + * NOTE: clock_gettime() is rather expensive, between 7000 and 7500 clock + * cycles (measured with rdtsc on an Intel Pentium) + * gettimeofday() is half as expensive (3000 to 3500 clock cycles), + * but is not POSIX compliant... :-( + * Nevertheless this is peanuts (20 us on a 350 MHz cpu) compared to + * the timescales required to read a modbus frame over a serial bus + * (aprox. 10 ms for a 10 byte frame on a 9600 baud bus!) + */ +static inline struct timespec timespec_add_curtime(struct timespec ts) { + struct timespec res = {.tv_sec = 0, .tv_nsec = 0}; + + /* is ts = 0 also return 0 !! */ + if ((ts.tv_sec != 0) || (ts.tv_nsec != 0)) + if (clock_gettime(CLOCK_MONOTONIC, &res) >= 0) + res = timespec_add(res, ts); + return res; +} + +/************************************/ +/** **/ +/** select() with absolute timeout **/ +/** **/ +/************************************/ + + + + +/* My private version of select using an absolute timeout, instead of the + * usual relative timeout. + * + * NOTE: Ususal select semantics for (a: end_time == NULL) and + * (b: *end_time == 0) also apply. + * + * (a) Indefinite timeout + * (b) Try once, and and quit if no data available. + */ +/* Returns: -1 on error + * 0 on timeout + * >0 on success + */ +static int my_select(int fd, fd_set *rfds, fd_set *wfds, const struct timespec *end_time) { + + int res; + struct timespec cur_time; + struct timeval timeout, *tv_ptr; + fd_set tmp_rfds, *tmp_rfds_ptr, tmp_wfds, *tmp_wfds_ptr; + + tmp_rfds_ptr = NULL; + tmp_wfds_ptr = NULL; + if (rfds != NULL) tmp_rfds_ptr = &tmp_rfds; + if (wfds != NULL) tmp_wfds_ptr = &tmp_wfds; + + /*============================* + * wait for data availability * + *============================*/ + do { + if (rfds != NULL) tmp_rfds = *rfds; + if (wfds != NULL) tmp_wfds = *wfds; + /* NOTE: To do the timeout correctly we would have to revert to timers + * and asociated signals. That is not very thread friendly, and is + * probably too much of a hassle trying to figure out which signal + * to use. What if we don't have any free signals? + * + * The following solution is not correct, as it includes a race + * condition. The following five lines of code should really + * be atomic! + * + * NOTE: see also the timeout related comment in the + * modbus_tcp_read() function! + */ + if (end_time == NULL) { + tv_ptr = NULL; + } else { + tv_ptr = &timeout; + if ((end_time->tv_sec == 0) && (end_time->tv_nsec == 0)) { + timeout.tv_sec = timeout.tv_usec = 0; + } else { + /* ATOMIC - start */ + if (clock_gettime(CLOCK_MONOTONIC, &cur_time) < 0) + return -1; + timeout = timespec_to_timeval(timespec_dif(*end_time, cur_time)); + } + } + + res = select(fd, tmp_rfds_ptr, tmp_wfds_ptr, NULL, tv_ptr); + /* ATOMIC - end */ + +#ifdef DEBUG + {int i; + if (tmp_rfds_ptr != NULL) + for (i = 0; i < fd; i++) + if (FD_ISSET(i, tmp_rfds_ptr)) + fprintf(stderr,"fd=%d is ready for reading\n", i); + if (tmp_wfds_ptr != NULL) + for (i = 0; i < fd; i++) + if (FD_ISSET(i, tmp_wfds_ptr)) + fprintf(stderr,"fd=%d is ready for writing\n", i); + } +#endif + if (res == 0) { +#ifdef DEBUG + printf("Comms time out\n"); +#endif + return 0; + } + if ((res < 0) && (errno != EINTR)) { + return -1; + } + } while (res <= 0); + + if (rfds != NULL) *rfds = tmp_rfds; + if (wfds != NULL) *wfds = tmp_wfds; + return res; +} + + + + + + +#endif /* __MODBUS_TIME_UTIL_H */ diff -r 000000000000 -r ae252e0fd9b8 mb_types.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 . + * + * This code is made available on the understanding that it will not be + * used in safety-critical situations without a full and competent review. + */ + + + +#ifndef __MB_TYPES_H +#define __MB_TYPES_H + +#ifndef __PLC_TYPES_H + /* if we have already included the MatPLC's type definitions, we don't need to delare the types ourselves... */ + + +/* Tell the stdint.h file we want the limits of the data types defined. */ +/* If being compiled by a C++ compiler, this is required! */ +/* If being compiled by a C compiler, this is ignored! */ +#define __STDC_LIMIT_MACROS +#include + + + +/* 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 */ + diff -r 000000000000 -r ae252e0fd9b8 mb_util.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 . + * + * This code is made available on the understanding that it will not be + * used in safety-critical situations without a full and competent review. + */ + + + +#ifndef MB_UTIL_H +#define MB_UTIL_H + +/* This file has constants related to the modbus protocol */ +/* + * Some of these constants are specific to the layer two protocols + * (i.e. master and slave), while others are specific of the + * layer one protocols (i.e. rtu, ascii, tcp). + * + * a) Unfortunately, due to the nature of the modbus protocol, that does not + * include a frame size field in the layer 1 frame (see note 1), and the + * fact that we are implementing it at the user level, the implementation + * of some layer 1 protocols need to know the content of the layer 2 protocol + * in order to determine the size of the frame. + * + * b) The layer two message formats are in fact the same, just reversing the role + * being played (master or slave). + * + * Bothe a) and b) mean we need the same modbus protocol constants in several files. + * It ends up making more sense to put them all together in a single file, which + * makes updating easier, even though we are trying to strictly seperate the layer 1 + * and layer 2 protocols. + * + * + * + * Notes: + * (1) There is no layer 1 field with the frame size, nevertheless this + * size can be determined indirectly due to timing restrictions on the rtu + * protocol. Unfortunately, due to the fact that we are implementing + * it at the user level, we are not guaranteed to detect these timings + * correctly, and therefore have to rely on layer 2 protocol info to + * determine the frame size. + * For the ascii protocol, the frame size is determined indirectly by + * a frame header and tail, so we do not use layer 2 protocol info. + */ + + + /* Layer 2 Frame Structure... */ + /* Valid for both master and slave protocols */ +#define L2_FRAME_HEADER_LENGTH 6 +#define L2_FRAME_BYTECOUNT_LENGTH 1 +#define L2_FRAME_DATABYTES_LENGTH 255 +#define MAX_L2_FRAME_LENGTH (L2_FRAME_HEADER_LENGTH + L2_FRAME_BYTECOUNT_LENGTH + \ + L2_FRAME_DATABYTES_LENGTH) + +#define L2_FRAME_SLAVEID_OFS 0 +#define L2_FRAME_FUNCTION_OFS 1 + + /* Layer 1 - Ascii Frame sizes... */ +#define L2_TO_ASC_CODING 2 /* number of ascii bytes used to code a Layer 2 frame byte */ +#define ASC_FRAME_HEADER_LENGTH 1 +#define ASC_FRAME_HEADER ':' +#define ASC_FRAME_TAIL_LENGTH 2 +#define ASC_FRAME_TAIL_0 '\13' /* 'CR' */ +#define ASC_FRAME_TAIL_1 '\10' /* 'LF' */ +#define ASC_FRAME_LRC_LENGTH 2 + + /* Layer 1 - RTU Frame sizes... */ +#define RTU_FRAME_CRC_LENGTH 2 + + /* Layer 1 - TCP Frame sizes... */ +#define TCP_HEADER_LENGTH 6 + + /* Global Frame sizes */ +#define MAX_RTU_FRAME_LENGTH MAX_L2_FRAME_LENGTH + RTU_FRAME_CRC_LENGTH +#define MAX_ASC_FRAME_LENGTH ((MAX_L2_FRAME_LENGTH * L2_TO_ASC_CODING) + \ + ASC_FRAME_HEADER_LENGTH + ASC_FRAME_TAIL_LENGTH + \ + ASC_FRAME_LRC_LENGTH) + + +/* Modbus Exception codes */ +#define ERR_ILLEGAL_FUNCTION 0x01 +#define ERR_ILLEGAL_DATA_ADDRESS 0x02 +#define ERR_ILLEGAL_DATA_VALUE 0x03 +#define ERR_SLAVE_DEVICE_FAILURE 0x04 +#define ERR_ACKNOWLEDGE 0x05 +#define ERR_SLAVE_DEVICE_BUSY 0x06 +#define ERR_NEGATIVE_ACKNOWLEDGE 0x07 +#define ERR_MEMORY_PARITY_ERROR 0x08 +#define ERR_GATEWAY_PATH_UNAVAILABLE 0x0A +#define ERR_GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND 0x0B + + + +#endif /* MB_UTIL_H */ + + + + + + + + diff -r 000000000000 -r ae252e0fd9b8 sin_util.c --- /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 . + * + * This code is made available on the understanding that it will not be + * used in safety-critical situations without a full and competent review. + */ + + +/* sin_util.c */ + +#include "sin_util.h" + +#include +#include +#include +#include // gethostbyname(), ... +#include // errno +#include // strtoll() +#include // isspace() +#include // memcpy(), memset() +#include // strcasecmp() +#include // perror() + +#ifndef INADDR_NONE +#define INADDR_NONE 0xffffffff +#endif + +/* Last time I (msousa) checked, this was not being defined in QNX */ +#ifndef socklen_t +typedef unsigned int socklen_t; +#endif + + +static inline int str_is_whitespace(const char *str) { + const char *s; + for (s = str; *s; s++) if (!isspace(*s)) return 0; // non whitespace char found + return 1; // all whitespace, or empty "" +} + +/* Convert a string to a number, allowing for leading and trailing whitespace */ +/* Number may be in decimal, hexadecimal (leading '0x') or octal (leading '0') format */ +static long long str_to_portnum(const char *str) { + long long port = 0; + char *errstr; + #define PORT_MIN 0 + #define PORT_MAX 65535 + errno = 0; // strtoll() does not set errno to 0 on success!! + port = strtoll(str, &errstr, 0 /* accept base 8, 10 and 16 */); + if (str == errstr) return -1; // not a number + if (errno == ERANGE) return -2; // out of range + if (!str_is_whitespace(errstr)) return -1; // not a number (has trailing characters) + if ((port < PORT_MIN) || (port > PORT_MAX)) return -2; // out of range + return (port); +} + + + +static int gettypebyname(const char *protocol) { + if (strcasecmp(protocol, "tcp") == 0) return SOCK_STREAM; + if (strcasecmp(protocol, "udp") == 0) return SOCK_DGRAM; + if (strcasecmp(protocol, "raw") == 0) return SOCK_RAW; + return SOCK_PACKET; // a deprecated service type, we use here as error. +} + + +static int getportbyname(in_port_t *port, const char *service, const char *protocol) { + struct servent *se; + int32_t tmp; + // if service is NULL, "" or string of whitespace, then set port to 0, and return -1. + // Used when binding to a random port on the local host... + if ( port == NULL) { return -1;} + if ( service == NULL) {*port = 0; return -1;} + if (str_is_whitespace(service)) {*port = 0; return -1;} + if ((se = getservbyname(service, protocol)) != NULL) {*port = se->s_port; return 0;} + if ((tmp = str_to_portnum(service)) >= 0) {*port = htons((uint16_t)tmp); return 0;} + return -2; +} + + +static int getipbyname(struct in_addr *ip_addr, const char *host) { + struct hostent *he; + // if host is NULL, "", or "*", then set ip_addr to INADDR_ANY, and return -1. + // Used when binding to all interfaces on the local host... + if ( host == NULL) {ip_addr->s_addr = INADDR_ANY; return -1;} + if (str_is_whitespace(host)) {ip_addr->s_addr = INADDR_ANY; return -1;} + if (strcmp(host, "*") == 0) {ip_addr->s_addr = INADDR_ANY; return -1;} + if ((he = gethostbyname(host)) != NULL) {memcpy((char *)ip_addr, he->h_addr, he->h_length); return 0;} + if ((ip_addr->s_addr = inet_addr(host)) != INADDR_NONE) {return 0;} + return -2; +} + + + + + +int sin_initaddr(struct sockaddr_in *sin, + const char *host, int allow_null_host, // 1 => allow host NULL, "" or "*" -> INADDR_ANY + const char *service, int allow_null_serv, // 1 => allow serivce NULL or "" -> port = 0 + const char *protocol) { + int he = allow_null_host?-1:0; + int se = allow_null_serv?-1:0; + + memset((void *)sin, 0, sizeof(sin)); + sin->sin_family = AF_INET; + + if (getportbyname(&(sin->sin_port), service, protocol) < se) return -1; + if (getipbyname (&(sin->sin_addr), host) < he) return -1; + return 0; +} + + + +/* Create a socket for the IP protocol family, and connect to remote host. */ +int sin_connsock(const char *host, const char *service, const char *protocol) { + struct sockaddr_in sin; + int s, type; + + if (sin_initaddr(&sin, host, 0, service, 0, protocol) < 0) return -1; + if ((type = gettypebyname(protocol)) == SOCK_PACKET) return -1; + /* create the socket */ + if ((s = socket(PF_INET, type, 0)) < 0) {perror("socket()"); return -1;} + /* connect the socket */ + if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) {perror("connect()"); return -1;} + + return(s); +} + + + + +/* Create a socket for the IP protocol family, and bind to local host's port. */ +int sin_bindsock(const char *host, const char *service, const char *protocol) { + struct sockaddr_in sin; + int s, type; + + // TODO allow random port... Needs new input parameter to function interface! + if (sin_initaddr(&sin, host, 1, service, 0, protocol) < 0) return -1; + if ((type = gettypebyname(protocol)) == SOCK_PACKET) return -1; + /* create the socket */ + if ((s = socket(PF_INET, type, 0)) < 0) {perror("socket()"); return -1;} + /* bind the socket */ + if (bind(s, (struct sockaddr *)&sin, sizeof (sin)) < 0) {perror("bind()"); return -1;} + + return(s); +} + + + + diff -r 000000000000 -r ae252e0fd9b8 sin_util.h --- /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 . + * + * This code is made available on the understanding that it will not be + * used in safety-critical situations without a full and competent review. + */ + + +#ifndef SIN_UTIL_H +#define SIN_UTIL_H + + +#include +#include +#include +#include + + + +/* Create a socket for the IP protocol family, and connect to remote host. */ +int sin_connsock(const char *host, const char *service, const char *protocol); + +/* Create a socket for the IP protocol family, and bind to local host's port. */ +int sin_bindsock(const char *host, const char *service, const char *protocol); + +/* Initialize a in_addr structure */ +int sin_initaddr(struct sockaddr_in *sin, + const char *host, int allow_null_host, // 1 => allow host NULL, "" or "*" -> INADDR_ANY + const char *service, int allow_null_serv, // 1 => allow serivce NULL or "" -> port = 0 + const char *protocol); +#endif