pax_global_header00006660000000000000000000000064144406534100014513gustar00rootroot0000000000000052 comment=0b9d244cf5965823ab3155ed5ea137df0745afa5 solarpowerlog-solarpowerlog-0.26/000077500000000000000000000000001444065341000172365ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/.gitignore000066400000000000000000000011001444065341000212160ustar00rootroot00000000000000Debug/ .cproject .deps/ .project .settings/ Makefile.in config.log config.status src/Makefile Makefile src/config.h src/stamp-h1 autom4te.cache/* documentation/*.html *.o *.exe *~ aclocal.m4 config/* configure src/config.h.in src/solarpowerlog *.a libtool m4/libtool.m4 m4/lt~obsolete.m4 m4/ltoptions.m4 m4/ltsugar.m4 m4/ltversion.m4 extlibs/dbixx/autom4te.cache/ extlibs/dbixx/config.guess extlibs/dbixx/config.sub extlibs/dbixx/depcomp extlibs/dbixx/install-sh extlibs/dbixx/libtool extlibs/dbixx/ltmain.sh extlibs/dbixx/missing documentation/doxygen/html INSTALL .dirstamp solarpowerlog-solarpowerlog-0.26/.travis.yml000066400000000000000000000011031444065341000213420ustar00rootroot00000000000000# Travis CI Integration sudo: required language: c++ compiler: - gcc # - clang <<< does not like the LOG_SA_HASH() script: ./bootstrap.sh && ./configure && make -j2 before_install: # - curl https://ftp-master.debian.org/keys/archive-key-7.0.asc | sudo apt-key add - # - sudo add-apt-repository -y "deb http://ftp.de.debian.org/debian/ sid main contrib non-free" - sudo add-apt-repository -y "deb http://archive.ubuntu.com/ubuntu/ vivid main universe" - sudo apt-get update -qq - sudo apt-get -qq build-dep solarpowerlog - sudo apt-get install -qq libcppdb-dev solarpowerlog-solarpowerlog-0.26/AUTHORS000066400000000000000000000002631444065341000203070ustar00rootroot00000000000000solarpowerlog is written by Tobias Frost List of Contributors: E.A.Neonakis Markus Amsler solarpowerlog-solarpowerlog-0.26/COPYING000066400000000000000000000032751444065341000203000ustar00rootroot00000000000000Solarpowerlog is free software. The individual source files containes a statement which license is to be applied, but here is a general guideline: Files not marked (build system, examples, tools ...) are to be considered as GPL-3 (or later) licensed. Every source file (*.cpp and *.c ) is licensed using the GPL-3 (or later): 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 . Every interface file (header files, *.h)) is using the LGPL 3 (or later): This program 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 program. If not, see . solarpowerlog-solarpowerlog-0.26/COPYRIGHT000066400000000000000000000002701444065341000205300ustar00rootroot00000000000000COPYRIGHT ========== Unless otherwise stated: All files are copyrighted 2009 by the AUTHORS. See the file AUTHORS for details. For the type of license, please see the file COPYING solarpowerlog-solarpowerlog-0.26/ChangeLog000066400000000000000000000005601444065341000210110ustar00rootroot000000000000002011-12-24 Tobias Frost * *: The ChangeLog is auto-generated when releasing. If you are seeing this, use 'git log' for a detailed list of changes. You can generate the ChangeLog by "make dist" when the git repository is present. For a condensed changelog, please see debian/changelog in the debian branch of the repository. solarpowerlog-solarpowerlog-0.26/GPL000066400000000000000000001045131444065341000176070ustar00rootroot00000000000000 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 . solarpowerlog-solarpowerlog-0.26/GPL-3000066400000000000000000001045131444065341000177470ustar00rootroot00000000000000 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 . solarpowerlog-solarpowerlog-0.26/LGPL-3000066400000000000000000000167431444065341000200720ustar00rootroot00000000000000 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. solarpowerlog-solarpowerlog-0.26/Makefile.am000066400000000000000000000015751444065341000213020ustar00rootroot00000000000000## Makefile.am -- Process this file with automake to produce Makefile.in ACLOCAL_AMFLAGS = -I m4 SUBDIRS=src EXTRA_DIST = example_confs bootstrap.sh tools/solarpowerlog.init tools/solarpowerlog.default tools/solarpowerlog.logrotate src/ctemplate/README # install some extras into the dist files... dist_doc_DATA = AUTHORS NEWS README GPL GPL-3 LGPL-3 PLAN COPYRIGHT dist_man_MANS = solarpowerlog.1 dist-hook: @if test -d "$(srcdir)/.git"; \ then \ echo Creating ChangeLog && \ ( cd "$(top_srcdir)" && \ echo '# Generated by Makefile. Do not edit.'; echo; \ echo '# Repository Information: (git describe)'; git describe; echo; \ config/missing --run git2cl) > ChangeLog.tmp \ && mv -f ChangeLog.tmp $(distdir)/ChangeLog \ || ( rm -f ChangeLog.tmp ; \ echo Failed to generate ChangeLog >&2 ); \ else \ echo A git clone is required to generate a ChangeLog >&2; \ fi solarpowerlog-solarpowerlog-0.26/NEWS000066400000000000000000000001621444065341000177340ustar00rootroot00000000000000News in Version 0.25 - New Filter "DBWriter" that can log to a database. see the examples for how to use it. solarpowerlog-solarpowerlog-0.26/PLAN000066400000000000000000000103411444065341000177120ustar00rootroot00000000000000solarpowerlog This file is some random thought collection and gives some ideas what direction solarpowerlog goes... ROADMAP Next Version: (which will probably be version 0.24) -> Rework of the Sputnik Inverters (complete) Current version had basically copy-paste code for every command and was ugly to maintain. The new version encapsulates all commands that the Sputnik inverter undertands in a set of templated and special classes. As a side-effect adding new commands is now (usually) child-play. -> Sputnik Simulator integrated in solarpowerlog (complete) The "existing" netcat based inverter was too static, and therefore, based on the CSputnikInverter a simulator has been embedded. After compiling with simulator support and configurating it, it will listen e.g TCP/IP and answer queries from e.g. another instance of solarpowerlog (or other programms that support the Sputnik Inverters) Additionally, it will provide a control server, so the answers to the queries can be tweaked (values changed, commands turned off/on) -> Improved SIGTERM Handling (complete) Current versions took some time from SIGTERM to actual termination due to the internal handling of events (WorkScheduler had to finish a work before checking the status.) Worst-case syvinit sent SIGKILL before we handled SIGTERM and this could lead that some buffers are not flushed. The new handling inserts a "Work" when SIGTERM is received and therefore actively informing the inverters/datafilters of the imminent termination, so that appropiate actions can be performded. -> SharedComms (complete) SharedComms are still experimental and there are several ugly race-condtions, especially when receiving data from the inverter. The problem is that in the time betwen the first read request is completed and the time until the inverter will request the next one bytes can get lost. Solution is to introduce some caching, so the SharedComms will always read, even without an read request and buffer until the inverter actually requests to read. -> Comms/Inverter interaction, configuration. (complete) -> The inverters should specify timeouts, not the communication objects This allows more fine-grade settings, in respect to context. ======================================= Scheduled for next+1 versions: -> Documentation update solarpowerlog needs some better documentation. The current docs are quite outdated and incompete. -> Development documents update The same for the dev docs -> Docs are not well maintainable They are maintained "out of the source" and this is hard to keep in sync. So bring the docs into the source tree and auto-generate docs at buildtime ======================================= open, but no release goal yet: -> Sputnik Inverter -> Make the "event log" available, (EC* commands.) -> Support setting the date/time of the inverters -> CVS Inverters -> Specify the time between flushes, not only "flush every time" -> Configuration System -> Allow all classes to cache config info and cache them locally in member variables (as dynamic reconfiguration has been dropped as design goal) This should reduce complexity (lower CPU at the cost of memory, but will increase readability of the code) -> SQL datafilter -> JSON/XML output -> Communication: The cases where callbacks are allowed to be NULL are now gone Check if deletion of the commands in the task of the ASIO based classes still needs to be done or if this can be removed. -> Complete the HTML Writer to allow plotting more than one inverter. ----------------------------------------------- random ideas.... - Rework the Capability interface, maybe a helper class that will update the variables by callbacks or by direct accessing members: This is a common coding for all filters, so having code reuse will might this easier and better - witty-application or XML-Request interface (to make some ajaxiation ;-) - "End of Day" Filter -- for just one dataset reflecting the whole day. - Adding some other inverters - additional filters, for monthly, yearly reports, comparisions of inverters, string) - mail plugin to report errors (or a daily mail) - reporting not only to mail, but also to *** (e.g. twitter) - solar weather forecast (there was a post debian-mentors) solarpowerlog-solarpowerlog-0.26/README000066400000000000000000000060501444065341000201170ustar00rootroot00000000000000WHAT IS SOLARPOWERLOG? ====================== The program's purpose is to track and log data from photo-voltaic inverters, collect power data and store them. Also a purpose is to provide an interface to extract the data, allowing applications like web site stats of the system. Currently, solarpowerlog is working with the Sputnik Solarmax inverter, but the framework supports any kind of inverters and even other energy generation systems. (Some coding required -- all it needs is to create the inverter class) Solarpowerlog is mature software and (for my purposes) feature-complete. As with the nature of FOSS, feel free to contribute new features. The preferrded method is pull requests on github: https://github.com/coldtobi/solarpowerlog HELP WANTED =========== Any idea? Some programming skills? Missing a feature? Some spare time? You're at the right place! Any contribution is very welcome. Don't forget, this is the spirit of open source! Especially this fields needs support: - Write some user documentation. - Support for other inverters beside the Sputnik ones (to test the code, you should have access to one) - Programming for some tools making use of the data. - Documentation (manuals, code documentation, examples, ....) - and bug crushing. (not that I am aware of bugs, but I'm sure they are hiding) INSTALLATION ============ Before compiling yourself, solarpowerlog is available in Debian and derivates, like Ubuntu. Look for the package "solarpowerlog". GETTING THE SOURCE ================== You either need to grab the sources as tarball from http://sourceforge.net/projects/solarpowerlog/files/solarpowerlog/ or you can use the git repository to get the latest source. The repository is located at: https://github.com/coldtobi/solarpowerlog.git and the command git clone https://github.com/coldtobi/solarpowerlog.git should get you a copy. solarpowerlog has currently those main branches: trunk -- development trunk debian -- repository to store debian packaging files upstream/x.yy -- snapshot for the releases (starting at 0.21) (master -- this was an ancient branch for the last release -- please do not use it) I recommend using the repository instead the relased tarballs. If you want to contribue, please make sure that you work on trunk. BUILDING ======== solarpowerlog uses autotools. To build solarpowerlog, just use the usual ./bootstrap.sh ./configure make for a guide, please see the file INSTALL. (it will be created by ./bootstrap) Build-dependencies: =================== To build under debian, you need to have the following packages installed: Minimal: autoconf libtool libconfig++-dev libboost-dev libboost-system-dev libboost-thread-dev libbost-program-options-dev libdbi-dev Full-fledged (additional) liblog4cxx-dev libcppdb-dev Note: On Debian systems, apt-get build-dep solarpowerlog will get you started. CONFIGURATION FILE ================== Please see the directory example_confs for examples how to configure solarpowerlog. You can specify the config file to be used with the -c parameter: solarpowerlog -c solarpowerlog-solarpowerlog-0.26/bootstrap.sh000077500000000000000000000011561444065341000216150ustar00rootroot00000000000000#!/bin/sh autoreconf --force --install --warnings=all exit 0 # generate everything for the autotools... # old version, kept for the moment. rm -rf aclocal.m4 >/dev/null 2>&1 mkdir config >/dev/null 2>&1 libtoolize && autoheader -B src && aclocal -I m4 && autoconf && automake --add-missing # generate for the extlibs under autoconf's regime rm -rf extlibs/dbixx/.svn/ extlibs/dbixx/autom4te.cache/ extlibs/dbixx/config.guess extlibs/dbixx/config.sub extlibs/dbixx/depcomp extlibs/dbixx/install-sh extlibs/dbixx/ltmain.sh extlibs/dbixx/missing [ -e extlibs/dbixx ] && (cd extlibs/dbixx && ./autogen.sh) || true solarpowerlog-solarpowerlog-0.26/configure.ac000066400000000000000000000271241444065341000215320ustar00rootroot00000000000000dnl Process this file with autoconf to produce a configure script. AC_INIT([solarpowerlog],[0.26],[tobi@coldtobi.de]) AC_PREREQ([2.68]) # use own directory for the autotools stuff AC_CONFIG_AUX_DIR(config) AC_CONFIG_MACRO_DIR([m4]) LT_INIT CPPFLAGS+=" -Wall -Wextra" # we want this header to be created. AC_CONFIG_HEADERS(src/config.h) # TODO #AC_COPYRIGHT (copyright-notice ) AM_INIT_AUTOMAKE([1.9 dist-xz no-dist-gzip dist-zip tar-pax subdir-objects]) AC_CONFIG_SRCDIR([src/solarpowerlog.cpp]) # configure extlibs ### options for some features that might not always be needed ### (for shrinking the footprint) ## option for disable logging (default: on) AC_ARG_ENABLE([logging], [AS_HELP_STRING([--disable-logging], [Disable logging facility. \ Error output is limited to stdout/err and not configureable.]) ] ) ## option for disable command-line options support ## as this pulls in a boost library AC_ARG_ENABLE([commandlineoptions], [AS_HELP_STRING([--disable-commandlineoptions], [Disable the ability to specify \ command-line options. Instead, the program will always use defaults \ (that is e.g reading solarpowerlog.conf from the current dir). Avoids using \ some external libraries.]) ] ) if test "x$enable_commandlineoptions" != "xno" ; then enable_commandlineoptions=yes else AC_MSG_NOTICE([Support for commandlineoptions disabled as requested.]) enable_commandlineoptions=no fi ### option to disable some modules. Default is to compile everything. ## Inverters # Sputnik support. AC_ARG_ENABLE([sputnik], [AS_HELP_STRING([--disable-sputnik], [Do not support inverters by Sputnik engineering.]) ] ) if test "x$enable_sputnik" != "xno" ; then AC_DEFINE([HAVE_INV_SPUTNIK], [1], [Sputnik inverter support]) enable_sputnik=yes else AC_MSG_NOTICE([Support for Sputnik inverters disabled as requested.]) fi # Sputnik Simulator support. AC_ARG_ENABLE([sputniksimulator], [AS_HELP_STRING([--disable-sputniksimulator], [Simulates a Sputnik engineering inverter.]) ] ) if test "x$enable_sputniksimulator" != "xno" ; then AC_DEFINE([HAVE_INV_SPUTNIKSIMULATOR], [1], [Sputnik simulator]) enable_sputniksimulator=yes else AC_MSG_NOTICE([Support for sputnik invertor simulation disabled as requested.]) enable_sputniksimulator=no fi # Dummy Inverter AC_ARG_ENABLE([dummyinverter], [AS_HELP_STRING([--enable-dummyinverter], [Build the dummy inverter (development example).]) ] ) if test "x$enable_dummyinverter" = "xyes" ; then AC_DEFINE([HAVE_INV_DUMMY], [1], [Dummy inverter support]) enable_dummyinverter=yes else AC_MSG_NOTICE([Dummy inverter support disabled (default).]) enable_dummyinverter=no fi ## Filters / Dumpers # CSV Writer AC_ARG_ENABLE([csvlogger], [AS_HELP_STRING([--disable-csvlogger], [Do not support logging to CSV files.]) ] ) if test "x$enable_csvlogger" != "xno" ; then AC_DEFINE([HAVE_FILTER_CSVDUMP], [1], [CSV Writer Support]) enable_csvlogger=yes else AC_MSG_NOTICE([Support for CSV Writer disabled as requested.]) fi # Dumb Dumper AC_ARG_ENABLE([dumbdumper], [AS_HELP_STRING([--disable-dumbdumper], [Do not support the "dumb dumper" logging filter.]) ] ) if test "x$enable_dumbdumper" != "xno" ; then AC_DEFINE([HAVE_FILTER_DUMBDUMP], [1], [Dumb Dumper Support]) enable_dumbdumper=yes else AC_MSG_NOTICE([Support for the Dumb Dumper disabled as requested.]) fi # CSV Writer AC_ARG_ENABLE([csvlogger], [AS_HELP_STRING([--disable-csvlogger], [Do not support logging to CSV files.]) ] ) if test "x$enable_csvlogger" != "xno" ; then AC_DEFINE([HAVE_FILTER_CSVDUMP], [1], [CSV Writer Support]) enable_csvlogger=yes else AC_MSG_NOTICE([Support for CSV Writer disabled as requested.]) fi # HTML Writer AC_ARG_ENABLE([htmlwriter], [AS_HELP_STRING([--disable-htmlwriter], [Do not support the html writer module for writing static HTML files.]) ] ) if test "x$enable_htmlwriter" != "xno" ; then AC_DEFINE([HAVE_FILTER_HTMLWRITER], [1], [HTML Writer Support]) enable_htmlwriter=yes else AC_MSG_NOTICE([Support for the HTMLWriter disabled as requested.]) fi # Database Writer AC_ARG_ENABLE([dbwriter], [AS_HELP_STRING([--disable-dbwriter], [Do not support the database writer module to push results to a database.]) ] ) if test "x$enable_dbwriter" != "xno" ; then enable_dbwriter=yes else AC_MSG_NOTICE([Support for the DBWriter disabled as requested.]) fi ## Communication Methods # Boost::asio::tcpip connection AC_ARG_ENABLE([tcpcomms], [AS_HELP_STRING([--disable-tcpcomms], [Do not support TCP/IP connections to the inverter.]) ] ) if test "x$enable_tcpcomms" != "xno" ; then AC_DEFINE([HAVE_COMMS_ASIOTCPIO], [1], [TCP/IP communication support]) enable_tcpcomms=yes else AC_MSG_NOTICE([Support for TCP/IP Communications disabled as requested.]) fi # Boost::asio::serial_port connection AC_ARG_ENABLE([serialcomms], [AS_HELP_STRING([--disable-serialcomms], ["Do not support serial port -- like RS232 -- connections to the inverter."]) ] ) if test "x$enable_serialcomms" != "xno" ; then AC_DEFINE([HAVE_COMMS_ASIOSERIAL], [1], [Serial port communication support]) enable_serialcomms=yes else AC_MSG_NOTICE([Support for serial port communications disabled as requested.]) fi # Shared Connection provider AC_ARG_ENABLE([sharedcomms], [AS_HELP_STRING([--disable-sharedcomms], ["Support sharing a communication object between two or more inverters. (experimental)"]) ] ) if test "x$enable_sharedcomms" != "xno" ; then AC_DEFINE([HAVE_COMMS_SHAREDCONNECTION], [1], [Shared communication support]) enable_sharedcomms=yes else AC_MSG_NOTICE([Support for shared communications disabled as requested.]) enable_sharedcomms=no fi ### Checks for programs. # C Compiler AC_PROG_CC # C++ Compiler AC_PROG_CXX AM_PROG_CC_C_O # pkg-config PKG_CHECK_EXISTS # We only use C++... AC_LANG(C++) ### Checks for header files. AC_CHECK_HEADER([w32api/wtypes.h], [AC_DEFINE([HAVE_WIN32_API], [1], [Windows API]) ]) if test "x$ac_cv_header_w32api_wtypes_h" = "xyes"; then AC_MSG_RESULT([WIN32 API detected. ]) WIN32_LIBS="-lws2_32" AC_SUBST(WIN32_LIBS) fi ### Checks for libraries. ## Check for libconfig. Always required. PKG_CHECK_MODULES(CONFIG, libconfig++ >= 1.3.1) ## Check for library liblog4cxx. Can be turned off with --disable-logging. if test "x$enable_logging" != "xno" ; then logging_dependencies_fullfilled="missing" PKG_CHECK_MODULES(LOG4CXX, liblog4cxx >= 0.10.0, , AC_MSG_RESULT([LibLog4CXX not found])) if test "x$LOG4CXX_PKG_ERRORS" != "x"; then logging_dependencies_fullfilled="missing liblog4cxx " fi # libapr / aprutils only for win32 build needed, dont ask me why if test "x$ac_cv_header_w32api_wtypes_h" = "xyes"; then PKG_CHECK_MODULES(APR, apr-1 > 1, , AC_MSG_RESULT([apache portable runtime apr library not found])) if test "x$APR_PKG_ERRORS" != "x"; then logging_dependencies_fullfilled="$logging_dependencies_fullfilled libapr " fi PKG_CHECK_MODULES(APRUTIL, apr-util-1 > 1, , AC_MSG_RESULT([apr utilities library not found])) if test "x$APRUTIL_PKG_ERRORS" != "x"; then logging_dependencies_fullfilled="$logging_dependencies_fullfilled libaprutil " fi fi if test "$logging_dependencies_fullfilled" != "missing" ; then AC_MSG_NOTICE([liblog4cxx: Dependecy problem(s): $logging_dependencies_fullfilled]) enable_logging="no (dependency problem(s): $logging_dependencies_fullfilled)" APR_LIBS="" APRUTIL_LIBS="" LOG4CXX_LIBS="" else AC_DEFINE([HAVE_LIBLOG4CXX], [1], [Logging Library]) enable_logging="yes" fi else AC_MSG_NOTICE([Logging disabled as requested.]) fi ## Check for Boost BOOST_REQUIRE([1.48]) # Add the option to statically link to the boost libraries BOOST_STATIC() #Check for ASIO (header only file) BOOST_ASIO() #Check for boost libraries BOOST_SYSTEM() BOOST_THREADS() #BOOST_DATE_TIME() if test "x$enable_commandlineoptions" = "xyes"; then BOOST_PROGRAM_OPTIONS if test "x$BOOST_PROGRAM_OPTIONS_LIBS" = "x"; then AC_MSG_NOTICE([boost::program_options library $BOOST_PROGRAM_OPTIONS_LIBS not found ]) enable_commandlineoptions="no (dependency problem)" else AC_DEFINE([HAVE_CMDLINEOPTS], [1], [Use Boost for parsing command line options.]) fi fi ## cppdb library (only needed if dbwriter has been enabled) if test "x$enable_dbwriter" == "xyes" ; then dbwriter_dependencies_fulfilled="missing" AC_MSG_CHECKING(cppdb database access library) AC_LANG(C++) SAVED_LIBS=$LIBS LIBS="$LIBS -lcppdb" AC_LINK_IFELSE( [AC_LANG_PROGRAM([#include ], [cppdb::session sql("")])], [CPPDB_LIBS="$CPPDB_LIBS -lcppdb"] [AC_DEFINE(HAVE_CPPDB,1,[Did we find the cppdb library?])] [AC_MSG_RESULT(yes)], [AC_MSG_RESULT(no)] [AC_MSG_NOTICE(Missing depdency for dbwriter: cppdb)] [dbwriter_dependencies_fulfilled="$dbwriter_dependencies_fulfilled cppdb"] ) LIBS=$SAVED_LIBS if test "x$CPPDB_LIBS" != "x" ; then AC_DEFINE([HAVE_FILTER_DBWRITER], [1], [DB Writer Support]) AC_SUBST(CPPDB_LIBS) else enable_dbwriter="no (dependency problem: $dbwriter_dependencies_fulfilled)" fi fi ### Checks for typedefs, structures, and compiler characteristics. AC_HEADER_STDBOOL AC_HEADER_TIME AC_HEADER_ASSERT AC_TYPE_SIZE_T ### Checks for functions. AC_CHECK_FUNCS([socket]) AC_CHECK_FUNCS([memset]) AC_CHECK_FUNCS([modf]) AC_CHECK_FUNCS([strdup]) AC_CHECK_FUNCS([strncasecmp]) AC_CHECK_FUNCS([strrchr]) AC_CHECK_FUNCS([strtol]) AC_CHECK_FUNCS([strtoul]) AC_FUNC_FORK AC_FUNC_MALLOC ### Check for types AC_TYPE_INT32_T AC_TYPE_UINT32_T AC_TYPE_PID_T ### Check for language features AC_C_INLINE ### Checks for headers AC_CHECK_HEADERS([fcntl.h]) AC_CHECK_HEADERS([stddef.h]) AC_CHECK_HEADERS([syslog.h]) ## check for backtrace_symbols_fd() AC_CHECK_FUNC([backtrace_symbols_fd], AC_DEFINE([HAVE_BACKTRACE_SYMBOLS_FD], 1, [1 if backtraces are possible]) ) # Cygwin has no open_memstream, at least not in the current stable version. AC_CHECK_FUNC(open_memstream, AC_DEFINE(HAVE_OPEN_MEMSTREAM, 1, [1 if open_memstream is available])) # Lets check if we have a syslog here, as we want to prepare it if we run in background. AC_CHECK_FUNC(openlog, [have_openlog=yes], [have_openlog=no]) if test "$have_openlog" = "yes" ;then AC_DEFINE([HAVE_OPENLOG], [1], [Are the syslog-calls available on this system]) fi AC_PROG_INSTALL AC_CONFIG_FILES( [Makefile] [src/Makefile] ) AC_OUTPUT AC_MSG_NOTICE([]); AC_MSG_NOTICE([CONFIGURATION SUMMARY:]); ### Print summary AC_MSG_NOTICE([Logging support: ........................... $enable_logging]) if test "$have_openlog" = "no"; then AC_MSG_NOTICE([ No support for syslog detected]) fi AC_MSG_NOTICE([Commandline options: ....................... $enable_commandlineoptions]) AC_MSG_NOTICE([INVERTERS:]); AC_MSG_NOTICE([Sputnik inverter support: .................. $enable_sputnik]) AC_MSG_NOTICE([Sputnik simulator: ......................... $enable_sputniksimulator]) AC_MSG_NOTICE([Dummy inverter: ............................ $enable_dummyinverter]) AC_MSG_NOTICE([COMMUNICATIONS:]); AC_MSG_NOTICE([TCP/IP: .................................... $enable_tcpcomms]) AC_MSG_NOTICE([Serial Ports: .............................. $enable_serialcomms]) AC_MSG_NOTICE([Shared Communication: ...................... $enable_sharedcomms]) AC_MSG_NOTICE([FILTERS AND LOGGERS:]); AC_MSG_NOTICE([CSV logger support: ........................ $enable_csvlogger]) AC_MSG_NOTICE([Dumb Dumper support: ....................... $enable_dumbdumper]) AC_MSG_NOTICE([HTML Writer support: ....................... $enable_htmlwriter]) AC_MSG_NOTICE([DB Writer support: ......................... $enable_dbwriter]) solarpowerlog-solarpowerlog-0.26/example_confs/000077500000000000000000000000001444065341000220615ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/example_confs/dbwriter/000077500000000000000000000000001444065341000237035ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/example_confs/dbwriter/solarpowerlog-custom.conf000066400000000000000000000020621444065341000307610ustar00rootroot00000000000000# This snippet explains all options for the custom database backend. # solarpowerlog uses CppDB as its database abstraction library. # To get additional hints about configuration options, # see http://cppcms.com/sql/cppdb/backendref.html # and http://cppcms.com/sql/cppdb/connstr.html. # NOTE: This snippet is designed to be included in the logger section. # Example use: /* logger: { loggers = ( { # We want to have a DBWriter: type = "DBWriter"; # This DB Writer is known as (required) name = "DBWriter_tst"; # It is of type # And gets its data from datasource = "Simulator"; @include "solarpowerlog-odbc.conf" (...) */ # Ok here we go... ### Custom DB Backend Bridge # This backend is special, because you will completely need to provide # the CppDB connection string. # http://cppcms.com/sql/cppdb/connstr.html db_type="custom"; # Example: db_cppdb_options="mysql:database=test;user=joe;password='d''eep secret'"# solarpowerlog-solarpowerlog-0.26/example_confs/dbwriter/solarpowerlog-mysql.conf000066400000000000000000000030021444065341000306070ustar00rootroot00000000000000# This snippet explains all options for the MySQL database backend. # solarpowerlog uses CppDB as its database abstraction library. # To get additional hints about configuration options, # see http://cppcms.com/sql/cppdb/backendref.html # and http://cppcms.com/sql/cppdb/connstr.html. # NOTE: This snippet is designed to be included in the logger section. # Example use: /* logger: { loggers = ( { # We want to have a DBWriter: type = "DBWriter"; # This DB Writer is known as (required) name = "DBWriter_tst"; # It is of type # And gets its data from datasource = "Simulator"; @include "solarpowerlog-mysql.conf" (...) */ # Ok here we go... ### MySQL ### # CppDB backend documentation: http://cppcms.com/sql/cppdb/mysql.html db_type="mysql"; # user to connect db_user="dbuser"; # password to connect to the server db_password="very\'secret"; # database to be used db_database="solarpowerlog"; # hostname of the server (default is localhost) #db_host="localhost" # Port to access the server # NOTE: you need to specify either #db_port="3306"; # Alternatively, specify the unix socket to be used # note: specify either db_host+db_port or db_unixsocket -- not both! db_unixsocket="/var/run/mysqld/mysqld.sock" # Additional parameters (see the CppDB-MySQL backend reference) #db_cppdb_options="opt_read_timeout=10;opt_compress=1"; solarpowerlog-solarpowerlog-0.26/example_confs/dbwriter/solarpowerlog-odbc.conf000066400000000000000000000020111444065341000303500ustar00rootroot00000000000000# This snippet explains all options for the ODBC database backend. # solarpowerlog uses CppDB as its database abstraction library. # To get additional hints about configuration options, # see http://cppcms.com/sql/cppdb/backendref.html # and http://cppcms.com/sql/cppdb/connstr.html. # NOTE: This snippet is designed to be included in the logger section. # Example use: /* logger: { loggers = ( { # We want to have a DBWriter: type = "DBWriter"; # This DB Writer is known as (required) name = "DBWriter_tst"; # It is of type # And gets its data from datasource = "Simulator"; @include "solarpowerlog-odbc.conf" (...) */ # Ok here we go... ### ODBC Bridge # http://cppcms.com/sql/cppdb/odbc.html db_type="odbc"; # You need to specify the connection string with db_cppdb_options: # See CppDB documentation of the ODBC backend. db_cppdb_options="DSN=MySource;UID=myuser;PWD=secret"; solarpowerlog-solarpowerlog-0.26/example_confs/dbwriter/solarpowerlog-postgresql.conf000066400000000000000000000027001444065341000316510ustar00rootroot00000000000000# This snippet explains all options for the PostgreSQL database backend. # solarpowerlog uses CppDB as its database abstraction library. # To get additional hints about configuration options, # see http://cppcms.com/sql/cppdb/backendref.html # and http://cppcms.com/sql/cppdb/connstr.html. # NOTE: This snippet is designed to be included in the logger section. # WARNING: I did not test this snippet -- it shows only the configuration options # I coded into solarpowerlog. Feedback if it works or not is appreciated. # Example use: /* logger: { loggers = ( { # We want to have a DBWriter: type = "DBWriter"; # This DB Writer is known as (required) name = "DBWriter_tst"; # It is of type # And gets its data from datasource = "Simulator"; @include "solarpowerlog-postgresql.conf" (...) */ # Ok here we go... ### PostgreSQL ### # Backend documentation: http://cppcms.com/sql/cppdb/postgresql.html db_type="postgresql"; # hostname of the server (default is localhost) db_host="localhost" # Port to access the server db_port="5432"; # user to connect db_user="dbuser"; # password to connect to the server db_password="very\'secret"; # database to be used db_database="solarpowerlog"; # Additional parameters (see CppDB's postgresql backend reference) #db_cppdb_options="@blob=lo"; solarpowerlog-solarpowerlog-0.26/example_confs/dbwriter/solarpowerlog-sqlite3.conf000066400000000000000000000026621444065341000310410ustar00rootroot00000000000000# This snippet explains all options for the sqlite3 database backend. # solarpowerlog uses CppDB as its database abstraction library. # To get additional hints about configuration options, # see http://cppcms.com/sql/cppdb/backendref.html # and http://cppcms.com/sql/cppdb/connstr.html. # NOTE: This snippet is designed to be included in the logger section. # Example use: /* logger: { loggers = ( { # We want to have a DBWriter: type = "DBWriter"; # This DB Writer is known as (required) name = "DBWriter_tst"; # It is of type # And gets its data from datasource = "Simulator"; @include "solarpowerlog-sqlite3.conf" (...) */ # Ok here we go... ### sqlite3 ### # CppDB documentation for the sqlite3 backend: # http://cppcms.com/sql/cppdb/sqlite3.html # For sqlite3 you need this: db_type="sqlite3" # (REQUIRED) database file db_database="/tmp/solarpowerlog.sqlite3"; # mode to open the database. # "create" (default) create if db does not exist # "readwrite" open db readwrite, but fail if it does not exists before # (the other availbable mode, "readonly" is not useful for solarpowerlog) #db_mode = "create" # Other options permitted by the CppDB library # example: db_cppdb_options="busy_timeout=100@use_prepared=off" # default is empty. #db_cppdb_options="" solarpowerlog-solarpowerlog-0.26/example_confs/dbwriter/solarpowerlog.conf000066400000000000000000000174751444065341000274670ustar00rootroot00000000000000# This in an example describing the configuration options of the DBWriter. # The example does NOT show on how to configure the database backends. # This information is available in the snippets in this directory. # This sample is configured to work together with the simulator example # found in examples_confs/solarpowerlog_simulator # NOTE: This example DOES NOT create the required tables, see # db_create_table below application: { dbglevel = "ALL" }; inverter : { inverters = ( { #TODO replace this minimal config by an include to the examples. name = "Simulator"; dbglevel = "ERROR"; description = "Sample Inverter for the DB Example"; manufacturer = "SPUTNIK_ENGINEERING"; model ="S-Series"; comms = "TCP/IP"; tcpadr = "127.0.0.1"; tcpport = "12345"; tcptimeout = 5000; commadr = 1; } ); }; logger: { loggers = ( { # We want to have a DBWriter: type = "DBWriter"; # This DB Writer is known as (required) name = "DBWriter_tst"; # And gets its data from datasource = "Simulator"; # Get the database configured. # (See there for details) @include "solarpowerlog-sqlite3.conf" ## this list specifies the jobs to be done... # jobs are bundled data sets, which should be logged to # a table db_jobs = ( { # Time between logging to the database. # unit is seconds. # Currently REQUIRED (but might be changed later.) db_logevery=5.0; # (optional) Should we only log if actually *something* changed # If set to "true" the logger will only write to the # database if actual data changed (might reduce db size) # (note: to determine if something has changed only data from # the inverter is considered -- not e.g %TIMESTAMP or like. # this defaults to "false" # db_logchangedonly=false; # (optional) Sparse data are data sets which are only partially # available. # When true, this option allows to log data even if only # a subset of the data is available # If this this is false, solarpowerlog will not log anything # unless it has got all the data. (default) # db_allowsparse=false; # (REQUIRED) The table to be used for this job db_table="mytable"; # Should the table create on startup? # POTENTIALLY DANGEROUS -- can cause data loss. # You have these options: # "no" (or any other setting) # do not create any tables # "YES" # A uppercase YES will create the table if it does # no exists yet: The table is not if it already exists. # (if your SQL supports this) # "YES-WIPE-MY-DATA" # This will DELETE the table before creating it. # "print-sql-statement" (lower case string): Just log the # statement required to create the table as INFO-Level message. # (This is good to give you an hint to create the table yourself) # # NOTE2: The table will not be created directly on startup, # but with some delay, as solarpyesowerlog first needs to figure # out the data-types to be used for each column -- As it cannot # create tables with sparse datasets. # This also means that db_layout must only reference existing # Capabilities and provided by the inverter. # db_create_table="no"; # (REQUIRED) specifies some principle operational mode # (note: selectors are explained later) # for the DB-Writer. Options are: # continuous # like CVS, just logging everything to one big # table. You maybe want a %TIMESTAMP. # $Selektors are not available. # single # for status/single rows -- e.g tables with just one row # showing the actual status. This needs a $selector, # which specifies the condition to select the to-be-updated # row. # cumulative # log day/month/year/hourly data. This something between # continuous/sinlge: # It will update a row instead of adding a new row, but # add new rows if there was no row to be udated. # To select the right row all %modifiers are used as # selectors. To have the possibility to only use a sub-set # of those %-types, only the ones starting with "!" will be # considered as selectors. # You can of course also use static "$" selectors like in # the single mode. # There are dedicated examples showinng these modes! db_operation_mode="continuous"; #db_operation_mode="single"; #db_operation_mode="cumulative"; # Special keywords for data # The DBWriter internally creates some handy data which can be # used to be logged to the database, # Currently the following special keywords are understood: # %TIMESTAMP, %YEAR, %MONTH, %DAY, %HOUR, %MINUTE # When used, they expand to the wallclock value; # (where timestamp expands to the unix timestamp) # Selectors # In the single and cumulative modes, solarpowerlog needs to # update the data and make decisions which rows it needs to # update. # For this the selectors are used. # Selectors can be recognized by a "$" or "!" as the first # character. # "$" is used when a literal should be used as a selector. # For example "$Inv1" selects the literal string "Inv1" # "!" is used when of the "special Keywords" should be used as # the selector. For example "!MONTH" is using the current # month to select the row. # There can be more than one selector -- in this case they all # have to match. # single mode does not honor "!" selectors. # The db_layout list describes the mapping between the data to # logged and the columns of the table. # See the example for the syntax. # Capability/Keyword describes what to log by specifying the # the inverters' capability. # Also you can use the special keywords and (in single and # cumulative mode) the selectors. # The following cumulative example logs only the current power # along with the timestamp. The timestamp goes to the column # "created" and the feeding power to the column "pac" db_layout = ( # [ "Capability/Keyword" , "column" ], [ "%TIMESTAMP" , "created" ], [ "Current Grid Feeding Power", "pac" ] ); } /* # a second jobs would go in the block... , { db_logevery=60.0; db_table="table2"; (...) } */ ) } ); }; solarpowerlog-solarpowerlog-0.26/example_confs/dbwriter/solarpowerlog_cumulative.conf000066400000000000000000000034371444065341000317160ustar00rootroot00000000000000# This example-snippet shows how to use the DB Writer in cumulative mode. # it only focuses on this part, all unrelated options are NOT shown. # this snippet goes into the db_jobs list. # Example 1 /* # The example configures logging for a month overview of the daily produced # electricity. # As an example the database should read after a while # Year | Month | Day | Today_Produced #====================================== # 2014 | 07 | 01 | 10.2 # 2014 | 07 | 02 | 14.3 # 2014 | 07 | 03 | 9.7 # cumulative mode db_operation_mode="cumulative"; db_layout = ( # [ "Capability/Keyword" , "column" ], [ "!YEAR" , "Year" ], [ "!MONTH" , "Month" ], [ "!DAY" , "Day" ], [ "Energy produced today", "Today_Produced" ] ); */ # Example 2 /* # To demonstrate a selector, here's a modified example from above. # In this setup, the table contains data for more than one Inverter, # so solarpowerlog must ensure only to update e.g Inverter1's data. # We want the table to contain this data, for example # Inverter | Year | Month | Day | Today_Produced #================================================== # Inverter1 | 2014 | 07 | 01 | 10.2 # Inverter2 | 2014 | 07 | 01 | 21.1 # Inverter1 | 2014 | 07 | 02 | 14.3 # Inverter2 | 2014 | 07 | 02 | 24.9 # Inverter1 | 2014 | 07 | 03 | 9.7 # Inverter2 | 2014 | 07 | 03 | 18.4 # cumulative mode db_operation_mode="cumulative"; db_layout = ( # [ "Capability/Keyword" , "column" ], [ "$Inverter1" , "Inverter" ], [ "!YEAR" , "Year" ], [ "!MONTH" , "Month" ], [ "!DAY" , "Day" ], [ "Energy produced today", "Today_Produced" ] ); */solarpowerlog-solarpowerlog-0.26/example_confs/dbwriter/solarpowerlog_dbg.conf000066400000000000000000000335311444065341000302720ustar00rootroot00000000000000# This in an example describing the configuration options of the DBWriter. # The example does NOT show on how to configure the database backends. # This information is available in the snippets in this directory. # This sample is configured to work together with the simulator example # found in examples_confs/solarpowerlog_simulator # NOTE: This example DOES NOT create the required tables, see # db_create_table below application: { dbglevel = "TRACE" }; inverter : { inverters = ( { #TODO replace this minimal config by an include to the examples. name = "Simulator"; dbglevel = "INFO"; description = "Sample Inverter for the DB Example"; manufacturer = "SPUTNIK_ENGINEERING"; model ="S-Series"; comms = "TCP/IP"; tcpadr = "127.0.0.1"; tcpport = "12345"; tcptimeout = 5000; commadr = 1; } ); }; logger: { loggers = ( { # We want to have a DBWriter: type = "DBWriter"; # This DB Writer is known as (required) name = "DBWriter_tst"; # And gets its data from datasource = "Simulator"; # Get the database configured. # (See there for details) @include "solarpowerlog-sqlite3.conf" ## this list specifies the jobs to be done... # jobs are bundled data sets, which should be logged to # a table db_jobs = ( { # Time between logging to the database. # unit is seconds. # Currently REQUIRED (but might be changed later.) db_logevery=10.0; # Should we only log if actually *something* changed # If set to "true" the logger will only write to the # database if actual data changed (might reduce db size) # (note: to determine if smth has changed only data from the # inverter is considered -- not e.g %TIMESTAMP or like. # this defaults to "false" db_logchangedonly=true; # Sparse datas are datsets which are only partial available. # When true, this option allows to log data even if only # a subset is available # If this this is false, solarpowerlog will not log anything # unless it has got all the data. (default) db_allowsparse=true; # (REQUIRED) The table to be used for this job db_table="Inverter_data_1"; # Should the table create on startup? # POTENTIALLY DANGEROUS -- can cause data loss. # You have these options: # "no" (or any other setting) # do not create any tables # "YES" # A uppercase YES will create the table if it does # no exists yet: The table is not if it already exists. # (if your SQL supports this) # "YES-WIPE-MY-DATA" # This will DELETE the table before creating it. # "print-sql-statement" (lower case string): Just log the # statement required to create the table as INFO-Level message. # (This is good to give you an hint to create the table yourself) # # NOTE2: The tabdb_create_tablele _laststatementfailedwill not be created directly on startup, # but with some delay, as solarpyesowerlog first needs to figure # out the data-types to be used for each column -- As it cannot # create tables with sparse datasets. # This also means that db_layout must only reference existing # Capabilities and provided by the inverter. db_create_table="YES"; # (REQUIRED) specifies some principle operational mode # (note: selectors are explained later) # for the DB-Writer. Options are: # continous # like CVS, just logging everything to one big # table. You maybe want a %TIMESTAMP. # $Selektors are not available. # single # for status/single rows -- e.g tables with just one row # showing the actual status. This needs a $selector, # which specifies the condition to select the to-be-updated # row. # cumulative # log day/month/year/hourly data. This something between # continous/sinlge: # It will update a row instead of adding a new row, but # add new rows if there was no row to be udated. # To select the right row all %modifiers are used as # selectors. To have the possiblity to only use a sub-set # of those %-types, only the ones starting with "!" will be # considered as selectors. # You can of course also use static "$" selectors like in # the single mode. # There are dedicated examples showinng these modes! db_operation_mode="continuous"; #db_operation_mode="single"; #db_operation_mode="cumulative"; # Special keywords for data # The DBWriter internally creates some handy data which can be # used to be logged to the database, # Currently the following special keywords are understood: # %TIMESTAMP, %YEAR, %MONTH, %DAY_laststatementfailed, %HOUR, %MINUTE # When used, they expand to the wallclock value; # (where timestamp expands to the unix timestamp) # Selectors # In the single and cumulative modes, solarpowerlog needs to # update the data and make decissions which rows it needs to # update. # For this the selectors are used. # Selectors can be recognized by a "$" or "!" as the first # character. # "$" is used when a literal should be used as a selector. # For example "$Inv1" selects the literal string "Inv1" # "!" is used when of the "special Keywords" should be used as # the selector. For example "!MONTH" is using the current # month to select the row. # There can be more than one selector -- in this case they all # have to match. # single mode does not honor "!" selectors. # The db_layout list describes the mapping between the data to # logged and the columns of the table. # See the example for the syntax. # Capability/Keyword describes what to log by specifying the # the inverters' capability. # Also you can use the special keywords and (in single and # cumulative mode) the selectors. # The following cumulative example logs only the curernt power # along with the timestamp. The timestamp goes to the column # "created" and the feeding power to the column "pac" db_layout = ( # [ "Capability/Keyword" , "column" ], [ "%TIMESTAMP" , "created" ], ["Energy produced today (kWh)" , "kdy" ], ["Energy produced this month (kWh)" , "kmt" ], ["Energy produced this yea_laststatementfailedr (kWh)" , "kyr" ], ["Energy produced accumulated all time (kWh)" , "kt0" ], ["Net frequency (Hz)" , "tnf" ], ["Current Grid Feeding Power" , "pac" ], ["relative Power (%)" , "prl" ], ["Inverter Temperature (C)" , "tkk" ], ["AC grid feeding current (A)" , "il1" ], ["DC current in (A)" , "idc" ], ["AC grid voltage (V)" , "ul1" ], ["DC voltage in (V)" , "udc" ] ); } , { # Time between logging to the database. # unit is seconds. # Currently REQUIRED (but might be changed later.) db_logevery=5.0; # Should we only log if actually *something* changed # If set to "true" the logger will only write to the # database if actual data changed (might reduce db size) # (note: to determine if smth has changed only data from the # inverter is considered -- not e.g %TIMESTAMP or like. # this defaults to "false" db_logchangedonly=true; # Sparse datas are datsets which are only partial available. # When true, this option allows to log data even if only # a subset is available # If this this is false, solarpowerlog will not log anything # unless it has got all the data. (default) db_allowsparse=false; # (REQUIRED) The table to be used for this job db_table="Inverter_status_1"; # Should the table create on startup? # POTENTIALLY DANGEROUS -- can cause data loss. # You have these options: # "no" (or any other setting) # do not create any tables # "YES" # A uppercase YES will create the table if it does # no exists yet: The table is not if it already exists. # (if your SQL supports this) # "YES-WIPE-MY-DATA" # This will DELETE the table before creating it. # "print-sql-statement" (lower case string): Just log the # statement required to create the table as INFO-Level message. # (This is good to give you an hint to create the table yourself) # # NOTE2: The tabdb_create_tablele will not be created directly on startup, # but with some delay, as solarpyesowerlog first needs to figure # out the data-types to be used for each column -- As it cannot # create tables with sparse datasets. # This also means that db_layout must only reference existing # Capabilities and provided by the inverter. db_create_table="YES"; # (REQUIRED) specifies some principle operational mode # (note: selectors are explained later) # for the DB-Writer. Options are: # continous # like CVS, just logging everything to one big # table. You maybe want a %TIMESTAMP. # $Selektors are not available. # single # for status/single rows -- e.g tables with just one row # showing the actual status. This needs a $selector, # which specifies the condition to select the to-be-updated # row. # cumulative # log day/month/year/hourly data. This something between # continous/sinlge: # It will update a row instead of adding a new row, but # add new rows if there was no row to be udated. # To select the right row all %modifiers are used as # selectors. To have the possiblity to only use a sub-set # of those %-types, only the ones starting with "!" will be # considered as selectors. # You can of course also use static "$" selectors like in # the single mode. # There are dedicated examples showinng these modes! db_operation_mode="continuous"; #db_operation_mode="single"; #db_operation_mode="cumulative"; # Special keywords for data # The DBWriter internally creates some handy data which can be # used to be logged to the database, # Currently the following special keywords are understood: # %TIMESTAMP, %YEAR, %MONTH, %DAY, %HOUR, %MINUTE # When used, they expand to the wallclock value; # (where timestamp expands to the unix timestamp) # Selectors # In the single and cumulative modes, solarpowerlog needs to # update the data and make decissions which rows it needs to # update. # For this the selectors are used. # Selectors can be recognized by a "$" or "!" as the first # character. # "$" is used when a literal should be used as a selector. # For example "$Inv1" selects the literal string "Inv1" # "!" is used when of the "special Keywords" should be used as # the selector. For example "!MONTH" is using the current # month to select the row. # There can be more than one selector -- in this case they all # have to match. # single mode does not honor "!" selectors. # The db_layout list describes the mapping between the data to # logged and the columns of the table. # See the example for the syntax. # Capability/Keyword describes what to log by specifying the # the inverters' capability. # Also you can use the special keywords and (in single and # cumulative mode) the selectors. # The following cumulative example logs only the curernt power # along with the timestamp. The timestamp goes to the column # "created" and the feeding power to the column "pac" db_layout = ( # [ "Capability/Keyword" , "column" ], [ "%TIMESTAMP" , "created" ], [ "Inverter Overall Status" , "status" ] ); }, { db_logevery=5.0; db_logchangedonly=true; db_allowsparse=false; db_table="single_mode"; db_create_table="YES"; db_operation_mode="single"; db_layout = ( # [ "Capability/Keyword" , "column" ], [ "%TIMESTAMP" , "created" ], [ "Current Grid Feeding Power", "pac" ], [ "$Inverter1","inverter"] ); }, { db_table="cumulative_mode"; db_logevery=5.0; db_logchangedonly=true; db_allowsparse=false; db_create_table="YES"; # cumulative mode db_operation_mode="cumulative"; db_layout = ( # [ "Capability/Keyword" , "column" ], [ "$Inverter1" , "Inverter" ], [ "!YEAR" , "Year" ], [ "!MONTH" , "Month" ], [ "!DAY" , "Day" ], [ "Energy produced today (kWh)", "Today_Produced" ] ); } ) } ); }; solarpowerlog-solarpowerlog-0.26/example_confs/dbwriter/solarpowerlog_dbwriter_common.conf000066400000000000000000000317621444065341000327340ustar00rootroot00000000000000# This example shows the common part of the DBWriter. # About the Database writer: # The plugin writes any collected data to a database, and is highly # configureable. application: { # what debug-level should be used if not specified by any component # Choose out of ALL, TRACE, DEBUG, INFO, WARN, ERROR ,FATAL and NONE # optional. If not given, it defaults to "ERROR" # Note, if specified a logconfig file, the config file can override this # setting. dbglevel = "ALL" # configuration file to load for liblog4cxx # see the liblog4cxx docs for details. # (http://http://logging.apache.org/log4cxx/index.html) # With the configuration file you can specify different log files for different # components of the program. # note, that if not given it will log everything to the console. # note: if the filename ends with .xml, libconfig tries to load the # configuration in xml-format, which allows even more tweaks. #logconfig = "solarpowerlog_lib4cxx.conf"; #logconfig = "solarpowerlog_lib4cxx.xml"; }; inverter : { inverters = ( { # (REQUIRED) Name of the Inverter (Will be used as reference in the program) name = "Netcat"; dbglevel = "ERROR"; # (REQUIRED) "Human Readable Description of the fine piece of hardware. Not interpreted by this software" description = "A Inverter inverting"; # (REQUIRED) Selects the family of the inverters. # (In SW-Speak: Specifies the family of factory to generate. The generated factory will instanciate the correct model class # specified by "model" manufacturer = "SPUTNIK_ENGINEERING"; # (REQUIRED) Which model we about to talk too # Your choice is "S-Series" (currently) model ="S-Series"; # (REQUIRED BY SPUTNIK, S-Series) # mode of comm (planned: # "TCP/IP", Ethernet # "RS485, RS485 # "RS485overTCPIP" Daisy-Chained modules with one over ethernet, the others chained to the rs485 output. comms = "TCP/IP"; # TCP/IP options. Address and Port. # Port can also be a so-called "well-known-port" named as string. Example: "http" tcpadr = "127.0.0.1"; tcpport = "12345"; # optional: Wait maximum this time to connect to the inverter. # default: 3 seconds (value: 3000) # Note: Currently blocks the logger until timeout or connection! tcptimeout = 5000; # Communication address of the inverter (as set in the menu of the inverter) commadr = 1; # Adress to use as "our" adress for communication # defaults to 0xFB ownadr = 0xfb; # How often should the inverter be queried # The value specifies how long the class will *wait* # after one query has been answered. # The value can be a float. # This value is optional and defaults to 5 seconds queryinterval= 3; } ); }; logger: { loggers = ( { # This DB Writer is known as (required) name = "DBWriter_tst"; # It is of type type = "DBWriter"; # And gets its data from datasource = "Netcat"; # Note: solarpowerlog uses CppDB as its database backend. # So to get additional hints about configuration options, # see http://cppcms.com/sql/cppdb/backendref.html # and http://cppcms.com/sql/cppdb/connstr.html. # By setting the database type to "custom" you can override all # settings by define a connection string using the option # db_cppdb_options. # (REQUIRED) selects the database engine to be used. # choices are "mysql", "sqlite3", "postgresql", "odbc" and "custom" # Depending on the selected type, the database backends needs # different configuration options. Below are examples. # db_type="database backend" ### sqlite3 ### # CppDB documentation for the sqlite3 backend: # http://cppcms.com/sql/cppdb/sqlite3.html db_type="sqlite3" # (REQUIRED) database file db_database="/tmp/solarpowerlog.sqlite3"; # mode to open the database. # "create" (default) create if db does not exist # "readwrite" open db readwrite, but fail if it does not exists before # (the other availbable mode, "readonly" is not useful for solarpowerlog) db_mode = "create" # Other options permitted by the CppDB library # Just an example how to use it: # NOTE: Be careful! #db_cppdb_options="busy_timeout=100@use_prepared=off" ### MySQL # CppDB backend documentation: http://cppcms.com/sql/cppdb/mysql.html #db_type="mysql"; # hostname of the server (default is localhost) #db_host="localhost" # user to connect #db_user="dbuser"; # password to connect to the server #db_password="very\'secret"; # database to be used #db_database="solarpowerlog"; # Port to access the server #db_port="3306"; # Alternatively, specify the unix socket to be used # note: specify either port or socket -- not both! #db_unixsocket="tmp/mysql.sock" # Additional parameters (see CppDB homepage, especially the MySQL backend reference) #db_cppdb_options="opt_read_timeout=10;opt_compress=1"; ### PostgreSQL ### # Backend documentation: http://cppcms.com/sql/cppdb/postgresql.html #db_type="postgresql"; # hostname of the server (default is localhost) #db_host="localhost" # user to connect #db_user="dbuser"; # password to connect to the server #db_password="very\'secret"; # database to be used #db_database="solarpowerlog"; # Port to access the server #db_port="5432"; # Additional parameters (see CppDB homepage, especially the postgresql backend reference) #db_cppdb_options="@blob=lo"; ### ODBC Bridge #db_type="odbc"; # You need to specify the connection string with db_cppdb_options: # See CppDB documentation of the ODBC backend. # http://cppcms.com/sql/cppdb/odbc.html #db_cppdb_options="DSN=MySource;UID=myuser;PWD=secret"; ### Custom #db_type="custom"; # You need to specify the connection string with db_cppdb_options: # solarpowerlog will just pass the string to the library. #db_cppdb_options="odbc:DSN=MySource;UID=myuser;PWD=secret"; ## this list specifies the jobs to be done... # every entry is one job... db_jobs = ( { # The table to be used for this job db_table="mytable_cumulative"; # Allow "Sparse Tables", so logging also values if only # a subset of all data is available. # solarpowerlog will then put "NULL" into the database. # optional, defaults to false db_allowsparse=false; # which value should be put into the database, if there is a # sparse data set to be insert # Value "null" will insert the NULL value, "default" the (either # explicit or implicit SQL default value. # NOTE THIS VALUE IS CURRENTLY NOT USED: (as logic not implemented) # optional, defaults to null. #db_sparsepolicy="null" # Should we only log if actually *something* changed # If set to "true" the logger will only write to the # database if actual data changed (might reduce db size) # (note: to determine if smth has changed only data from the # inverter is considered -- not e.g %TIMESTAMP or like. db_logchangedonly=true; # Time between logging to the database. db_logevery=3.0; # Should the table create on startup? # POTENTIALLY DANGEROUS -- can cause data loss. # You have these options: # "no" -- do not create any tables # "YES" -- uppercase YES will create the table but will add # "IF NOT EXISTS" to the SQL statement. So the table should # not be destroyed if it indeeds already exists. # (if your SQL supports this) # "YES-WIPE-MY-DATA" -- DANGEROUS OPTION, use uppercase letters # This will DELETE the table before creating it. # "print-sql-statement" (lower case string): Just log the # statement required to create the table as INFO-Level message. # (This is good to give you an hint to create the table yourself) # # NOTE: The table will not be created directly on startup, # but with some delay, as solarpowerlog first needs to figure # out the data-types to be used for each column. # This also means that db_layout must only reference # Capabilities that are actually existing and provided # by the Inverter. db_create_table="no" #db_create_table="YES"; #db_create_table="YES-WIPE-MY-DATA"; #db_create_table="print-sql-statement" # db_operation_mode specifies some principle operational mode # for the DB-Writer. Options are: # continous # like CVS, just logging everything to one big # table. You maybe want a %TIMESTAMP, but that depends on you # backend reading the data from the database. # $Selektors are not available. # single # for status/single rows -- e.g tables with just one row # showing the actual status. This needs a $selector, # which specifies the condition to select the to be updated # row. # cumulative # log day/month/year/hourly data. This something between # continous/sinlge, as this will add data to the table, # It will update a row instead of adding a new row. # To select the right row all %modifiers are used as # Selektors. #db_operation_mode="continuous"; #db_operation_mode="single"; db_operation_mode="cumulative"; # Keywords # modifer "%...." special placeholders (Day, month, timestamp, ....) # selector "$" (to select the right row) # db_layout = ( # [ "Capability/Keyword" , "column" ], [ "%TIMESTAMP" , "created" ], [ "%DAY" , "d" ], [ "!HOUR" , "h" ], [ "!MINUTE" , "m" ], [ "Current Grid Feeding Power", "pac" ] ); } /* , { db_table="mytable2"; logchangedonly=true; logevery=15.0; operation_mode="continuous"; #operation_mode="single"; #operation_mode="cumulative"; # continous log -- like CVS, just loggiong everything to one big table. Needs a %TIMESTAMP column . # single status/single row -- for tables with just one row showing actual status. determined by existance of a $selector # which specifies an condition to select the right row. # day/month/year/hourly/.. -- for cumulative tables (like how many kwH per day; determined by any %MONTH, %DAY, %HOUR ... time defining # selectors.) # Keywords # modifer "%...." special placeholders (Day, month, timestamp, ....) # selector "$" (to select the right row) # db_layout = ( # [ "Capability/Keyword" , "column" ], [ "%TIMESTAMP" , "created" ], [ "%MONTH" , "m" ], [ "%DAY" , "d" ], [ "%HOUR" , "h" ], [ "Current Grid Feeding Power", "pac" ] ); #tablespec = # [ "$1", "id" ] # [ "Inverter Overall Status", "status" ] # [ "Inverter Temperature (C)", "Temperature"]; } */ ) } ); }; solarpowerlog-solarpowerlog-0.26/example_confs/dbwriter/solarpowerlog_single.conf000066400000000000000000000015731444065341000310200ustar00rootroot00000000000000# This example-snippet shows how to use the DB Writer in single mode. # it only focuses on this part, all unrelated options are NOT shown. # this snippet goes into the db_jobs list. # The snippet below will setup logging so that a single row will be # continuously updated with the latest value. # This can be for example used to generated an overview / snapshot of the # current data when history is not necessary. # The example below will log to the database the current power along # with the timestamp, overwriting the values with every update. # Note: In single mode, only "$" selectors can be used. "!" type selectors # work only in cumulative mode. db_operation_mode="single"; db_layout = ( # [ "Capability/Keyword" , "column" ], [ "%TIMESTAMP" , "created" ], [ "Current Grid Feeding Power", "pac" ], [ "$Inverter1","inverter"] ); solarpowerlog-solarpowerlog-0.26/example_confs/dbwriter/solarpowerlog_sma.conf000066400000000000000000000220551444065341000303150ustar00rootroot00000000000000# This in an example describing the configuration options of the DBWriter. # The example does NOT show on how to configure the database backends. # This information is available in the snippets in this directory. # This sample is configured to work together with the simulator example # found in examples_confs/solarpowerlog_simulator # NOTE: This example DOES create the required tables, see # db_create_table below # This example is designed to work with the visualisation # taken from https://sourceforge.net/projects/solarmaxwatcher/ # NOTE: the visualization is not 100% compatible, but a patched # version is available in the tools/solarmaxwatcher directory # in the solarpowerlog src tarball. # (on Debian it will be in /usr/share/doc/solarpowerlog/examples/solarmaxwatcher) application: { dbglevel = "ALL" }; inverter : { inverters = ( { #TODO replace this minimal config by an include to the examples. name = "Simulator"; dbglevel = "ERROR"; description = "Sample Inverter for the DB Example"; manufacturer = "SPUTNIK_ENGINEERING"; model ="S-Series"; comms = "TCP/IP"; tcpadr = "127.0.0.1" tcpport = "12345"; tcptimeout = 5000; commadr = 1; } ); }; logger: { loggers = ( { # We want to have a DBWriter: type = "DBWriter"; # This DB Writer is known as (required) name = "DBWriter_tst"; # And gets its data from datasource = "Simulator"; # Get the database configured. # (See there for details) @include "solarpowerlog-mysql-test.conf" ## this list specifies the jobs to be done... # jobs are bundled data sets, which should be logged to # a table db_jobs = ( { # Time between logging to the database. # unit is seconds. # Currently REQUIRED (but might be changed later.) db_logevery=5.0; # Should we only log if actually *something* changed # If set to "true" the logger will only write to the # database if actual data changed (might reduce db size) # (note: to determine if smth has changed only data from the # inverter is considered -- not e.g %TIMESTAMP or like. # this defaults to "false" db_logchangedonly=false; # Sparse datas are datsets which are only partial available. # When true, this option allows to log data even if only # a subset is available # If this this is false, solarpowerlog will not log anything # unless it has got all the data. (default) db_allowsparse=false; # (REQUIRED) The table to be used for this job db_table="solarpowerlog1"; # Should the table create on startup? # POTENTIALLY DANGEROUS -- can cause data loss. # You have these options: # "no" -- do not create any tables # "YES" -- uppercase YES will create the table but will add # "IF NOT EXISTS" to the SQL statement. So the table should # not be destroyed if it indeeds already exists. # (if your SQL supports this) # "YES-WIPE-MY-DATA" -- DANGEROUS OPTION, use uppercase letters # This will DELETE the table before creating it. # "print-sql-statement" (lower case string): Just log the # statement required to create the table as INFO-Level message. # (This is good to give you an hint to create the table yourself) # # NOTE: The table will not be created directly on startup, # but with some delay, as solarpowerlog first needs to figure # out the data-types to be used for each column. # This also means that db_layout must only reference # Capabilities that are actually existing and provided # by the Inverter. db_create_table="YES"; # (REQUIRED) specifies some principle operational mode # (note: selectors are explained later) # for the DB-Writer. Options are: # continous # like CVS, just logging everything to one big # table. You maybe want a %TIMESTAMP. # $Selektors are not available. # single # for status/single rows -- e.g tables with just one row # showing the actual status. This needs a $selector, # which specifies the condition to select the to-be-updated # row. # cumulative # log day/month/year/hourly data. This something between # continous/single: # It will update a row instead of adding a new row, but # add new rows if there was no row to be udated. # To select the right row all %modifiers are used as # selectors. To have the possiblity to only use a sub-set # of those %-types, only the ones starting with "!" will be # considered as selectors. # You can of course also use static "$" selectors like in # the single mode. # There are dedicated examples showinng these modes! db_operation_mode="continuous"; #db_operation_mode="single"; #db_operation_mode="cumulative"; # Special keywords for data # The DBWriter internally creates some handy data which can be # used to be logged to the database, # Currently the following special keywords are understood: # %TIMESTAMP, %YEAR, %MONTH, %DAY, %HOUR, %MINUTE # When used, they expand to the wallclock value; # (where timestamp expands to the unix timestamp) # Selectors # In the single and cumulative modes, solarpowerlog needs to # update the data and make decissions which rows it needs to # update. # For this the selectors are used. # Selectors can be recognized by a "$" or "!" as the first # character. # "$" is used when a literal should be used as a selector. # For example "$Inv1" selects the literal string "Inv1" # "!" is used when of the "special Keywords" should be used as # the selector. For example "!MONTH" is using the current # month to select the row. # There can be more than one selector -- in this case they all # have to match. # single mode does not honor "!" selectors. # The db_layout list describes the mapping between the data to # logged and the columns of the table. # See the example for the syntax. # Capability/Keyword describes what to log by specifying the # the inverters' capability. # Also you can use the special keywords and (in single and # cumulative mode) the selectors. # The following cumulative example logs only the current power # along with the timestamp. The timestamp goes to the column # "created" and the feeding power to the column "pac" db_layout = ( # [ "Capability/Keyword" , "column" ], [ "%TIMESTAMP" , "created" ], ["Energy produced today (kWh)" , "kdy" ], ["Energy produced this month (kWh)" , "kmt" ], ["Energy produced this year (kWh)" , "kyr" ], ["Energy produced accumulated all time (kWh)" , "kt0" ], ["Net frequency (Hz)" , "tnf" ], ["Current Grid Feeding Power" , "pac" ], ["relative Power (%)" , "prl" ], ["Inverter Temperature (C)" , "tkk" ], ["AC grid feeding current (A)" , "il1" ], ["DC current in (A)" , "idc" ], ["AC grid voltage (V)" , "ul1" ], ["DC voltage in (V)" , "udc" ] ); } /* # a second jobs would go in the block... , { db_logevery=60.0; db_table="table2"; (...) } */ ) } ); }; solarpowerlog-solarpowerlog-0.26/example_confs/htmlwriter.conf000066400000000000000000000046771444065341000251470ustar00rootroot00000000000000 { # This filters will be known as (required) name = "HTMLWriterExample"; # It is of the type type = "HTMLWriter"; # And gets its data from datasource = "Inverter1"; # The module has a parameter-set which might be useful for generating # your own templates: Template-Generatin-Assitance. In this mode, all generated # values will be put out to a template file called like the name of # the HTML Writer object (see above, name), listing all known capabilites # for the inveerter. # the next one is the main switch for the feature: (optional, default false) # generate_template = false; # this one says the template generator, in what directory to put # the template (optional, default /tmp/) # generate_template_dir = "/tmp/"; #update the HTML page every seconds: #the value "0" means everytime something changes ,probably not what you want #( WARNING: THE VALUE 0 IS NOT YET IMPLEMENTED) #if not given, we will try to extract this out of the Data Query Interval #of the inverter, which will, if not available, defaults to 300. #if not available #writeevery=300; # As in the CSV Plugin, # you can specify here the file/path where to store the files # the files. %s will be replaced by the current date in ISO 8601 # format: YYYY-MM-DD. When there is no %s, the file will be replaced # on midnight. # This option is required. htmlfile="/var/www/solarpowerlog-%s.html"; # Template file to be used # This file will be loaded as template... Mandatory. templatefile="example_confs/htmlwritertemplate/test.tmpl"; # The filter can do some modifcations on the values # before handing them over to the template engine: # to keep it flexible, this is done with this list lists: # what_capbility names the one to be formatted # formatting operation is the operation to be performed: # stripwebroot remove the webroot-prefix from the capa formatters = ( # ( what_capability, formating_operation ) ( CVSDumper::Filename, stripwebroot ) ); } solarpowerlog-solarpowerlog-0.26/example_confs/htmlwritertemplate/000077500000000000000000000000001444065341000260165ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/example_confs/htmlwritertemplate/css/000077500000000000000000000000001444065341000266065ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/example_confs/htmlwritertemplate/css/style.css000066400000000000000000000057321444065341000304670ustar00rootroot00000000000000@charset "utf-8"; * { margin: 0; padding: 0; font-family:Arial, Helvetica, sans-serif; } html { overflow: auto; max-height: 100%; } body { background-color: #000; color: #fff0e0; margin: 0; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; width: 100%; height: 100%; } #page { width:98%; margin-left: 1em; margin-right: 1em; } /* the header of the page. */ #header { background-color: #000; color: #afa0e0; text-align:left; font-size:90%; padding: 1em 0em 0em 0em; clear:both; } #header a:link { color: #afa0e0; } #header a:visited { color: #483d8b; } /* this is the clearfix for the header, so that the floats are cleaned. Also paints the line below the header */ #header:after { content: " "; display: block; height: 0; clear: both; padding-top: 0.2em; padding-bottom: 0; margin: 0; --border-bottom-width: thin; --border-bottom-style: solid; } /* solarpowerlog is free software -- to the right please. */ .FLOSS { float:right; text-align:right; font-size:xx-small; } /* paragraphs in the header */ #header p { font-size:75%; margin-left: 1em; } /* main content div */ #content { background-color: #00001a; /*background-color:red;*/ padding-top: 1px; min-height: 1em; width:99%; border-top: 1px solid #afa0e0; } #mainarea { } #content:after { content: " "; visibility:hidden; display: block; height: 0; clear: both; } /* the coloumn on the left */ #leftcolumn { border-right: thin solid #afa0e0; float:left; width: 15em; overflow:auto; margin-right:0.5em; padding-top: 0.5em; } table.bid_table { border-width: 0px 0px 0px 0px; border-spacing: 0px; border-style: none none none none; border-color: gray gray gray gray; border-collapse: collapse; } table.bid_table th { border: thin dotted black; background-color: #483d8b; color:white; } table.bid_table td { padding: 0 0 0 0.1em; border: thin dotted black; } table.bid_table tr:hover { color: #fff; padding: 1px 1px 1px 1px; } table.bid_table tr { background-color: #4b70b2; color: #9f91cc; padding: 1px 1px 1px 1px; } .bid_table { width:98%; } .bid_cv { visibility: hidden; height: 5px; } .bid_name { font-size: 120%; } .bid_model { font-size: small; max-width: 15em; } .debug { visibility: hidden; height: 0px; } #csvtimeplot { background-color: #f8f8ff; height: 450px; } .bid_maindiv { } .csvdumper { } #csvtimeplot { color: black; height:400px; clear:right; } .csv_filename { font-size: 10px; height:24px; float:right; } .csv_capas { visibility: hidden; height: 0px; } .csv_header { visibility: hidden; height: 0px; } .csv_name { font-weight:bolder; float:left; padding-right: 1em; } .csv_model {float:left; font-weight:normal; } solarpowerlog-solarpowerlog-0.26/example_confs/htmlwritertemplate/css/style_dark.css000066400000000000000000000054611444065341000314670ustar00rootroot00000000000000@charset "utf-8"; * { margin: 0; padding: 0; font-family:Arial, Helvetica, sans-serif; } html { overflow: auto; max-height: 100%; } body { background-color: #000; color: #fff0e0; margin: 0; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; width: 100%; height: 100%; } #page { width:99%; margin-left: auto; margin-right: auto; } /* the header of the page. */ #header { background-color: #000; color: #afa0e0; text-align:left; font-size:90%; padding: 1em 1em 0em 1em; clear:both; } /* this is the clearfix for the header, so that the floats are cleaned. Also paints the line below the header */ #header:after { content: " "; display: block; height: 0; clear: both; padding-top: 0.2em; padding-bottom: 0; margin: 0; border-bottom-width: thin; border-bottom-style: solid; } /* solarpowerlog is free software -- to the right please. */ .FLOSS { float:right; text-align:right; font-size:xx-small; } /* paragraphs in the header */ #header p { font-size:75%; margin-left: 1em; } /* main content div */ #content { background-color: #00001a; /*background-color:red;*/ padding-top: 1px; min-height: 1em; width:99%; } #mainarea { } #content:after { content: " "; visibility:hidden; display: block; height: 0; clear: both; } /* the coloumn on the left */ #leftcolumn { border-right: thin solid #afa0e0; float:left; width: 15em; overflow:auto; margin-right:0.5em; padding-top: 0.5em; /* margin 0 0 0 0; padding 0 0 0 0; */ } table.bid_table { border-width: 0px 0px 0px 0px; border-spacing: 0px; border-style: none none none none; border-color: gray gray gray gray; border-collapse: collapse; } table.bid_table th { border: thin dotted black; background-color: #483d8b; color:white; } table.bid_table td { padding: 0 0 0 0.1em; border: thin dotted black; } table.bid_table tr:hover { color: #fff; padding: 1px 1px 1px 1px; } table.bid_table tr { background-color: #4b70b2; color: #9f91cc; padding: 1px 1px 1px 1px; } .bid_table { width:98%; } .bid_cv { visibility: hidden; height: 5px; } .bid_name { font-size: 120%; } .bid_model { font-size: small; max-width: 15em; } .debug { visibility: hidden; height: 0px; } #csvtimeplot { background-color: #f8f8ff; height: 450px; } .bid_maindiv { } .csvdumper { } #csvtimeplot { color: black; height:400px; clear:right; } .csv_filename { font-size: 10px; height:24px; float:right; } .csv_capas { visibility: hidden; height: 0px; } .csv_header { visibility: hidden; height: 0px; } .csv_name { font-weight:bolder; float:left; padding-right: 1em; } .csv_model {float:left; font-weight:normal; } solarpowerlog-solarpowerlog-0.26/example_confs/htmlwritertemplate/snippets/000077500000000000000000000000001444065341000276635ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/example_confs/htmlwritertemplate/snippets/CSVLogger.tmpl000066400000000000000000000062771444065341000323700ustar00rootroot00000000000000

CSV Dumper

Filename:

LoggedCapas:

<* First emit the header of the script *>
solarpowerlog-solarpowerlog-0.26/example_confs/htmlwritertemplate/snippets/CSVLogger_header.tmpl000066400000000000000000000003351444065341000336650ustar00rootroot00000000000000<* Part of the game which has to be placed in the header. But only once. *> solarpowerlog-solarpowerlog-0.26/example_confs/htmlwritertemplate/snippets/basic_inverter_data.tmpl000066400000000000000000000051431444065341000345540ustar00rootroot00000000000000<* This template snippet writes a small table with showing the values of the inverter. *>

<* Basic Values of the inverter *>

Current Values

Name Value Unit
Feeding Power W
Power-on hours h
produced this year kWh
produced this month kWh
produced today kWh
produced all time kWh
installed power Wp
net frequency Hz
rel. power %
Panel Voltage V
Panel Current A
Grid voltage V
Grid current A
Inverter Temperature °C
solarpowerlog-solarpowerlog-0.26/example_confs/htmlwritertemplate/test.tmpl000066400000000000000000000034401444065341000276740ustar00rootroot00000000000000 Solarpowerlog CHTMWriter Example <* The CSV logger visualization needs some javascript tags in the body. Therefore, we need to include the middle part, which will also include the "body" tag.*> <*TMPL_IF name="CSVDumpler::LoggedCaps"*> <*TMPL_ELSIF*> <*body*> <*/TMPL_IF*>
<* Emit Basic Inverter information *>
<* visualization in timeplots *>
solarpowerlog-solarpowerlog-0.26/example_confs/simulator/000077500000000000000000000000001444065341000241005ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/example_confs/simulator/README000066400000000000000000000071131444065341000247620ustar00rootroot00000000000000## License of this README: GPL3+ Using the solarpowerlog sputnik simulator ========================================= Compiling and example configurations ------------------------------------ Solarpowerlog has its own embedded simulator, superceeding netcat based scripts used to be found at tools/sputnik-simulator With release 0.24 those scripts have beeen removed from the distribution. The simulator models a Sputnik inverter and can be used to get a feeling of solarpowerlog though its primary goal is to be a help for debugging even at night. To use the simulator, please make sure that it is enabled at compile time. If it is not enabled you need to recompile solarpowerlog and enable the feature with ./configure --enable-sputniksimulator See the file INSTALL for details how to compile solarpowerlog. In this directory (example_confs/simulator) you find to example configuration files to help you getting started with the simulator. solarpowerlog.conf solarpowerlog_simulator.conf (the file solarpowerlog_shared_sim.conf shows how to simulate using a shared connection, its config-counterpart is solarpowerlog_shared.conf)) The first file defines a inverter which to connect to a simulator on localhost:12345 and the second file defines this simulator. So running those two commands (in two terminals) will show how they work together: solarpowerlog -c example_confs/simulator/solarpowerlog.conf solarpowerlog -c example_confs/simulator/solarpowerlog_simulator.conf Control server -------------- The simulator can also offer a control server. This allows setting the values the simulator will return as well as turning commands off an on. The control server allows manipulation of the responses of the simulated inverter, like tweaking values and turning commands to be answered off and on. To connect to the control server you can e.g use telnet or netcat: Example: (using port 12346) telnet localhost 12346 or echo "PIN=2500" | netcat localhost 12346 Note: Strangely, with telnet the first byte is not received by solarpowerlog, but subsequent commands work fine. The control server is enabled by the simulators' config, so if you cannot connect, make sure it is configured. Watch the log for a "Sputnik-Simulator control server ready." To set a value, use this syntax: =[,value2][;=[,]](...) where "token" is one of the inverter's commands, like (for example the PIN, power installed) If a token takes more thn one value (like the SYS) you seperate it by a comma. More than one value can be set at a time. Just seperate the tokens by a ";" Examples: PIN=2500 TNF=49.95 SYS=20004,0 PIN=2500;SYS=20004,0 To disable commands, you can use the special value "off" and to reenable "on": PIN=off PIN=on You can feed complete sputnik telegrams, they just must include the {..} (note: lenght, checksum and adresses will be ignored in this case so you can modify the string as you wish and it will be accepted.) Example how to use the control server with a sputnik telegram: echo "{01;FB;97|64:PAC=0;KHR=138E;PIN=11F8;KT0=863;KYR=1;KMT=1;KDY=1;KT0=863;UDC=80A;IDC=4;IL1=0;UL1=8F5;TKK=18;TNF=1384;SYS=4E22,0|246E}" | netcat localhost 12346 With 0.24 the control server also supports the following commands: quit - closes the connection to the ctrl server version - prints the version of solarpowerlog offline - "offlines" the simulator but stays connected online - go "online" again, reverts offline disconnect - disconnects the simulator (including closing of the connection) connect - reverts "disconnect" These commmands may not be combined with other commands. solarpowerlog-solarpowerlog-0.26/example_confs/simulator/solarpowerlog.conf000066400000000000000000000201031444065341000276420ustar00rootroot00000000000000# This sample configuration shows how to connect with the simulator, # coigured in the examples_confs/solarpowerlog_simulator files working with the embedded simulator # # Please note, the file is parsed by libconfig. application: { # what debuglevel should be used if not specified by any component # Choose out of ALL, TRACE, DEBUG, INFO, WARN, ERROR ,FATAL and NONE # optional. If not given, it defaults to "ERROR" # Note, if specified a logconfig file, the config file can override this # setting. dbglevel = "DEBUG" # configuration file to load for liblog4cxx # see the liblog4cxx docs for details. # (http://http://logging.apache.org/log4cxx/index.html) # With the configuration file you can specify different log files for different # components of the program. # note, that if not given it will log everything to the console. # note: if the filename ends with .xml, libconfig tries to load the # configuration in xml-format, which allows even more tweaks. #logconfig = "solarpowerlog_lib4cxx.conf"; #logconfig = "solarpowerlog_lib4cxx.xml"; }; inverter : { inverters = ( { # (REQUIRED) Name of the Inverter (Will be used as reference in the program) name = "Netcat"; # (REQUIRED) "Human Readable Description of the fine piece of hardware. Not interpreted by this software" description = "A Inverter inverting"; # (REQUIRED) Selects the family of the inverters. # (In SW-Speak: Specifies the family of factory to generate. The generated factory will instanciate the correct model class # specified by "model" manufacturer = "SPUTNIK_ENGINEERING"; # (REQUIRED) Which model we about to talk too # Your choice is "S-Series" (currently) model ="S-Series"; # (REQUIRED BY SPUTNIK, S-Series) # mode of comm (planned: # "TCP/IP", Ethernet # "RS485, RS485 # "RS485overTCPIP" Daisy-Chained modules with one over ethernet, the others chained to the rs485 output. comms = "TCP/IP"; # TCP/IP options. Address and Port. # Port can also be a so-called "well-known-port" named as string. Example: "http" tcpadr = "127.0.0.1"; tcpport = "12345"; # optional: Wait maximum this time to connect to the inverter. # default: 3 seconds (value: 3000) # Note: Currently blocks the logger until timeout or connection! tcptimeout = 5000; # Communication address of the inverter (as set in the menu of the inverter) commadr = 1; # Adress to use as "our" adress for communication # defaults to 0xFB ownadr = 0xfb; # How often should the inverter be queried # The value specifies how long the class will *wait* # after one query has been answered. # The value can be a float. # This value is optional and defaults to 5 seconds queryinterval= 3; } ); }; logger: { loggers = ( { # This dumper is known as (required) name = "CVS_Netcat"; # It is of type type = "CVSWriter"; # And gets its data from datasource = "Netcat"; # Where should we write the file? # (If rotate is true, %s will be replaced by the date # of the file, formated after ISO 8601: YYYY-MM-DD ) # Note: If you proved the directory by e.g cron, you can # also write the %s in the path. logfile="/tmp/Inverter1_%s.csv"; # Rotate the file at midnight? rotate=true; # What should we log # Valid is "all" as string (Note: The generated CSV will not # be RFC 4180 compliant, as we change header in mid-file) # Alternate Option: # Specify an array with the caps you want. #Hint: First use "all" then use the generated file to get your array) #data2log="all"; data2log= [ "AC grid feeding current (A)", "AC grid voltage (V)", "Current Grid Feeding Power", "DC current in (A)", "DC voltage in (V)", "Data Query Interval", "Data Validity", "Energy produced cumulated all time (kWh)", "Energy produced this month (kWh)", "Energy produced this year (kWh)", "Energy produced today (kWh)", "Inverter Overall Status", "Inverter Power On Hours", "Inverter Temperature (C)", "Net frequency (Hz)" ]; # options for the plugin Compact_CSV=true; flush_file_buffer_immediatly=true; }, { # This filters will be known as (required) name = "HTMLWriter_Netcat"; # It is of the type type = "HTMLWriter"; # And gets its data from datasource = "CVS_Netcat"; # The module has a parameter-set which might be useful for generating # your own templates: Template-Generatin-Assitance. In this mode, all generated # values will be put out to a template file called like the name of # the HTML Writer object (see above, name), listing all known capabilites # for the inverter. # the next one is the main switch for the feature: (optional, default false) generate_template = true; # this one says the template generator, in what directory to put # the template (optional, default /tmp/) generate_template_dir = "/tmp"; #update the HTML page every seconds: #the value "0" means everytime something changes ,probably not what you want #( WARNING: THE VALUE 0 IS NOT YET IMPLEMENTED) #if not given, we will try to extract this out of the Data Query Interval #of the inverter, which will, if not available, defaults to 300. #if not available writeevery=15; # As in the CSV Plugin, # you can specify here the file/path where to store the files # the files. %s will be replaced by the current date in ISO 8601 # format: YYYY-MM-DD. When there is no %s, the file will be replaced # on midnight. # This option is required. htmlfile="/var/www/solarpowerlog-%s.html"; # Template file to be used # This file will be loaded as template... Mandatory. templatefile="example_confs/htmlwritertemplate/test.tmpl"; # The filter can do some modifcations on the values # before handing them over to the template engine: # to keep it flexible, this is done with this list lists: # Parameters: # what_capbility names the one to be formatted # where_to_store (optional) if you want to export it to the # using another name. if "", uses the cap name # parameters one string with extra parameters to be supplied # to the formatter. # note: there can be as many parameters as the # formatter wants, each in its own string) # # formatting operation is the operation to be performed: # stripwebroot remove the webroot-prefix from the capa # this one needs the config "webroot". # See below for the parameter this formatter uses. formatters = ( # [ what_capability, formating_operation, where_to_store ], [ "CSVDumper::Filename", "stripwebroot", "" , "/var/www" ], [ "CSVDumper::LoggedCaps", "searchcvsentry", "powernow", "Current Grid Feeding Power" ], [ "CSVDumper::LoggedCaps", "searchcvsentry", "kwhtoday", "Energy produced today (kWh)" ] ); } ); }; solarpowerlog-solarpowerlog-0.26/example_confs/simulator/solarpowerlog_shared.conf000066400000000000000000000044701444065341000312010ustar00rootroot00000000000000# This sample configuration configured to Sputnik inverters using a shared # connection. # This file is best used with the shared-communication simulator example. # (example_confs/solarpowerlog_shared_sim.conf) application: { # what debuglevel should be used if not specified by any component # Choose out of ALL, TRACE, DEBUG, INFO, WARN, ERROR ,FATAL and NONE # optional. If not given, it defaults to "ERROR" # Note, if specified a logconfig file, the config file can override this # setting. dbglevel = "TRACE" # configuration file to load for liblog4cxx # see the liblog4cxx docs for details. # (http://http://logging.apache.org/log4cxx/index.html) # With the configuration file you can specify different log files for different # components of the program. # note, that if not given it will log everything to the console. # note: if the filename ends with .xml, libconfig tries to load the # configuration in xml-format, which allows even more tweaks. #logconfig = "solarpowerlog_lib4cxx.conf"; #logconfig = "solarpowerlog_lib4cxx.xml"; }; inverter : { inverters = ( { name = "Shared_Comms_Inverter1"; description = "A Inverter inverting"; manufacturer = "SPUTNIK_ENGINEERING"; model ="S-Series"; comms = "SharedConnection"; sharedconnection_type = "master"; realcomms = { comms = "TCP/IP"; tcpadr = "127.0.0.1"; tcpport = "12345"; }; commadr = 1; ownadr = 0xfb; queryinterval= 3; # Set timeouts to 1h to ease debugging #response_timeout = 3600000; #connection_timeout = 3600000; #send_timeout = 3600000; } , { name = "Shared_Comms_Inverter2"; description = "A Inverter inverting"; manufacturer = "SPUTNIK_ENGINEERING"; model ="S-Series"; comms = "SharedConnection"; sharedconnection_type = "slave"; useconnection = "Shared_Comms_Inverter1"; commadr = 2; ownadr = 0xfb; queryinterval= 3.1; # Set timeouts to 1h to ease debugging #response_timeout = 3600000; #connection_timeout = 3600000; #send_timeout = 3600000; } ); }; logger: { loggers = ( ); }; solarpowerlog-solarpowerlog-0.26/example_confs/simulator/solarpowerlog_shared_sim.conf000066400000000000000000000032141444065341000320440ustar00rootroot00000000000000# This configuration files configures two sputnik simulators using a sharedconnection_type # communication. # To check it out, run in one shell # src/solarpowerlog -c tools/sputnik_simulator/solarpowerlog_shared.conf # and in another # src/solarpowerlog -c example_confs/solarpowerlog_shared_sim.conf # and enjoy the show :-) # (This file was created for debugging purpose of the shared comms, # so only inverters are configured but no loggers.) application: { # what debuglevel should be used if not specified by any component # Choose out of ALL, TRACE, DEBUG, INFO, WARN, ERROR ,FATAL and NONE # optional. If not given, it defaults to "ERROR" # Note, if specified a logconfig file, the config file can override this # setting. dbglevel = "ALL"; }; inverter : { inverters = ( { name = "Simulator"; description = "Sputnik Simulator"; manufacturer = "SPUTNIK_ENGINEERING"; model ="Simulator"; commadr = 1; ownadr = 0xfb; queryinterval= 5; comms = "SharedConnection"; sharedconnection_type = "master"; realcomms = { comms = "TCP/IP"; tcpadr = "127.0.0.1"; tcpport = 12345; tcpmode = "server"; }; }, { name = "Simulator2"; description = "Sputnik Simulator"; manufacturer = "SPUTNIK_ENGINEERING"; model ="Simulator"; comms = "SharedConnection"; sharedconnection_type = "slave"; useconnection = "Simulator" commadr = 2; ownadr = 0xfb; queryinterval= 5; } ); }; logger: { loggers = ( ); }; solarpowerlog-solarpowerlog-0.26/example_confs/simulator/solarpowerlog_simulator.conf000066400000000000000000000073461444065341000317570ustar00rootroot00000000000000# License of this conf-file: GPL3+ # This is a sample configuation file for solarpowerlog. # # For syntax: The file is parsed by libconfig. # This example file shows how to enable the "internal" Sputnik simulator. # Note that the logger section can be empty, but must be present. # NOTE: For instructions how to use the simulator, please read # tools/sputnik_simulator/README application: { # what debuglevel should be used if not specified by any component # Choose out of ALL, TRACE, DEBUG, INFO, WARN, ERROR ,FATAL and NONE # optional. If not given, it defaults to "ERROR" # Note, if specified a logconfig file, the config file can override this # setting. dbglevel = "DEBUG" # configuration file to load for liblog4cxx # see the liblog4cxx docs for details. # (http://http://logging.apache.org/log4cxx/index.html) # With the configuration file you can specify different log files for # different components of the program. # note, that if not given it will log everything to the console. # note: if the filename ends with .xml, libconfig tries to load the # configuration in xml-format, which allows even more tweaks. #logconfig = "solarpowerlog_lib4cxx.conf"; #logconfig = "solarpowerlog_lib4cxx.xml"; }; inverter : { inverters = ( { # (REQUIRED) Name of the Inverter (Will be used as reference in the program) name = "Simulator"; # (REQUIRED) "Human Readable Description of the fine piece of # hardware. Not interpreted by this software" # description = "Sputnik Simulator"; # (REQUIRED) Selects the family of the inverters. # (In SW-Speak: Specifies the family of factory to generate. The # generated factory will instanciate the correct model class # specified by "model" manufacturer = "SPUTNIK_ENGINEERING"; # (REQUIRED) Which model we about to talk too # Your choice is at the moment "S-Series" or "Simulator". # (To obtain the list of choice, comment this line and run solarpowerlog # -- it will tell you the valid options.) model ="Simulator"; # (optional) TYP defines the model identifie used by Sputnik Engineering to identfy # their modules. # The default value is, 65534 (0xFFFE) is not defined by Sputnik Engineering # but helps to identify that the simulator has been used in e.g. log files. # See the CSputnikCommandTYP.cpp for (known) values. # some examples # note that you can also set this value at runtimne via the ctrl server. # type there TYP=, for example TYP=2001 #TYP=2001; #"SolarMax 2000 E" #TYP=2010; #"SolarMax 2000 C" #TYP=20010; #"SolarMax 2000 S" #TYP=20030; #"SolarMax 4200 S" # (REQUIRED BY SPUTNIK, S-Series) # mode of communication comms = "TCP/IP"; # Sets the communication for TCP/IP to "incoming connections", required # by the simulator. tcpmode = "server"; # TCP/IP options. Address and Port. # In the simulator mode, tcpadr can be used to specify the interface we # will listen on -- by specifing the ip-address in dotted notation. # Default is to listen to any IP-V4 interface. (same as saying # tcpadr="any") # Theoretically, "any_v6" specifies IP-V6 interfaces, but the Sputnik # Inverters do not support this. # Example: the next commented line should limit connections to localhost. # tcpadr = "127.0.0.1"; # Specifies the port to listen on. # note that in contrast to the inverter class, the simulator needs an # number here. Well-known-services (via strings) are not possible. tcpport = 12345; # Communication address of the simulator commadr = 1; # Control server. ctrl_comms = { comms = "TCP/IP"; tcpmode = "server"; tcpadr = "127.0.0.1"; tcpport = 12346; }; } ); }; logger: { loggers = (); }; solarpowerlog-solarpowerlog-0.26/example_confs/solarpowerlog.conf000066400000000000000000000212231444065341000256270ustar00rootroot00000000000000# This is a sample configuation file for solarpowerlog. # # The file is parsed by libconfig, so for the grammar see # http://www.hyperrealm.com/libconfig/ # The application section is for global configuration. application: { # what debuglevel should be used if not specified by any component # Choose out of ALL, TRACE, DEBUG, INFO, WARN, ERROR ,FATAL and NONE # optional. If not given, it defaults to "ERROR" # Note, if specified a logconfig file, the config file can override this # setting. dbglevel = "ALL"; # configuration file to load for liblog4cxx # see the liblog4cxx docs for details. # (http://http://logging.apache.org/log4cxx/index.html) # With the configuration file you can specify different log files for # different components of the program. # note, that if not given everything will be logged to the console. # note: if the filename ends with .xml, libconfig tries to load the # configuration in xml-format, which allows even more tweaks. #logconfig = "solarpowerlog_lib4cxx.conf"; #logconfig = "solarpowerlog_lib4cxx.xml"; }; # This section declares the inverters. inverter : { inverters = ( { # (REQUIRED) Name of the Inverter (Will be used as reference in the # programm) name = "Inverter_1"; # (REQUIRED) "Human Readable Description of the fine piece of hardware. # Not interpreted by this software" description = "A inverter inverting"; # (REQUIRED) Selects the family of the inverters. # (In SW-Speak: Specifies the family of factory to generate. # The generated factory will instanciate the correct model class # specified by "model" manufacturer = "SPUTNIK_ENGINEERING"; # (REQUIRED) Which model we about to talk too # Your choice is at the moment "S-Series" or "Simulator". # (To obtain the list of choice, comment this line and run # solarpowerlog -- it will tell you the valid options.) model ="S-Series"; # Disable commands used for 3-phase invertes # This optional command reduced the amount of commands sent to the inverter # by the ones specific for 3-phase inverters. # (Even single-phase inverters answeres those. Benefit is just less "traffic" # to the invertes. # defaults to false, "not disabled", say "true" to disable the commands. disable_3phase_commands = false # Communication address of the inverter (as set in the communication # menu of the inverter) commadr = 1; # Adress to use as "our" adress for communication # if not given it will defaults to 0xfb.. # you should not need to change this value. ownadr = 0xfb; # How often should the inverter be queried? # The value specifies how long the class will *wait* # after one query has been answered. # The value can be a float. # This value is optional and defaults to 5 seconds queryinterval= 5; # mode of comm (planned: # "TCP/IP", Ethernet # "RS485, RS485 # "RS485overTCPIP" Daisy-Chained modules with one over ethernet, # the others chained to the rs485 output. comms = "TCP/IP"; # TCP/IP options. Address and Port. # All the options regarding the tcp communicatin starting with "tcp". # See the documentation of the comm methods for a list. # Port can also be a so-called "well-known-port" named as string. # (Example: tcpport="http") tcpadr = "192.168.0.20"; tcpport = "12345"; # (optional,depreciated) # Use this timeout value for all TCP/IP transcations. # Usually this parameter is set by the inverters at runtime, # so this parameter might not be considered. # It is depreciated and will be removed in a later # release of solarpowerlog. # default: 3 seconds (value: 3000) tcptimeout = 3000; } , #### second inverter #### { # please see the first inverter example for explanations. name = "Inverter_2"; description = "A second inverter inverting"; manufacturer = "SPUTNIK_ENGINEERING"; model ="S-Series"; comms = "TCP/IP"; tcpadr = "192.168.0.202"; tcpport = "12345"; tcptimeout = 3000; commadr = 2; ownadr = 0xfb; queryinterval=5; } ); }; # this section declares the filters and the loggers. # this example file shows the usage of a DumbDumper and CVS Dumper # We hook up: # Inverter_1 # one DumbDumper to Inverter_1, and one CVS Dumper # Inverter_2: # one CVS Dumper logger: { loggers = ( { # like inverters, loggers needs a name for identifaction. (REQUIRED) name = "Simple Dumper 1"; # and it solarpowerlog needs to know the type of the filter/logger # (REQUIRED) type = "DumbDumper"; # The filter/logger needs to know which data to use. This REQUIRED # parameter must match the name of an inverter of an logger/filter # note: the order is important -- do not refer to a logger/filter # which is declared later ("down" this file) datasource = "Inverter_1"; # now the logger-specific parameters: # Yes, it should clean the screen before dumping # (optional, defaults to false (off) # use true to enable it. clearscreen = true; }, { # 2nd example: hooking up a CVS_Writer to the Inverter_1 name = "CVS_Inverter_1"; type = "CVSWriter"; datasource = "Inverter_1"; # note, in this example you could also refer to the simple dumper 1 # so you could also write # this is important in cases where you "plug" some filters which # modifies some results... # datasource = "Simple Dumper 1"; # Again, the CVS writer has some parameter -- also see the wiki... # Where should we write the file? # (If rotate is true, %s will be replaced by the date # of the file, formated after ISO 8601: YYYY-MM-DD ) # Note: If you proved the directory by e.g cron, you can # also write the %s in the path. logfile="/tmp/Inverter1_%s.csv"; # if set to true, we'll rotate the file at midnight? (create new # file at midnight) # Otherwise we just append until end of times.... rotate=true; # What should we log? # Valid is "all" as string to log everything the inverter has to # offer. Please read the docs for an important notes on this option # (The generated CSV will notbe RFC 4180 compliant, as we change # header in mid-file and the order of the fields may change after # you restart solarpowerlog) # data2log="all"; # Instead of "all" you can specify an array with the data you want: # To get a list of known data, you can first try "all" and look at # the logged data. data2log= [ "AC grid feeding current (A)", "AC grid voltage (V)", "Current Grid Feeding Power", "DC current in (A)", "DC voltage in (V)", "Data Query Interval", "Data Validity", "Energy produced cumulated all time (kWh)", "Energy produced this month (kWh)", "Energy produced this year (kWh)", "Energy produced today (kWh)", "Inverter Overall Status", "Inverter Power On Hours", "Inverter Temperature (C)", "Net frequency (Hz)" ]; } , { name = "CVS_Inverter_2"; type = "CVSWriter"; datasource = "Inverter_2"; logfile="/tmp/Inverter2_%s.csv"; rotate=true; data2log="all"; #data2log= [ # "Current Grid Feeding Power", # "Energy produced today (kWh)", # "Energy produced this month (kWh)", # "DC voltage in (V)" # ]; # } ); }; solarpowerlog-solarpowerlog-0.26/example_confs/solarpowerlog_complex.conf000066400000000000000000000153131444065341000273610ustar00rootroot00000000000000# This is coldtobi's c onfiguation file for solarpowerlog. # 3 Inverters, feed data to a CVS logger each for data storage and a CVS logger each to store data for the HTML Writer # Of course, there is one HTML Writer for each Inverter. # Please note, the file is parsed by libconfig. application: { # what debuglevel should be used if not specified by any component # Choose out of ALL, TRACE, DEBUG, INFO, WARN, ERROR ,FATAL and NONE # optional. If not given, it defaults to "ERROR" # Note, if specified a logconfig file, the config file can override this # setting. dbglevel = "FATAL"; }; inverter : { inverters = ( { name = "Inverter_1"; description = "A Inverter inverting"; manufacturer = "SPUTNIK_ENGINEERING"; model ="S-Series"; comms = "TCP/IP"; tcpadr = "10.243.48.201"; tcpport = "12345"; tcptimeout = 3000; commadr = 1; ownadr = 0xfb; queryinterval= 5; }, { name = "Inverter_2"; description = "A Inverter inverting"; manufacturer = "SPUTNIK_ENGINEERING"; model ="S-Series"; comms = "TCP/IP"; tcpadr = "10.243.48.202"; tcpport = "12345"; tcptimeout = 3000; commadr = 1; ownadr = 0xfb; queryinterval=5; }, { name = "Inverter_3"; description = "A Inverter inverting"; manufacturer = "SPUTNIK_ENGINEERING"; model ="S-Series"; comms = "TCP/IP"; tcpadr = "10.243.48.203"; tcpport = "12345"; tcptimeout = 3000; commadr = 1; ownadr = 0xfb; queryinterval=5; } ); }; logger: { loggers = ( { name = "CVS_Inverter_1"; type = "CVSWriter"; datasource = "Inverter_1"; logfile = "/home/tobi/solar/Inverter1_%s.csv"; rotate = true; data2log = [ "AC grid feeding current (A)", "AC grid voltage (V)", "Current Grid Feeding Power", "DC current in (A)", "DC voltage in (V)", "Energy produced accumulated all time (kWh)", "Energy produced this month (kWh)", "Energy produced this year (kWh)", "Energy produced today (kWh)", "Inverter Overall Status", "Inverter Temperature (C)", "Net frequency (Hz)" ]; }, { name = "CVS_Inverter_2"; type = "CVSWriter"; datasource = "Inverter_2"; logfile="/home/tobi/solar/Inverter2_%s.csv"; rotate=true; data2log= [ "AC grid feeding current (A)", "AC grid voltage (V)", "Current Grid Feeding Power", "DC current in (A)", "DC voltage in (V)", "Energy produced accumulated all time (kWh)", "Energy produced this month (kWh)", "Energy produced this year (kWh)", "Energy produced today (kWh)", "Inverter Overall Status", "Inverter Temperature (C)", "Net frequency (Hz)" ]; }, { name = "CVS_Inverter_3"; type = "CVSWriter"; datasource = "Inverter_3"; logfile="/home/tobi/solar/Inverter3_%s.csv"; rotate=true; data2log= [ "AC grid feeding current (A)", "AC grid voltage (V)", "Current Grid Feeding Power", "DC current in (A)", "DC voltage in (V)", "Energy produced accumulated all time (kWh)" "Energy produced this month (kWh)", "Energy produced this year (kWh)", "Energy produced today (kWh)", "Inverter Overall Status", "Inverter Temperature (C)", "Net frequency (Hz)" ]; }, { name = "CVS_Inverter_HTML1"; type = "CVSWriter"; datasource = "Inverter_1"; logfile="/var/www/spl/Inverter1_%s.csv"; rotate=true; data2log= [ "Current Grid Feeding Power", "Energy produced today (kWh)" ]; flush_file_buffer_immediatly = true; }, { name = "CVS_Inverter_HTML2"; type = "CVSWriter"; datasource = "Inverter_2"; logfile="/var/www/spl/Inverter2_%s.csv"; rotate=true; data2log= [ "Current Grid Feeding Power", "Energy produced today (kWh)" ]; flush_file_buffer_immediatly = true; }, { name = "CVS_Inverter_HTML3"; type = "CVSWriter"; datasource = "Inverter_3"; logfile="/var/www/spl/Inverter3_%s.csv"; rotate=true; data2log= [ "Current Grid Feeding Power", "Energy produced today (kWh)" ]; flush_file_buffer_immediatly = true; }, { name = "HTMLWriter_Inverter1"; type = "HTMLWriter"; datasource = "CVS_Inverter_HTML1"; generate_template = true; generate_template_dir = "/tmp"; writeevery=60; htmlfile="/var/www/solarpowerlog-i1-%s.html"; templatefile="/etc/solarpowerlog/htmlwritertemplate/test.tmpl"; formatters = ( # [ what_capability, formating_operation, where_to_store ], [ "CSVDumper::Filename", "stripwebroot", "" , "/var/www" ], [ "CSVDumper::LoggedCaps", "searchcvsentry", "powernow", "Current Grid Feeding Power" ], [ "CSVDumper::LoggedCaps", "searchcvsentry", "kwhtoday", "Energy produced today (kWh)" ] ); }, { name = "HTMLWriter_Inverter2"; type = "HTMLWriter"; datasource = "CVS_Inverter_HTML2"; generate_template = false; generate_template_dir = "/tmp"; writeevery=60; htmlfile="/var/www/solarpowerlog-i2-%s.html"; templatefile="/etc/solarpowerlog/htmlwritertemplate/test.tmpl"; formatters = ( # [ what_capability, formating_operation, where_to_store ], [ "CSVDumper::Filename", "stripwebroot", "" , "/var/www" ], [ "CSVDumper::LoggedCaps", "searchcvsentry", "powernow", "Current Grid Feeding Power" ], [ "CSVDumper::LoggedCaps", "searchcvsentry", "kwhtoday", "Energy produced today (kWh)" ] ); }, { name = "HTMLWriter_Inverter3"; type = "HTMLWriter"; datasource = "CVS_Inverter_HTML3"; generate_template = false; generate_template_dir = "/tmp"; writeevery=60; htmlfile="/var/www/solarpowerlog-i3-%s.html"; templatefile="/etc/solarpowerlog/htmlwritertemplate/test.tmpl"; formatters = ( # [ what_capability, formating_operation, where_to_store ], [ "CSVDumper::Filename", "stripwebroot", "" , "/var/www" ], [ "CSVDumper::LoggedCaps", "searchcvsentry", "powernow", "Current Grid Feeding Power" ], [ "CSVDumper::LoggedCaps", "searchcvsentry", "kwhtoday", "Energy produced today (kWh)" ] ); } ); }; solarpowerlog-solarpowerlog-0.26/example_confs/solarpowerlog_serial.conf000066400000000000000000000123051444065341000271670ustar00rootroot00000000000000# This sample configuation shows how to configure the serial communication # # The file is parsed by libconfig, so for the grammar see # http://www.hyperrealm.com/libconfig/ # The application section is for global configuration. application: { # what debuglevel should be used if not specified by any component # Choose out of ALL, TRACE, DEBUG, INFO, WARN, ERROR ,FATAL and NONE # optional. If not given, it defaults to "ERROR" # Note, if specified a logconfig file, the config file can override this # setting. dbglevel = "ALL"; # configuration file to load for liblog4cxx # see the liblog4cxx docs for details. # (http://http://logging.apache.org/log4cxx/index.html) # With the configuration file you can specify different log files for # different components of the program. # note, that if not given everything will be logged to the console. # note: if the filename ends with .xml, libconfig tries to load the # configuration in xml-format, which allows even more tweaks. #logconfig = "solarpowerlog_lib4cxx.conf"; #logconfig = "solarpowerlog_lib4cxx.xml"; }; # This section declares the inverters. inverter : { inverters = ( { # (REQUIRED) Name of the Inverter (Will be used as reference in the # programm) name = "Inverter_1"; # (REQUIRED) "Human Readable Description of the fine piece of hardware. # Not interpreted by this software" description = "A inverter inverting"; # (REQUIRED) Selects the family of the inverters. # (In SW-Speak: Specifies the family of factory to generate. # The generated factory will instanciate the correct model class # specified by "model" manufacturer = "SPUTNIK_ENGINEERING"; # (REQUIRED) Which model we about to talk too # Your choice is at the moment "S-Series" or "Simulator". # (To obtain the list of choice, comment this line and run # solarpowerlog -- it will tell you the valid options.) model ="S-Series"; # Disable commands used for 3-phase invertes # This optional command reduced the amount of commands sent to the inverter # by the ones specific for 3-phase inverters. # (Even single-phase inverters answeres those. Benefit is just less "traffic" # to the invertes. # defaults to false, "not disabled", say "true" to disable the commands. disable_3phase_commands = false # Communication address of the inverter (as set in the communication # menu of the inverter) commadr = 1; # Adress to use as "our" adress for communication # if not given it will defaults to 0xfb.. # you should not need to change this value. ownadr = 0xfb; # How often should the inverter be queried? # The value specifies how long the class will *wait* # after one query has been answered. # The value can be a float. # This value is optional and defaults to 5 seconds queryinterval= 5; # mode of comm (planned: # "TCP/IP", Ethernet # "RS485, RS485 # "RS485overTCPIP" Daisy-Chained modules with one over ethernet, # the others chained to the rs485 output. comms = "RS2xx"; # (Required) Specifies the serial port to be used. serial_serialportname ="/dev/ttyUSB0"; # (Required) The baudrate to be used. serial_baudrate=9600; # (Optional) port parameters, default is 8N1 # -- 8 bits per symbol (valid 5 to 8) # -- N no parity (valid is E (even), O (odd) and N (none) # -- 1 is the number of stopp bits. choose between 1 and 2 #serial_portparameters="8N1"; # (Optional) flow contol / handshake, default is "none" # You have the options "none", "software" and "hardware" #serial_flowcontrol="none"; # (Optional, depreciated) # Sets the default timeout value in milliseconds. Please note that # this parameter is usually overriden by inverter settings, so this # option is depcreciated and will be removed in an sussequent release # of solarpowerlog. # Defaults to 3000 (3 seconds) #serial_timeout= 3000; # (Optional) # Specifies the timeout to be used between two bytes to detect the # end of a transmission. # By default, when specifing 0 as value, it is automatically # determined from the baudrate, but with a minimum of 50ms. # If set too low, the operating system might not be able to cope. # serial_interbytetimeout = 0; } ); }; # this section declares the filters and the loggers. # this example file shows the usage of a DumbDumper and CVS Dumper # We hook up: # Inverter_1 # one DumbDumper to Inverter_1, and one CVS Dumper # Inverter_2: # one CVS Dumper logger: { loggers = ( ); }; solarpowerlog-solarpowerlog-0.26/m4/000077500000000000000000000000001444065341000175565ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/m4/boost.m4000066400000000000000000001756231444065341000211640ustar00rootroot00000000000000# boost.m4: Locate Boost headers and libraries for autoconf-based projects. # Copyright (C) 2007-2011, 2014 Benoit Sigoure # # 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. # # Additional permission under section 7 of the GNU General Public # License, version 3 ("GPLv3"): # # If you convey this file as part of a work that contains a # configuration script generated by Autoconf, you may do so under # terms of your choice. # # 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 . m4_define([_BOOST_SERIAL], [m4_translit([ # serial 36 ], [# ], [])]) # Original sources can be found at http://github.com/tsuna/boost.m4 # You can fetch the latest version of the script by doing: # wget http://github.com/tsuna/boost.m4/raw/master/build-aux/boost.m4 # ------ # # README # # ------ # # This file provides several macros to use the various Boost libraries. # The first macro is BOOST_REQUIRE. It will simply check if it's possible to # find the Boost headers of a given (optional) minimum version and it will # define BOOST_CPPFLAGS accordingly. It will add an option --with-boost to # your configure so that users can specify non standard locations. # If the user's environment contains BOOST_ROOT and --with-boost was not # specified, --with-boost=$BOOST_ROOT is implicitly used. # For more README and documentation, go to http://github.com/tsuna/boost.m4 # Note: THESE MACROS ASSUME THAT YOU USE LIBTOOL. If you don't, don't worry, # simply read the README, it will show you what to do step by step. m4_pattern_forbid([^_?(BOOST|Boost)_]) # _BOOST_SED_CPP(SED-PROGRAM, PROGRAM, # [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) # -------------------------------------------------------- # Same as AC_EGREP_CPP, but leave the result in conftest.i. # # SED-PROGRAM is *not* overquoted, as in AC_EGREP_CPP. It is expanded # in double-quotes, so escape your double quotes. # # It could be useful to turn this into a macro which extracts the # value of any macro. m4_define([_BOOST_SED_CPP], [AC_LANG_PUSH([C++])dnl AC_LANG_PREPROC_REQUIRE()dnl AC_REQUIRE([AC_PROG_SED])dnl AC_LANG_CONFTEST([AC_LANG_SOURCE([[$2]])]) AS_IF([dnl eval is necessary to expand ac_cpp. dnl Ultrix and Pyramid sh refuse to redirect output of eval, so use subshell. dnl Beware of Windows end-of-lines, for instance if we are running dnl some Windows programs under Wine. In that case, boost/version.hpp dnl is certainly using "\r\n", but the regular Unix shell will only dnl strip `\n' with backquotes, not the `\r'. This results in dnl boost_cv_lib_version='1_37\r' for instance, which breaks dnl everything else. dnl Cannot use 'dnl' after [$4] because a trailing dnl may break AC_CACHE_CHECK dnl dnl Beware that GCC 5, when expanding macros, may embed # line directives dnl a within single line: dnl dnl # 1 "conftest.cc" dnl # 1 "" dnl # 1 "" dnl # 1 "conftest.cc" dnl # 1 "/opt/local/include/boost/version.hpp" 1 3 dnl # 2 "conftest.cc" 2 dnl boost-lib-version = dnl # 2 "conftest.cc" 3 dnl "1_56" dnl dnl So get rid of the # and empty lines, and glue the remaining ones together. (eval "$ac_cpp conftest.$ac_ext") 2>&AS_MESSAGE_LOG_FD | grep -v '#' | grep -v '^[[[:space:]]]*$' | tr -d '\r' | tr -s '\n' ' ' | $SED -n -e "$1" >conftest.i 2>&1], [$3], [$4]) rm -rf conftest* AC_LANG_POP([C++])dnl ])# _BOOST_SED_CPP # BOOST_REQUIRE([VERSION], [ACTION-IF-NOT-FOUND]) # ----------------------------------------------- # Look for Boost. If version is given, it must either be a literal of the form # "X.Y.Z" where X, Y and Z are integers (the ".Z" part being optional) or a # variable "$var". # Defines the value BOOST_CPPFLAGS. This macro only checks for headers with # the required version, it does not check for any of the Boost libraries. # On # success, defines HAVE_BOOST. On failure, calls the optional # ACTION-IF-NOT-FOUND action if one was supplied. # Otherwise aborts with an error message. AC_DEFUN_ONCE([BOOST_REQUIRE], [AC_REQUIRE([AC_PROG_CXX])dnl AC_REQUIRE([AC_PROG_GREP])dnl echo "$as_me: this is boost.m4[]_BOOST_SERIAL" >&AS_MESSAGE_LOG_FD boost_save_IFS=$IFS boost_version_req=$1 IFS=. set x $boost_version_req 0 0 0 IFS=$boost_save_IFS shift boost_version_req=`expr "$[1]" '*' 100000 + "$[2]" '*' 100 + "$[3]"` boost_version_req_string=$[1].$[2].$[3] AC_ARG_WITH([boost], [AS_HELP_STRING([--with-boost=DIR], [prefix of Boost $1 @<:@guess@:>@])])dnl AC_ARG_VAR([BOOST_ROOT],[Location of Boost installation])dnl # If BOOST_ROOT is set and the user has not provided a value to # --with-boost, then treat BOOST_ROOT as if it the user supplied it. if test x"$BOOST_ROOT" != x; then if test x"$with_boost" = x; then AC_MSG_NOTICE([Detected BOOST_ROOT; continuing with --with-boost=$BOOST_ROOT]) with_boost=$BOOST_ROOT else AC_MSG_NOTICE([Detected BOOST_ROOT=$BOOST_ROOT, but overridden by --with-boost=$with_boost]) fi fi AC_SUBST([DISTCHECK_CONFIGURE_FLAGS], ["$DISTCHECK_CONFIGURE_FLAGS '--with-boost=$with_boost'"])dnl boost_save_CPPFLAGS=$CPPFLAGS AC_CACHE_CHECK([for Boost headers version >= $boost_version_req_string], [boost_cv_inc_path], [boost_cv_inc_path=no AC_LANG_PUSH([C++])dnl m4_pattern_allow([^BOOST_VERSION$])dnl AC_LANG_CONFTEST([AC_LANG_PROGRAM([[#include #if !defined BOOST_VERSION # error BOOST_VERSION is not defined #elif BOOST_VERSION < $boost_version_req # error Boost headers version < $boost_version_req #endif ]])]) # If the user provided a value to --with-boost, use it and only it. case $with_boost in #( ''|yes) set x '' /opt/local/include /usr/local/include /opt/include \ /usr/include C:/Boost/include;; #( *) set x "$with_boost/include" "$with_boost";; esac shift for boost_dir do # Without --layout=system, Boost (or at least some versions) installs # itself in /include/boost-. This inner loop helps to # find headers in such directories. # # Any ${boost_dir}/boost-x_xx directories are searched in reverse version # order followed by ${boost_dir}. The final '.' is a sentinel for # searching $boost_dir" itself. Entries are whitespace separated. # # I didn't indent this loop on purpose (to avoid over-indented code) boost_layout_system_search_list=`cd "$boost_dir" 2>/dev/null \ && ls -1 | "${GREP}" '^boost-' | sort -rn -t- -k2 \ && echo .` for boost_inc in $boost_layout_system_search_list do if test x"$boost_inc" != x.; then boost_inc="$boost_dir/$boost_inc" else boost_inc="$boost_dir" # Uses sentinel in boost_layout_system_search_list fi if test x"$boost_inc" != x; then # We are going to check whether the version of Boost installed # in $boost_inc is usable by running a compilation that # #includes it. But if we pass a -I/some/path in which Boost # is not installed, the compiler will just skip this -I and # use other locations (either from CPPFLAGS, or from its list # of system include directories). As a result we would use # header installed on the machine instead of the /some/path # specified by the user. So in that precise case (trying # $boost_inc), make sure the version.hpp exists. # # Use test -e as there can be symlinks. test -e "$boost_inc/boost/version.hpp" || continue CPPFLAGS="$CPPFLAGS -I$boost_inc" fi AC_COMPILE_IFELSE([], [boost_cv_inc_path=yes], [boost_cv_version=no]) if test x"$boost_cv_inc_path" = xyes; then if test x"$boost_inc" != x; then boost_cv_inc_path=$boost_inc fi break 2 fi done done AC_LANG_POP([C++])dnl ]) case $boost_cv_inc_path in #( no) boost_errmsg="cannot find Boost headers version >= $boost_version_req_string" m4_if([$2], [], [AC_MSG_ERROR([$boost_errmsg])], [AC_MSG_NOTICE([$boost_errmsg])]) $2 ;;#( yes) BOOST_CPPFLAGS= ;;#( *) AC_SUBST([BOOST_CPPFLAGS], ["-I$boost_cv_inc_path"])dnl ;; esac if test x"$boost_cv_inc_path" != xno; then AC_DEFINE([HAVE_BOOST], [1], [Defined if the requested minimum BOOST version is satisfied]) AC_CACHE_CHECK([for Boost's header version], [boost_cv_lib_version], [m4_pattern_allow([^BOOST_LIB_VERSION$])dnl _BOOST_SED_CPP([[/^boost-lib-version = /{s///;s/[\" ]//g;p;q;}]], [#include boost-lib-version = BOOST_LIB_VERSION], [boost_cv_lib_version=`cat conftest.i`])]) # e.g. "134" for 1_34_1 or "135" for 1_35 boost_major_version=`echo "$boost_cv_lib_version" | sed 's/_//;s/_.*//'` case $boost_major_version in #( '' | *[[!0-9]]*) AC_MSG_ERROR([invalid value: boost_major_version='$boost_major_version']) ;; esac fi CPPFLAGS=$boost_save_CPPFLAGS ])# BOOST_REQUIRE # BOOST_STATIC() # -------------- # Add the "--enable-static-boost" configure argument. If this argument is given # on the command line, static versions of the libraries will be looked up. AC_DEFUN([BOOST_STATIC], [AC_ARG_ENABLE([static-boost], [AS_HELP_STRING([--enable-static-boost], [Prefer the static boost libraries over the shared ones [no]])], [enable_static_boost=yes], [enable_static_boost=no])])# BOOST_STATIC # BOOST_FIND_HEADER([HEADER-NAME], [ACTION-IF-NOT-FOUND], [ACTION-IF-FOUND]) # -------------------------------------------------------------------------- # Wrapper around AC_CHECK_HEADER for Boost headers. Useful to check for # some parts of the Boost library which are only made of headers and don't # require linking (such as Boost.Foreach). # # Default ACTION-IF-NOT-FOUND: Fail with a fatal error unless Boost couldn't be # found in the first place, in which case by default a notice is issued to the # user. Presumably if we haven't died already it's because it's OK to not have # Boost, which is why only a notice is issued instead of a hard error. # # Default ACTION-IF-FOUND: define the preprocessor symbol HAVE_ in # case of success # (where HEADER-NAME is written LIKE_THIS, e.g., # HAVE_BOOST_FOREACH_HPP). AC_DEFUN([BOOST_FIND_HEADER], [AC_REQUIRE([BOOST_REQUIRE])dnl if test x"$boost_cv_inc_path" = xno; then m4_default([$2], [AC_MSG_NOTICE([Boost not available, not searching for $1])]) else AC_LANG_PUSH([C++])dnl boost_save_CPPFLAGS=$CPPFLAGS CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" AC_CHECK_HEADER([$1], [m4_default([$3], [AC_DEFINE(AS_TR_CPP([HAVE_$1]), [1], [Define to 1 if you have <$1>])])], [m4_default([$2], [AC_MSG_ERROR([cannot find $1])])]) CPPFLAGS=$boost_save_CPPFLAGS AC_LANG_POP([C++])dnl fi ])# BOOST_FIND_HEADER # BOOST_FIND_LIBS([COMPONENT-NAME], [CANDIDATE-LIB-NAMES], # [PREFERRED-RT-OPT], [HEADER-NAME], [CXX-TEST], # [CXX-PROLOGUE], [CXX-POST-INCLUDE-PROLOGUE], # [ERROR_ON_UNUSABLE]) # -------------------------------------------------------------- # Look for the Boost library COMPONENT-NAME (e.g., `thread', for # libboost_thread) under the possible CANDIDATE-LIB-NAMES (e.g., # "thread_win32 thread"). Check that HEADER-NAME works and check that # libboost_LIB-NAME can link with the code CXX-TEST. The optional # argument CXX-PROLOGUE can be used to include some C++ code before # the `main' function. The CXX-POST-INCLUDE-PROLOGUE can be used to # include some code before the `main' function, but after the # `#include '. # # Invokes BOOST_FIND_HEADER([HEADER-NAME]) (see above). # # Boost libraries typically come compiled with several flavors (with different # runtime options) so PREFERRED-RT-OPT is the preferred suffix. A suffix is one # or more of the following letters: sgdpn (in that order). s = static # runtime, d = debug build, g = debug/diagnostic runtime, p = STLPort build, # n = (unsure) STLPort build without iostreams from STLPort (it looks like `n' # must always be used along with `p'). Additionally, PREFERRED-RT-OPT can # start with `mt-' to indicate that there is a preference for multi-thread # builds. Some sample values for PREFERRED-RT-OPT: (nothing), mt, d, mt-d, gdp # ... If you want to make sure you have a specific version of Boost # (eg, >= 1.33) you *must* invoke BOOST_REQUIRE before this macro. # # ERROR_ON_UNUSABLE can be set to "no" if the caller does not want their # configure to fail AC_DEFUN([BOOST_FIND_LIBS], [AC_REQUIRE([BOOST_REQUIRE])dnl AC_REQUIRE([_BOOST_FIND_COMPILER_TAG])dnl AC_REQUIRE([BOOST_STATIC])dnl AC_REQUIRE([_BOOST_GUESS_WHETHER_TO_USE_MT])dnl if test x"$boost_cv_inc_path" = xno; then AC_MSG_NOTICE([Boost not available, not searching for the Boost $1 library]) else dnl The else branch is huge and wasn't indented on purpose. AC_LANG_PUSH([C++])dnl AS_VAR_PUSHDEF([Boost_lib], [boost_cv_lib_$1])dnl AS_VAR_PUSHDEF([Boost_lib_LDFLAGS], [boost_cv_lib_$1_LDFLAGS])dnl AS_VAR_PUSHDEF([Boost_lib_LDPATH], [boost_cv_lib_$1_LDPATH])dnl AS_VAR_PUSHDEF([Boost_lib_LIBS], [boost_cv_lib_$1_LIBS])dnl AS_IF([test x"$8" = "xno"], [not_found_header='true']) BOOST_FIND_HEADER([$4], [$not_found_header]) boost_save_CPPFLAGS=$CPPFLAGS CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" AC_CACHE_CHECK([for the Boost $1 library], [Boost_lib], [_BOOST_FIND_LIBS($@)]) case $Boost_lib in #( (yes) _AC_MSG_LOG_CONFTEST AC_DEFINE(AS_TR_CPP([HAVE_BOOST_$1]), [1], [Defined if the Boost $1 library is available])dnl AC_SUBST(AS_TR_CPP([BOOST_$1_LDFLAGS]), [$Boost_lib_LDFLAGS])dnl AC_SUBST(AS_TR_CPP([BOOST_$1_LDPATH]), [$Boost_lib_LDPATH])dnl AC_SUBST([BOOST_LDPATH], [$Boost_lib_LDPATH])dnl AC_SUBST(AS_TR_CPP([BOOST_$1_LIBS]), [$Boost_lib_LIBS])dnl ;; (no) _AC_MSG_LOG_CONFTEST AS_IF([test x"$8" != "xno"], [ AC_MSG_ERROR([cannot find flags to link with the Boost $1 library (libboost-$1)]) ]) ;; esac CPPFLAGS=$boost_save_CPPFLAGS AS_VAR_POPDEF([Boost_lib])dnl AS_VAR_POPDEF([Boost_lib_LDFLAGS])dnl AS_VAR_POPDEF([Boost_lib_LDPATH])dnl AS_VAR_POPDEF([Boost_lib_LIBS])dnl AC_LANG_POP([C++])dnl fi ]) # BOOST_FIND_LIB([LIB-NAME], # [PREFERRED-RT-OPT], [HEADER-NAME], [CXX-TEST], # [CXX-PROLOGUE], [CXX-POST-INCLUDE-PROLOGUE], # [ERROR_ON_UNUSABLE]) # -------------------------------------------------------------- # Backward compatibility wrapper for BOOST_FIND_LIBS. # ERROR_ON_UNUSABLE can be set to "no" if the caller does not want their # configure to fail AC_DEFUN([BOOST_FIND_LIB], [BOOST_FIND_LIBS([$1], $@)]) # _BOOST_FIND_LIBS([LIB-NAME], [CANDIDATE-LIB-NAMES], # [PREFERRED-RT-OPT], [HEADER-NAME], [CXX-TEST], # [CXX-PROLOGUE], [CXX-POST-INCLUDE-PROLOGUE], # [ERROR_ON_UNUSABLE]) # -------------------------------------------------------------- # Real implementation of BOOST_FIND_LIBS: rely on these local macros: # Boost_lib, Boost_lib_LDFLAGS, Boost_lib_LDPATH, Boost_lib_LIBS # # The algorithm is as follows: first look for a given library name # according to the user's PREFERRED-RT-OPT. For each library name, we # prefer to use the ones that carry the tag (toolset name). Each # library is searched through the various standard paths were Boost is # usually installed. If we can't find the standard variants, we try # to enforce -mt (for instance on MacOSX, libboost_thread.dylib # doesn't exist but there's -obviously- libboost_thread-mt.dylib). # # ERROR_ON_UNUSABLE can be set to "no" if the caller does not want their # configure to fail AC_DEFUN([_BOOST_FIND_LIBS], [Boost_lib=no case "$3" in #( (mt | mt-) boost_mt=-mt; boost_rtopt=;; #( (mt* | mt-*) boost_mt=-mt; boost_rtopt=`expr "X$3" : 'Xmt-*\(.*\)'`;; #( (*) boost_mt=; boost_rtopt=$3;; esac if test $enable_static_boost = yes; then boost_rtopt="s$boost_rtopt" fi # Find the proper debug variant depending on what we've been asked to find. case $boost_rtopt in #( (*d*) boost_rt_d=$boost_rtopt;; #( (*[[sgpn]]*) # Insert the `d' at the right place (in between `sg' and `pn') boost_rt_d=`echo "$boost_rtopt" | sed 's/\(s*g*\)\(p*n*\)/\1\2/'`;; #( (*) boost_rt_d='-d';; esac # If the PREFERRED-RT-OPT are not empty, prepend a `-'. test -n "$boost_rtopt" && boost_rtopt="-$boost_rtopt" $boost_guess_use_mt && boost_mt=-mt # Look for the abs path the static archive. # $libext is computed by Libtool but let's make sure it's non empty. test -z "$libext" && AC_MSG_ERROR([the libext variable is empty, did you invoke Libtool?]) boost_save_ac_objext=$ac_objext # Generate the test file. AC_LANG_CONFTEST([AC_LANG_PROGRAM([$7 #include <$4> $6], [$5])]) dnl Optimization hacks: compiling C++ is slow, especially with Boost. What dnl we're trying to do here is guess the right combination of link flags dnl (LIBS / LDFLAGS) to use a given library. This can take several dnl iterations before it succeeds and is thus *very* slow. So what we do dnl instead is that we compile the code first (and thus get an object file, dnl typically conftest.o). Then we try various combinations of link flags dnl until we succeed to link conftest.o in an executable. The problem is dnl that the various TRY_LINK / COMPILE_IFELSE macros of Autoconf always dnl remove all the temporary files including conftest.o. So the trick here dnl is to temporarily change the value of ac_objext so that conftest.o is dnl preserved accross tests. This is obviously fragile and I will burn in dnl hell for not respecting Autoconf's documented interfaces, but in the dnl mean time, it optimizes the macro by a factor of 5 to 30. dnl Another small optimization: the first argument of AC_COMPILE_IFELSE left dnl empty because the test file is generated only once above (before we dnl start the for loops). AC_COMPILE_IFELSE([], [ac_objext=do_not_rm_me_plz], [AS_IF([test x"$8" != x"no"], [ AC_MSG_ERROR([cannot compile a test that uses Boost $1]) ]) ]) ac_objext=$boost_save_ac_objext boost_failed_libs= # Don't bother to ident the following nested for loops, only the 2 # innermost ones matter. for boost_lib_ in $2; do for boost_tag_ in -$boost_cv_lib_tag ''; do for boost_ver_ in -$boost_cv_lib_version ''; do for boost_mt_ in $boost_mt -mt ''; do for boost_rtopt_ in $boost_rtopt '' -d; do for boost_full_suffix in \ $boost_last_suffix \ x$boost_tag_$boost_mt_$boost_rtopt_$boost_ver_ \ x$boost_tag_$boost_rtopt_$boost_ver_ \ x$boost_tag_$boost_mt_$boost_ver_ \ x$boost_tag_$boost_ver_ do boost_real_suffix=`echo "$boost_full_suffix" | sed 's/^x//'` boost_lib="boost_$boost_lib_$boost_real_suffix" # Avoid testing twice the same lib case $boost_failed_libs in #( (*@$boost_lib@*) continue;; esac # If with_boost is empty, we'll search in /lib first, which is not quite # right so instead we'll try to a location based on where the headers are. boost_tmp_lib=$with_boost test x"$with_boost" = x && boost_tmp_lib=${boost_cv_inc_path%/include} for boost_ldpath in "$boost_tmp_lib/lib" '' \ /opt/local/lib* /usr/local/lib* /opt/lib* /usr/lib* \ "$with_boost" C:/Boost/lib /lib* do # Don't waste time with directories that don't exist. if test x"$boost_ldpath" != x && test ! -e "$boost_ldpath"; then continue fi boost_save_LDFLAGS=$LDFLAGS # Are we looking for a static library? case $boost_ldpath:$boost_rtopt_ in #( (*?*:*s*) # Yes (Non empty boost_ldpath + s in rt opt) Boost_lib_LIBS="$boost_ldpath/lib$boost_lib.$libext" test -e "$Boost_lib_LIBS" || continue;; #( (*) # No: use -lboost_foo to find the shared library. Boost_lib_LIBS="-l$boost_lib";; esac boost_save_LIBS=$LIBS LIBS="$Boost_lib_LIBS $LIBS" test x"$boost_ldpath" != x && LDFLAGS="$LDFLAGS -L$boost_ldpath" dnl First argument of AC_LINK_IFELSE left empty because the test file is dnl generated only once above (before we start the for loops). _BOOST_AC_LINK_IFELSE([], [Boost_lib=yes], [Boost_lib=no]) ac_objext=$boost_save_ac_objext LDFLAGS=$boost_save_LDFLAGS LIBS=$boost_save_LIBS if test x"$Boost_lib" = xyes; then # Check or used cached result of whether or not using -R or # -rpath makes sense. Some implementations of ld, such as for # Mac OSX, require -rpath but -R is the flag known to work on # other systems. https://github.com/tsuna/boost.m4/issues/19 AC_CACHE_VAL([boost_cv_rpath_link_ldflag], [case $boost_ldpath in '') # Nothing to do. boost_cv_rpath_link_ldflag= boost_rpath_link_ldflag_found=yes;; *) for boost_cv_rpath_link_ldflag in -Wl,-R, -Wl,-rpath,; do LDFLAGS="$boost_save_LDFLAGS -L$boost_ldpath $boost_cv_rpath_link_ldflag$boost_ldpath" LIBS="$Boost_lib_LIBS $boost_save_LIBS" _BOOST_AC_LINK_IFELSE([], [boost_rpath_link_ldflag_found=yes break], [boost_rpath_link_ldflag_found=no]) done ;; esac AS_IF([test "x$boost_rpath_link_ldflag_found" != "xyes"], [AC_MSG_ERROR([Unable to determine whether to use -R or -rpath])]) LDFLAGS=$boost_save_LDFLAGS LIBS=$boost_save_LIBS ]) test x"$boost_ldpath" != x && Boost_lib_LDFLAGS="-L$boost_ldpath $boost_cv_rpath_link_ldflag$boost_ldpath" Boost_lib_LDPATH="$boost_ldpath" boost_last_suffix="$boost_full_suffix" break 7 else boost_failed_libs="$boost_failed_libs@$boost_lib@" fi done done done done done done done # boost_lib_ rm -f conftest.$ac_objext ]) # --------------------------------------- # # Checks for the various Boost libraries. # # --------------------------------------- # # List of boost libraries: http://www.boost.org/libs/libraries.htm # The page http://beta.boost.org/doc/libs is useful: it gives the first release # version of each library (among other things). # BOOST_DEFUN(LIBRARY, CODE) # -------------------------- # Define BOOST_ as a macro that runs CODE. # # Use indir to avoid the warning on underquoted macro name given to AC_DEFUN. m4_define([BOOST_DEFUN], [m4_indir([AC_DEFUN], m4_toupper([BOOST_$1]), [m4_pushdef([BOOST_Library], [$1])dnl $2 m4_popdef([BOOST_Library])dnl ]) ]) # BOOST_ANY() # ------------ # Look for Boost.Any BOOST_DEFUN([Any], [BOOST_FIND_HEADER([boost/any.hpp])]) # BOOST_ARRAY() # ------------- # Look for Boost.Array BOOST_DEFUN([Array], [BOOST_FIND_HEADER([boost/array.hpp])]) # BOOST_ASIO() # ------------ # Look for Boost.Asio (new in Boost 1.35). BOOST_DEFUN([Asio], [AC_REQUIRE([BOOST_SYSTEM])dnl BOOST_FIND_HEADER([boost/asio.hpp])]) # BOOST_BIMAP() # ------------ # Look for Boost.Bimap BOOST_DEFUN([Bimap], [BOOST_FIND_HEADER([boost/bimap.hpp])]) # BOOST_ASSIGN() # ------------- # Look for Boost.Assign BOOST_DEFUN([Assign], [BOOST_FIND_HEADER([boost/assign.hpp])]) # BOOST_ATOMIC([PREFERRED-RT-OPT], [ERROR_ON_UNUSABLE]) # ------------------------------- # Look for Boost.Atomic. For the documentation of PREFERRED-RT-OPT, see the # documentation of BOOST_FIND_LIB above. BOOST_DEFUN([Atomic], [BOOST_FIND_LIB([atomic], [$1], [boost/atomic.hpp], [boost::atomic a;], [ ], [#ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_STDINT_H #include #endif], [$2]) ])# BOOST_ATOMIC # BOOST_BIND() # ------------ # Look for Boost.Bind. BOOST_DEFUN([Bind], [BOOST_FIND_HEADER([boost/bind.hpp])]) # BOOST_CAST() # ------------ # Look for Boost.Cast BOOST_DEFUN([Cast], [BOOST_FIND_HEADER([boost/cast.hpp])]) # BOOST_CHRONO([PREFERRED-RT-OPT], [ERROR_ON_UNUSABLE]) # -------------- # Look for Boost.Chrono. BOOST_DEFUN([Chrono], [# Do we have to check for Boost.System? This link-time dependency was # added as of 1.35.0. If we have a version <1.35, we must not attempt to # find Boost.System as it didn't exist by then. if test $boost_major_version -ge 135; then BOOST_SYSTEM([$1], [$2]) fi # end of the Boost.System check. boost_filesystem_save_LIBS=$LIBS boost_filesystem_save_LDFLAGS=$LDFLAGS m4_pattern_allow([^BOOST_SYSTEM_(LIBS|LDFLAGS)$])dnl LIBS="$LIBS $BOOST_SYSTEM_LIBS" LDFLAGS="$LDFLAGS $BOOST_SYSTEM_LDFLAGS" BOOST_FIND_LIB([chrono], [$1], [boost/chrono.hpp], [boost::chrono::thread_clock d;], [], [], [$2]) if test $enable_static_boost = yes && test $boost_major_version -ge 135; then BOOST_CHRONO_LIBS="$BOOST_CHRONO_LIBS $BOOST_SYSTEM_LIBS" fi LIBS=$boost_filesystem_save_LIBS LDFLAGS=$boost_filesystem_save_LDFLAGS ])# BOOST_CHRONO # BOOST_CONTEXT([PREFERRED-RT-OPT], [ERROR_ON_UNUSABLE]) # ----------------------------------- # Look for Boost.Context. For the documentation of PREFERRED-RT-OPT, see the # documentation of BOOST_FIND_LIB above. # # * This library was introduced in Boost 1.51.0 # * The signatures of make_fcontext() and jump_fcontext were changed in 1.56.0 # * A dependency on boost_thread appears in 1.57.0 # * The implementation details were moved to boost::context::detail in 1.61.0 # * 1.61 also introduces execution_context_v2, which is the "lowest common # denominator" for boost::context presence since then. # * boost::context::fiber was introduced in 1.69 and execution_context_v2 was # removed in 1.72 BOOST_DEFUN([Context], [boost_context_save_LIBS=$LIBS boost_context_save_LDFLAGS=$LDFLAGS if test $boost_major_version -ge 157; then BOOST_THREAD([$1], [$2]) m4_pattern_allow([^BOOST_THREAD_(LIBS|LDFLAGS)$])dnl LIBS="$LIBS $BOOST_THREAD_LIBS" LDFLAGS="$LDFLAGS $BOOST_THREAD_LDFLAGS" fi if test $boost_major_version -ge 169; then BOOST_FIND_LIB([context], [$1], [boost/context/fiber.hpp], [[ namespace ctx=boost::context; int a; ctx::fiber source{[&a](ctx::fiber&& sink){ a=0; int b=1; for(;;){ sink=std::move(sink).resume(); int next=a+b; a=b; b=next; } return std::move(sink); }}; for (int j=0;j<10;++j) { source=std::move(source).resume(); } return a == 34; ]], [], [], [$2]) elif test $boost_major_version -ge 161; then BOOST_FIND_LIB([context], [$1], [boost/context/execution_context_v2.hpp], [[ namespace ctx=boost::context; int res=0; int n=35; ctx::execution_context source( [n, &res](ctx::execution_context sink, int) mutable { int a=0; int b=1; while(n-->0){ auto result=sink(a); sink=std::move(std::get<0>(result)); auto next=a+b; a=b; b=next; } return sink; }); for(int i=0;i<10;++i){ auto result=source(i); source=std::move(std::get<0>(result)); res = std::get<1>(result); } return res == 34; ]], [], [], [$2]) else BOOST_FIND_LIB([context], [$1], [boost/context/fcontext.hpp],[[ // creates a stack void * stack_pointer = new void*[4096]; std::size_t const size = sizeof(void*[4096]); #if BOOST_VERSION <= 105100 ctx::make_fcontext(&fc, f); return ctx::jump_fcontext(&fcm, &fc, 3) == 6; #else fc = ctx::make_fcontext(stack_pointer, size, f); return ctx::jump_fcontext(&fcm, fc, 3) == 6; #endif ]],[dnl #include #if BOOST_VERSION <= 105100 namespace ctx = boost::ctx; static ctx::fcontext_t fcm, fc; static void f(intptr_t i) { ctx::jump_fcontext(&fc, &fcm, i * 2); } #elif BOOST_VERSION <= 105500 namespace ctx = boost::context; // context static ctx::fcontext_t fcm, *fc; // context-function static void f(intptr_t i) { ctx::jump_fcontext(fc, &fcm, i * 2); } #else namespace ctx = boost::context; // context static ctx::fcontext_t fcm, fc; // context-function static void f(intptr_t i) { ctx::jump_fcontext(&fc, fcm, i * 2); } #endif ], [], [], [$2]) fi LIBS=$boost_context_save_LIBS LDFLAGS=$boost_context_save_LDFLAGS ])# BOOST_CONTEXT # BOOST_CONVERSION() # ------------------ # Look for Boost.Conversion (cast / lexical_cast) BOOST_DEFUN([Conversion], [BOOST_FIND_HEADER([boost/cast.hpp]) BOOST_FIND_HEADER([boost/lexical_cast.hpp]) ])# BOOST_CONVERSION # BOOST_COROUTINE([PREFERRED-RT-OPT], [ERROR_ON_UNUSABLE]) # ----------------------------------- # Look for Boost.Coroutine. For the documentation of PREFERRED-RT-OPT, see the # documentation of BOOST_FIND_LIB above. This library was introduced in Boost # 1.53.0 BOOST_DEFUN([Coroutine], [ boost_coroutine_save_LIBS=$LIBS boost_coroutine_save_LDFLAGS=$LDFLAGS # Link-time dependency from coroutine to context BOOST_CONTEXT([$1], [$2]) # Starting from Boost 1.55 a dependency on Boost.System is added if test $boost_major_version -ge 155; then BOOST_SYSTEM([$1], [$2]) fi m4_pattern_allow([^BOOST_(CONTEXT|SYSTEM)_(LIBS|LDFLAGS)]) LIBS="$LIBS $BOOST_CONTEXT_LIBS $BOOST_SYSTEM_LIBS" LDFLAGS="$LDFLAGS $BOOST_CONTEXT_LDFLAGS" # in 1.53 coroutine was a header only library if test $boost_major_version -eq 153; then AS_IF([test x"$2" = "xno"], [not_found_header='true']) BOOST_FIND_HEADER([boost/coroutine/coroutine.hpp], [$not_found_header]) else BOOST_FIND_LIB([coroutine], [$1], [boost/coroutine/coroutine.hpp], [ #include #if BOOST_VERSION <= 105500 boost::coroutines::coroutine coro; coro.get(); #else boost::coroutines::asymmetric_coroutine::pull_type coro; coro.get(); #endif ], [], [], [$2]) fi # Link-time dependency from coroutine to context, existed only in 1.53, in 1.54 # coroutine doesn't use context from its headers but from its library. if test $boost_major_version -eq 153 || test $enable_static_boost = yes && test $boost_major_version -ge 154; then BOOST_COROUTINE_LIBS="$BOOST_COROUTINE_LIBS $BOOST_CONTEXT_LIBS" BOOST_COROUTINE_LDFLAGS="$BOOST_COROUTINE_LDFLAGS $BOOST_CONTEXT_LDFLAGS" fi if test $enable_static_boost = yes && test $boost_major_version -ge 155; then BOOST_COROUTINE_LIBS="$BOOST_COROUTINE_LIBS $BOOST_SYSTEM_LIBS" BOOST_COROUTINE_LDFLAGS="$BOOST_COROUTINE_LDFLAGS $BOOST_SYSTEM_LDFLAGS" fi LIBS=$boost_coroutine_save_LIBS LDFLAGS=$boost_coroutine_save_LDFLAGS ])# BOOST_COROUTINE # BOOST_CRC() # ----------- # Look for Boost.CRC BOOST_DEFUN([CRC], [BOOST_FIND_HEADER([boost/crc.hpp]) ])# BOOST_CRC # BOOST_DATE_TIME([PREFERRED-RT-OPT], [ERROR_ON_UNUSABLE]) # ----------------------------------- # Look for Boost.Date_Time. For the documentation of PREFERRED-RT-OPT, see the # documentation of BOOST_FIND_LIB above. BOOST_DEFUN([Date_Time], [BOOST_FIND_LIB([date_time], [$1], [boost/date_time/posix_time/posix_time.hpp], [boost::posix_time::ptime t;], [], [], [$2]) ])# BOOST_DATE_TIME # BOOST_EXCEPTION() # ------------ # Look for Boost.Exception BOOST_DEFUN([Exception], [BOOST_FIND_HEADER([boost/exception/all.hpp])]) # BOOST_FILESYSTEM([PREFERRED-RT-OPT], [ERROR_ON_UNUSABLE]) # ------------------------------------ # Look for Boost.Filesystem. For the documentation of PREFERRED-RT-OPT, see # the documentation of BOOST_FIND_LIB above. # Do not check for boost/filesystem.hpp because this file was introduced in # 1.34. BOOST_DEFUN([Filesystem], [# Do we have to check for Boost.System? This link-time dependency was # added as of 1.35.0. If we have a version <1.35, we must not attempt to # find Boost.System as it didn't exist by then. if test $boost_major_version -ge 135; then BOOST_SYSTEM([$1], [$2]) fi # end of the Boost.System check. boost_filesystem_save_LIBS=$LIBS boost_filesystem_save_LDFLAGS=$LDFLAGS m4_pattern_allow([^BOOST_SYSTEM_(LIBS|LDFLAGS)$])dnl LIBS="$LIBS $BOOST_SYSTEM_LIBS" LDFLAGS="$LDFLAGS $BOOST_SYSTEM_LDFLAGS" BOOST_FIND_LIB([filesystem], [$1], [boost/filesystem/path.hpp], [boost::filesystem::path p;], [], [], [$2]) if test $enable_static_boost = yes && test $boost_major_version -ge 135; then BOOST_FILESYSTEM_LIBS="$BOOST_FILESYSTEM_LIBS $BOOST_SYSTEM_LIBS" fi LIBS=$boost_filesystem_save_LIBS LDFLAGS=$boost_filesystem_save_LDFLAGS ])# BOOST_FILESYSTEM # BOOST_FLYWEIGHT() # ----------------- # Look for Boost.Flyweight. BOOST_DEFUN([Flyweight], [dnl There's a hidden dependency on pthreads. AC_REQUIRE([_BOOST_PTHREAD_FLAG])dnl BOOST_FIND_HEADER([boost/flyweight.hpp]) AC_SUBST([BOOST_FLYWEIGHT_LIBS], [$boost_cv_pthread_flag]) ]) # BOOST_FOREACH() # --------------- # Look for Boost.Foreach. BOOST_DEFUN([Foreach], [BOOST_FIND_HEADER([boost/foreach.hpp])]) # BOOST_FORMAT() # -------------- # Look for Boost.Format. # Note: we can't check for boost/format/format_fwd.hpp because the header isn't # standalone. It can't be compiled because it triggers the following error: # boost/format/detail/config_macros.hpp:88: error: 'locale' in namespace 'std' # does not name a type BOOST_DEFUN([Format], [BOOST_FIND_HEADER([boost/format.hpp])]) # BOOST_FUNCTION() # ---------------- # Look for Boost.Function BOOST_DEFUN([Function], [BOOST_FIND_HEADER([boost/function.hpp])]) # BOOST_FUSION() # ----------------- # Look for Boost.Fusion BOOST_DEFUN([Fusion], [BOOST_FIND_HEADER([boost/fusion/sequence.hpp])]) # BOOST_GEOMETRY() # ---------------- # Look for Boost.Geometry (new since 1.47.0). BOOST_DEFUN([Geometry], [BOOST_FIND_HEADER([boost/geometry.hpp]) ])# BOOST_GEOMETRY # BOOST_GRAPH([PREFERRED-RT-OPT], [ERROR_ON_UNUSABLE]) # ------------------------------- # Look for Boost.Graphs. For the documentation of PREFERRED-RT-OPT, see the # documentation of BOOST_FIND_LIB above. BOOST_DEFUN([Graph], [boost_graph_save_LIBS=$LIBS boost_graph_save_LDFLAGS=$LDFLAGS # Link-time dependency from graph to regex was added as of 1.40.0. if test $boost_major_version -ge 140; then BOOST_REGEX([$1], [$2]) m4_pattern_allow([^BOOST_REGEX_(LIBS|LDFLAGS)$])dnl LIBS="$LIBS $BOOST_REGEX_LIBS" LDFLAGS="$LDFLAGS $BOOST_REGEX_LDFLAGS" fi BOOST_FIND_LIB([graph], [$1], [boost/graph/adjacency_list.hpp], [boost::adjacency_list<> g;], [], [], [$2]) LIBS=$boost_graph_save_LIBS LDFLAGS=$boost_graph_save_LDFLAGS ])# BOOST_GRAPH # BOOST_HASH() # ------------ # Look for Boost.Functional/Hash BOOST_DEFUN([Hash], [BOOST_FIND_HEADER([boost/functional/hash.hpp])]) # BOOST_IOSTREAMS([PREFERRED-RT-OPT], [ERROR_ON_UNUSABLE]) # ----------------------------------- # Look for Boost.IOStreams. For the documentation of PREFERRED-RT-OPT, see the # documentation of BOOST_FIND_LIB above. BOOST_DEFUN([IOStreams], [BOOST_FIND_LIB([iostreams], [$1], [boost/iostreams/device/file_descriptor.hpp], [boost::iostreams::file_descriptor fd; fd.close();], [], [], [$2]) ])# BOOST_IOSTREAMS # BOOST_ITERATOR() # ------------ # Look for Boost.Iterator BOOST_DEFUN([Iterator], [BOOST_FIND_HEADER([boost/iterator/iterator_adaptor.hpp])]) # BOOST_LAMBDA() # -------------- # Look for Boost.Lambda BOOST_DEFUN([Lambda], [BOOST_FIND_HEADER([boost/lambda/lambda.hpp])]) # BOOST_LOCALE([PREFERRED-RT-OPT], [ERROR_ON_UNUSABLE]) # -------------- # Look for Boost.Locale BOOST_DEFUN([Locale], [ boost_locale_save_LIBS=$LIBS boost_locale_save_LDFLAGS=$LDFLAGS # require SYSTEM for boost-1.50.0 and up if test $boost_major_version -ge 150; then BOOST_SYSTEM([$1], [$2]) m4_pattern_allow([^BOOST_SYSTEM_(LIBS|LDFLAGS)$])dnl LIBS="$LIBS $BOOST_SYSTEM_LIBS" LDFLAGS="$LDFLAGS $BOOST_SYSTEM_LDFLAGS" fi # end of the Boost.System check. BOOST_FIND_LIB([locale], [$1], [boost/locale.hpp], [[boost::locale::generator gen; std::locale::global(gen(""));]], [], [], [$2]) LIBS=$boost_locale_save_LIBS LDFLAGS=$boost_locale_save_LDFLAGS ])# BOOST_LOCALE # BOOST_LOG([PREFERRED-RT-OPT], [ERROR_ON_UNUSABLE]) # ----------------------------- # Look for Boost.Log. For the documentation of PREFERRED-RT-OPT, see the # documentation of BOOST_FIND_LIB above. BOOST_DEFUN([Log], [boost_log_save_LIBS=$LIBS boost_log_save_LDFLAGS=$LDFLAGS BOOST_SYSTEM([$1], [$2]) BOOST_FILESYSTEM([$1], [$2]) BOOST_DATE_TIME([$1], [$2]) m4_pattern_allow([^BOOST_(SYSTEM|FILESYSTEM|DATE_TIME)_(LIBS|LDFLAGS)$])dnl LIBS="$LIBS $BOOST_DATE_TIME_LIBS $BOOST_FILESYSTEM_LIBS $BOOST_SYSTEM_LIBS" LDFLAGS="$LDFLAGS $BOOST_DATE_TIME_LDFLAGS $BOOST_FILESYSTEM_LDFLAGS $BOOST_SYSTEM_LDFLAGS" BOOST_FIND_LIB([log], [$1], [boost/log/core/core.hpp], [boost::log::attribute a; a.get_value();], [], [], [$2]) LIBS=$boost_log_save_LIBS LDFLAGS=$boost_log_save_LDFLAGS ])# BOOST_LOG # BOOST_LOG_SETUP([PREFERRED-RT-OPT], [ERROR_ON_UNUSABLE]) # ----------------------------------- # Look for Boost.Log. For the documentation of PREFERRED-RT-OPT, see the # documentation of BOOST_FIND_LIB above. BOOST_DEFUN([Log_Setup], [boost_log_setup_save_LIBS=$LIBS boost_log_setup_save_LDFLAGS=$LDFLAGS BOOST_LOG([$1]) m4_pattern_allow([^BOOST_LOG_(LIBS|LDFLAGS)$])dnl LIBS="$LIBS $BOOST_LOG_LIBS" LDFLAGS="$LDFLAGS $BOOST_LOG_LDFLAGS" BOOST_FIND_LIB([log_setup], [$1], [boost/log/utility/setup/from_settings.hpp], [boost::log::basic_settings bs; bs.empty();], [], [], [$2]) LIBS=$boost_log_setup_save_LIBS LDFLAGS=$boost_log_setup_save_LDFLAGS ])# BOOST_LOG_SETUP # BOOST_MATH() # ------------ # Look for Boost.Math # TODO: This library isn't header-only but it comes in multiple different # flavors that don't play well with BOOST_FIND_LIB (e.g, libboost_math_c99, # libboost_math_c99f, libboost_math_c99l, libboost_math_tr1, # libboost_math_tr1f, libboost_math_tr1l). This macro must be fixed to do the # right thing anyway. BOOST_DEFUN([Math], [BOOST_FIND_HEADER([boost/math/special_functions.hpp])]) # BOOST_MPI([PREFERRED-RT-OPT], [ERROR_ON_UNUSABLE]) # ------------------------------- # Look for Boost MPI. For the documentation of PREFERRED-RT-OPT, see the # documentation of BOOST_FIND_LIB above. Uses MPICXX variable if it is # set, otherwise tries CXX # BOOST_DEFUN([MPI], [boost_save_CXX=${CXX} boost_save_CXXCPP=${CXXCPP} if test x"${MPICXX}" != x; then CXX=${MPICXX} CXXCPP="${MPICXX} -E" fi BOOST_FIND_LIB([mpi], [$1], [boost/mpi.hpp], [int argc = 0; char **argv = 0; boost::mpi::environment env(argc,argv);], [], [], [$2]) CXX=${boost_save_CXX} CXXCPP=${boost_save_CXXCPP} ])# BOOST_MPI # BOOST_MPL() # ------------------ # Look for Boost.MPL BOOST_DEFUN([MPL], [BOOST_FIND_HEADER([boost/mpl/for_each.hpp])]) # BOOST_MULTIARRAY() # ------------------ # Look for Boost.MultiArray BOOST_DEFUN([MultiArray], [BOOST_FIND_HEADER([boost/multi_array.hpp])]) # BOOST_MULTIINDEXCCONTAINER() # ------------------ # Look for Boost.MultiIndexContainer BOOST_DEFUN([MultiIndexContainer], [BOOST_FIND_HEADER([boost/multi_index_container.hpp])]) # BOOST_NUMERIC_UBLAS() # -------------------------- # Look for Boost.NumericUblas (Basic Linear Algebra) BOOST_DEFUN([Numeric_Ublas], [BOOST_FIND_HEADER([boost/numeric/ublas/vector.hpp]) ])# BOOST_NUMERIC_UBLAS # BOOST_NUMERIC_CONVERSION() # -------------------------- # Look for Boost.NumericConversion (policy-based numeric conversion) BOOST_DEFUN([Numeric_Conversion], [BOOST_FIND_HEADER([boost/numeric/conversion/converter.hpp]) ])# BOOST_NUMERIC_CONVERSION # BOOST_OPTIONAL() # ---------------- # Look for Boost.Optional BOOST_DEFUN([Optional], [BOOST_FIND_HEADER([boost/optional.hpp])]) # BOOST_PREPROCESSOR() # -------------------- # Look for Boost.Preprocessor BOOST_DEFUN([Preprocessor], [BOOST_FIND_HEADER([boost/preprocessor/repeat.hpp])]) # BOOST_PROPERTY_TREE([PREFERRED-RT-OPT], [ERROR_ON_UNUSABLE]) # ----------------------------------------- # Look for Boost.Property_Tree. For the documentation of PREFERRED-RT-OPT, # see the documentation of BOOST_FIND_LIB above. BOOST_DEFUN([Property_Tree], [BOOST_FIND_LIB([property_tree], [$1], [boost/property_tree/ptree.hpp], [boost::property_tree::ptree pt; boost::property_tree::read_xml d("test", pt);], [], [], [$2]) ])# BOOST_PROPERTY_TREE # BOOST_RANDOM() # -------------------- # Look for Boost.Random BOOST_DEFUN([Random], [BOOST_FIND_HEADER([boost/random/random_number_generator.hpp])]) # BOOST_RANGE() # -------------------- # Look for Boost.Range BOOST_DEFUN([Range], [BOOST_FIND_HEADER([boost/range/adaptors.hpp])]) # BOOST_UNORDERED() # ----------------- # Look for Boost.Unordered BOOST_DEFUN([Unordered], [BOOST_FIND_HEADER([boost/unordered_map.hpp])]) # BOOST_UUID() # ------------ # Look for Boost.Uuid BOOST_DEFUN([Uuid], [BOOST_FIND_HEADER([boost/uuid/uuid.hpp])]) # BOOST_PROGRAM_OPTIONS([PREFERRED-RT-OPT], [ERROR_ON_UNUSABLE]) # ----------------------------------------- # Look for Boost.Program_options. For the documentation of PREFERRED-RT-OPT, # see the documentation of BOOST_FIND_LIB above. BOOST_DEFUN([Program_Options], [BOOST_FIND_LIB([program_options], [$1], [boost/program_options.hpp], [boost::program_options::options_description d("test");], [], [], [$2]) ])# BOOST_PROGRAM_OPTIONS # _BOOST_PYTHON_CONFIG(VARIABLE, FLAG) # ------------------------------------ # Save VARIABLE, and define it via `python-config --FLAG`. # Substitute BOOST_PYTHON_VARIABLE. m4_define([_BOOST_PYTHON_CONFIG], [AC_SUBST([BOOST_PYTHON_$1], [`python-config --$2 2>/dev/null`])dnl boost_python_save_$1=$$1 $1="$$1 $BOOST_PYTHON_$1"]) # BOOST_PYTHON([PREFERRED-RT-OPT], [ERROR_ON_UNUSABLE]) # -------------------------------- # Look for Boost.Python. For the documentation of PREFERRED-RT-OPT, # see the documentation of BOOST_FIND_LIB above. BOOST_DEFUN([Python], [_BOOST_PYTHON_CONFIG([CPPFLAGS], [includes]) _BOOST_PYTHON_CONFIG([LDFLAGS], [ldflags]) _BOOST_PYTHON_CONFIG([LIBS], [libs]) m4_pattern_allow([^BOOST_PYTHON_MODULE$])dnl BOOST_FIND_LIBS([python], [python python3], [$1], [boost/python.hpp], [], [BOOST_PYTHON_MODULE(empty) {}], [], [$2]) CPPFLAGS=$boost_python_save_CPPFLAGS LDFLAGS=$boost_python_save_LDFLAGS LIBS=$boost_python_save_LIBS ])# BOOST_PYTHON # BOOST_REF() # ----------- # Look for Boost.Ref BOOST_DEFUN([Ref], [BOOST_FIND_HEADER([boost/ref.hpp])]) # BOOST_REGEX([PREFERRED-RT-OPT], [ERROR_ON_UNUSABLE]) # ------------------------------- # Look for Boost.Regex. For the documentation of PREFERRED-RT-OPT, see the # documentation of BOOST_FIND_LIB above. BOOST_DEFUN([Regex], [BOOST_FIND_LIB([regex], [$1], [boost/regex.hpp], [boost::regex exp("*"); boost::regex_match("foo", exp);], [], [], [$2]) ])# BOOST_REGEX # BOOST_SCOPE_EXIT() # ------------ # Look for Boost.ScopeExit. BOOST_DEFUN([SCOPE_EXIT], [BOOST_FIND_HEADER([boost/scope_exit.hpp])]) # BOOST_SERIALIZATION([PREFERRED-RT-OPT], [ERROR_ON_UNUSABLE]) # --------------------------------------- # Look for Boost.Serialization. For the documentation of PREFERRED-RT-OPT, see # the documentation of BOOST_FIND_LIB above. BOOST_DEFUN([Serialization], [BOOST_FIND_LIB([serialization], [$1], [boost/archive/text_oarchive.hpp], [std::ostream* o = 0; // Cheap way to get an ostream... boost::archive::text_oarchive t(*o);], [], [], [$2]) ])# BOOST_SERIALIZATION # BOOST_SIGNALS([PREFERRED-RT-OPT], [ERROR_ON_UNUSABLE]) # --------------------------------- # Look for Boost.Signals. For the documentation of PREFERRED-RT-OPT, see the # documentation of BOOST_FIND_LIB above. BOOST_DEFUN([Signals], [BOOST_FIND_LIB([signals], [$1], [boost/signal.hpp], [boost::signal s;], [], [], [$2]) ])# BOOST_SIGNALS # BOOST_SIGNALS2() # ---------------- # Look for Boost.Signals2 (new since 1.39.0). BOOST_DEFUN([Signals2], [BOOST_FIND_HEADER([boost/signals2.hpp]) ])# BOOST_SIGNALS2 # BOOST_SMART_PTR() # ----------------- # Look for Boost.SmartPtr BOOST_DEFUN([Smart_Ptr], [BOOST_FIND_HEADER([boost/scoped_ptr.hpp]) BOOST_FIND_HEADER([boost/shared_ptr.hpp]) ]) # BOOST_STATICASSERT() # -------------------- # Look for Boost.StaticAssert BOOST_DEFUN([StaticAssert], [BOOST_FIND_HEADER([boost/static_assert.hpp])]) # BOOST_STRING_ALGO() # ------------------- # Look for Boost.StringAlgo BOOST_DEFUN([String_Algo], [BOOST_FIND_HEADER([boost/algorithm/string.hpp]) ]) # BOOST_SYSTEM([PREFERRED-RT-OPT], [ERROR_ON_UNUSABLE]) # -------------------------------- # Look for Boost.System. For the documentation of PREFERRED-RT-OPT, see the # documentation of BOOST_FIND_LIB above. This library was introduced in Boost # 1.35.0. BOOST_DEFUN([System], [BOOST_FIND_LIB([system], [$1], [boost/system/error_code.hpp], [boost::system::error_code e; e.clear();], [], [], [$2]) ])# BOOST_SYSTEM # BOOST_TEST([PREFERRED-RT-OPT], [ERROR_ON_UNUSABLE]) # ------------------------------ # Look for Boost.Test. For the documentation of PREFERRED-RT-OPT, see the # documentation of BOOST_FIND_LIB above. BOOST_DEFUN([Test], [m4_pattern_allow([^BOOST_CHECK$])dnl BOOST_FIND_LIB([unit_test_framework], [$1], [boost/test/unit_test.hpp], [BOOST_CHECK(2 == 2);], [using boost::unit_test::test_suite; test_suite* init_unit_test_suite(int argc, char ** argv) { return NULL; }], [], [$2]) ])# BOOST_TEST # BOOST_THREAD([PREFERRED-RT-OPT], [ERROR_ON_UNUSABLE]) # --------------------------------- # Look for Boost.Thread. For the documentation of PREFERRED-RT-OPT, see the # documentation of BOOST_FIND_LIB above. BOOST_DEFUN([Thread], [dnl Having the pthread flag is required at least on GCC3 where dnl boost/thread.hpp would complain if we try to compile without dnl -pthread on GNU/Linux. AC_REQUIRE([_BOOST_PTHREAD_FLAG])dnl boost_thread_save_LIBS=$LIBS boost_thread_save_LDFLAGS=$LDFLAGS boost_thread_save_CPPFLAGS=$CPPFLAGS # Link-time dependency from thread to system was added as of 1.49.0. if test $boost_major_version -ge 149; then BOOST_SYSTEM([$1], [$2]) fi # end of the Boost.System check. m4_pattern_allow([^BOOST_SYSTEM_(LIBS|LDFLAGS)$])dnl LIBS="$LIBS $BOOST_SYSTEM_LIBS $boost_cv_pthread_flag" LDFLAGS="$LDFLAGS $BOOST_SYSTEM_LDFLAGS" CPPFLAGS="$CPPFLAGS $boost_cv_pthread_flag" # When compiling for the Windows platform, the threads library is named # differently. This suffix doesn't exist in new versions of Boost, or # possibly new versions of GCC on mingw I am assuming it's Boost's change for # now and I am setting version to 1.48, for lack of knowledge as to when this # change occurred. if test $boost_major_version -lt 148; then case $host_os in (*mingw*) boost_thread_lib_ext=_win32;; esac fi BOOST_FIND_LIBS([thread], [thread$boost_thread_lib_ext], [$1], [boost/thread.hpp], [boost::thread t; boost::mutex m;], [], [], [$2]) case $host_os in (*mingw*) boost_thread_w32_socket_link=-lws2_32;; esac BOOST_THREAD_LIBS="$BOOST_THREAD_LIBS $BOOST_SYSTEM_LIBS $boost_cv_pthread_flag $boost_thread_w32_socket_link" BOOST_THREAD_LDFLAGS="$BOOST_SYSTEM_LDFLAGS" BOOST_CPPFLAGS="$BOOST_CPPFLAGS $boost_cv_pthread_flag" LIBS=$boost_thread_save_LIBS LDFLAGS=$boost_thread_save_LDFLAGS CPPFLAGS=$boost_thread_save_CPPFLAGS ])# BOOST_THREAD AU_ALIAS([BOOST_THREADS], [BOOST_THREAD]) # BOOST_TOKENIZER() # ----------------- # Look for Boost.Tokenizer BOOST_DEFUN([Tokenizer], [BOOST_FIND_HEADER([boost/tokenizer.hpp])]) # BOOST_TRIBOOL() # --------------- # Look for Boost.Tribool BOOST_DEFUN([Tribool], [BOOST_FIND_HEADER([boost/logic/tribool_fwd.hpp]) BOOST_FIND_HEADER([boost/logic/tribool.hpp]) ]) # BOOST_TUPLE() # ------------- # Look for Boost.Tuple BOOST_DEFUN([Tuple], [BOOST_FIND_HEADER([boost/tuple/tuple.hpp])]) # BOOST_TYPETRAITS() # -------------------- # Look for Boost.TypeTraits BOOST_DEFUN([TypeTraits], [BOOST_FIND_HEADER([boost/type_traits.hpp])]) # BOOST_UTILITY() # --------------- # Look for Boost.Utility (noncopyable, result_of, base-from-member idiom, # etc.) BOOST_DEFUN([Utility], [BOOST_FIND_HEADER([boost/utility.hpp])]) # BOOST_VARIANT() # --------------- # Look for Boost.Variant. BOOST_DEFUN([Variant], [BOOST_FIND_HEADER([boost/variant/variant_fwd.hpp]) BOOST_FIND_HEADER([boost/variant.hpp])]) # BOOST_POINTER_CONTAINER() # ------------------------ # Look for Boost.PointerContainer BOOST_DEFUN([Pointer_Container], [BOOST_FIND_HEADER([boost/ptr_container/ptr_deque.hpp]) BOOST_FIND_HEADER([boost/ptr_container/ptr_list.hpp]) BOOST_FIND_HEADER([boost/ptr_container/ptr_vector.hpp]) BOOST_FIND_HEADER([boost/ptr_container/ptr_array.hpp]) BOOST_FIND_HEADER([boost/ptr_container/ptr_set.hpp]) BOOST_FIND_HEADER([boost/ptr_container/ptr_map.hpp]) ])# BOOST_POINTER_CONTAINER # BOOST_WAVE([PREFERRED-RT-OPT], [ERROR_ON_UNUSABLE]) # ------------------------------ # NOTE: If you intend to use Wave/Spirit with thread support, make sure you # call BOOST_THREAD first. # Look for Boost.Wave. For the documentation of PREFERRED-RT-OPT, see the # documentation of BOOST_FIND_LIB above. BOOST_DEFUN([Wave], [AC_REQUIRE([BOOST_FILESYSTEM])dnl AC_REQUIRE([BOOST_DATE_TIME])dnl boost_wave_save_LIBS=$LIBS boost_wave_save_LDFLAGS=$LDFLAGS m4_pattern_allow([^BOOST_((FILE)?SYSTEM|DATE_TIME|THREAD)_(LIBS|LDFLAGS)$])dnl LIBS="$LIBS $BOOST_SYSTEM_LIBS $BOOST_FILESYSTEM_LIBS $BOOST_DATE_TIME_LIBS \ $BOOST_THREAD_LIBS" LDFLAGS="$LDFLAGS $BOOST_SYSTEM_LDFLAGS $BOOST_FILESYSTEM_LDFLAGS \ $BOOST_DATE_TIME_LDFLAGS $BOOST_THREAD_LDFLAGS" BOOST_FIND_LIB([wave], [$1], [boost/wave.hpp], [boost::wave::token_id id; get_token_name(id);], [], [], [$2]) LIBS=$boost_wave_save_LIBS LDFLAGS=$boost_wave_save_LDFLAGS ])# BOOST_WAVE # BOOST_XPRESSIVE() # ----------------- # Look for Boost.Xpressive (new since 1.36.0). BOOST_DEFUN([Xpressive], [BOOST_FIND_HEADER([boost/xpressive/xpressive.hpp])]) # ----------------- # # Internal helpers. # # ----------------- # # _BOOST_PTHREAD_FLAG() # --------------------- # Internal helper for BOOST_THREAD. Computes boost_cv_pthread_flag # which must be used in CPPFLAGS and LIBS. # # Yes, we *need* to put the -pthread thing in CPPFLAGS because with GCC3, # boost/thread.hpp will trigger a #error if -pthread isn't used: # boost/config/requires_threads.hpp:47:5: #error "Compiler threading support # is not turned on. Please set the correct command line options for # threading: -pthread (Linux), -pthreads (Solaris) or -mthreads (Mingw32)" # # Based on ACX_PTHREAD: http://autoconf-archive.cryp.to/acx_pthread.html AC_DEFUN([_BOOST_PTHREAD_FLAG], [AC_REQUIRE([AC_PROG_CXX])dnl AC_REQUIRE([AC_CANONICAL_HOST])dnl AC_LANG_PUSH([C++])dnl AC_CACHE_CHECK([for the flags needed to use pthreads], [boost_cv_pthread_flag], [ boost_cv_pthread_flag= # The ordering *is* (sometimes) important. Some notes on the # individual items follow: # (none): in case threads are in libc; should be tried before -Kthread and # other compiler flags to prevent continual compiler warnings # -lpthreads: AIX (must check this before -lpthread) # -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) # -llthread: LinuxThreads port on FreeBSD (also preferred to -pthread) # -pthread: GNU Linux/GCC (kernel threads), BSD/GCC (userland threads) # -pthreads: Solaris/GCC # -mthreads: MinGW32/GCC, Lynx/GCC # -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it # doesn't hurt to check since this sometimes defines pthreads too; # also defines -D_REENTRANT) # ... -mt is also the pthreads flag for HP/aCC # -lpthread: GNU Linux, etc. # --thread-safe: KAI C++ case $host_os in #( *solaris*) # On Solaris (at least, for some versions), libc contains stubbed # (non-functional) versions of the pthreads routines, so link-based # tests will erroneously succeed. (We need to link with -pthreads/-mt/ # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather # a function called by this macro, so we could check for that, but # who knows whether they'll stub that too in a future libc.) So, # we'll just look for -pthreads and -lpthread first: boost_pthread_flags="-pthreads -lpthread -mt -pthread";; #( *) boost_pthread_flags="-lpthreads -Kthread -kthread -llthread -pthread \ -pthreads -mthreads -lpthread --thread-safe -mt";; esac # Generate the test file. AC_LANG_CONFTEST([AC_LANG_PROGRAM([#include void *f(void*){ return 0; }], [pthread_t th; pthread_create(&th,0,f,0); pthread_join(th,0); pthread_attr_t attr; pthread_attr_init(&attr); pthread_cleanup_push(0, 0); pthread_cleanup_pop(0);])]) for boost_pthread_flag in '' $boost_pthread_flags; do boost_pthread_ok=false dnl Re-use the test file already generated. boost_pthreads__save_LIBS=$LIBS LIBS="$LIBS $boost_pthread_flag" AC_LINK_IFELSE([], [if grep ".*$boost_pthread_flag" conftest.err; then echo "This flag seems to have triggered warnings" >&AS_MESSAGE_LOG_FD else boost_pthread_ok=:; boost_cv_pthread_flag=$boost_pthread_flag fi]) LIBS=$boost_pthreads__save_LIBS $boost_pthread_ok && break done ]) AC_LANG_POP([C++])dnl ])# _BOOST_PTHREAD_FLAG # _BOOST_gcc_test(MAJOR, MINOR) # ----------------------------- # Internal helper for _BOOST_FIND_COMPILER_TAG. m4_define([_BOOST_gcc_test], ["defined __GNUC__ && __GNUC__ == $1 && __GNUC_MINOR__ == $2 && !defined __ICC @ gcc$1$2"])dnl # _BOOST_mingw_test(MAJOR, MINOR) # ----------------------------- # Internal helper for _BOOST_FIND_COMPILER_TAG. m4_define([_BOOST_mingw_test], ["defined __GNUC__ && __GNUC__ == $1 && __GNUC_MINOR__ == $2 && !defined __ICC && \ (defined WIN32 || defined WINNT || defined _WIN32 || defined __WIN32 \ || defined __WIN32__ || defined __WINNT || defined __WINNT__) @ mgw$1$2"])dnl # _BOOST_FIND_COMPILER_TAG() # -------------------------- # Internal. When Boost is installed without --layout=system, each library # filename will hold a suffix that encodes the compiler used during the # build. The Boost build system seems to call this a `tag'. AC_DEFUN([_BOOST_FIND_COMPILER_TAG], [AC_REQUIRE([AC_PROG_CXX])dnl AC_REQUIRE([AC_CANONICAL_HOST])dnl AC_CACHE_CHECK([for the toolset name used by Boost for $CXX], [boost_cv_lib_tag], [boost_cv_lib_tag=unknown if test x$boost_cv_inc_path != xno; then AC_LANG_PUSH([C++])dnl # The following tests are mostly inspired by boost/config/auto_link.hpp # The list is sorted to most recent/common to oldest compiler (in order # to increase the likelihood of finding the right compiler with the # least number of compilation attempt). # Beware that some tests are sensible to the order (for instance, we must # look for MinGW before looking for GCC3). # I used one compilation test per compiler with a #error to recognize # each compiler so that it works even when cross-compiling (let me know # if you know a better approach). # Known missing tags (known from Boost's tools/build/v2/tools/common.jam): # como, edg, kcc, bck, mp, sw, tru, xlc # I'm not sure about my test for `il' (be careful: Intel's ICC pre-defines # the same defines as GCC's). for i in \ "defined __clang__ && __clang_major__ == 12 && __clang_minor__ == 0 @ clang120" \ "defined __clang__ && __clang_major__ == 11 && __clang_minor__ == 1 @ clang111" \ "defined __clang__ && __clang_major__ == 11 && __clang_minor__ == 0 @ clang110" \ "defined __clang__ && __clang_major__ == 10 && __clang_minor__ == 0 @ clang100" \ "defined __clang__ && __clang_major__ == 9 && __clang_minor__ == 0 @ clang90" \ "defined __clang__ && __clang_major__ == 8 && __clang_minor__ == 0 @ clang80" \ "defined __clang__ && __clang_major__ == 7 && __clang_minor__ == 0 @ clang70" \ "defined __clang__ && __clang_major__ == 6 && __clang_minor__ == 0 @ clang60" \ "defined __clang__ && __clang_major__ == 5 && __clang_minor__ == 0 @ clang50" \ "defined __clang__ && __clang_major__ == 4 && __clang_minor__ == 0 @ clang40" \ "defined __clang__ && __clang_major__ == 3 && __clang_minor__ == 9 @ clang39" \ "defined __clang__ && __clang_major__ == 3 && __clang_minor__ == 8 @ clang38" \ "defined __clang__ && __clang_major__ == 3 && __clang_minor__ == 7 @ clang37" \ _BOOST_mingw_test(11, 1) \ _BOOST_gcc_test(11, 1) \ _BOOST_mingw_test(10, 3) \ _BOOST_gcc_test(10, 3) \ _BOOST_mingw_test(10, 2) \ _BOOST_gcc_test(10, 2) \ _BOOST_mingw_test(10, 1) \ _BOOST_gcc_test(10, 1) \ _BOOST_mingw_test(9, 3) \ _BOOST_gcc_test(9, 3) \ _BOOST_mingw_test(9, 2) \ _BOOST_gcc_test(9, 2) \ _BOOST_mingw_test(9, 1) \ _BOOST_gcc_test(9, 1) \ _BOOST_mingw_test(9, 0) \ _BOOST_gcc_test(9, 0) \ _BOOST_mingw_test(8, 5) \ _BOOST_gcc_test(8, 5) \ _BOOST_mingw_test(8, 4) \ _BOOST_gcc_test(8, 4) \ _BOOST_mingw_test(8, 3) \ _BOOST_gcc_test(8, 3) \ _BOOST_mingw_test(8, 2) \ _BOOST_gcc_test(8, 2) \ _BOOST_mingw_test(8, 1) \ _BOOST_gcc_test(8, 1) \ _BOOST_mingw_test(8, 0) \ _BOOST_gcc_test(8, 0) \ _BOOST_mingw_test(7, 4) \ _BOOST_gcc_test(7, 4) \ _BOOST_mingw_test(7, 3) \ _BOOST_gcc_test(7, 3) \ _BOOST_mingw_test(7, 2) \ _BOOST_gcc_test(7, 2) \ _BOOST_mingw_test(7, 1) \ _BOOST_gcc_test(7, 1) \ _BOOST_mingw_test(7, 0) \ _BOOST_gcc_test(7, 0) \ _BOOST_mingw_test(6, 5) \ _BOOST_gcc_test(6, 5) \ _BOOST_mingw_test(6, 4) \ _BOOST_gcc_test(6, 4) \ _BOOST_mingw_test(6, 3) \ _BOOST_gcc_test(6, 3) \ _BOOST_mingw_test(6, 2) \ _BOOST_gcc_test(6, 2) \ _BOOST_mingw_test(6, 1) \ _BOOST_gcc_test(6, 1) \ _BOOST_mingw_test(6, 0) \ _BOOST_gcc_test(6, 0) \ _BOOST_mingw_test(5, 5) \ _BOOST_gcc_test(5, 5) \ _BOOST_mingw_test(5, 4) \ _BOOST_gcc_test(5, 4) \ _BOOST_mingw_test(5, 3) \ _BOOST_gcc_test(5, 3) \ _BOOST_mingw_test(5, 2) \ _BOOST_gcc_test(5, 2) \ _BOOST_mingw_test(5, 1) \ _BOOST_gcc_test(5, 1) \ _BOOST_mingw_test(5, 0) \ _BOOST_gcc_test(5, 0) \ _BOOST_mingw_test(4, 10) \ _BOOST_gcc_test(4, 10) \ _BOOST_mingw_test(4, 9) \ _BOOST_gcc_test(4, 9) \ _BOOST_mingw_test(4, 8) \ _BOOST_gcc_test(4, 8) \ _BOOST_mingw_test(4, 7) \ _BOOST_gcc_test(4, 7) \ _BOOST_mingw_test(4, 6) \ _BOOST_gcc_test(4, 6) \ _BOOST_mingw_test(4, 5) \ _BOOST_gcc_test(4, 5) \ _BOOST_mingw_test(4, 4) \ _BOOST_gcc_test(4, 4) \ _BOOST_mingw_test(4, 3) \ _BOOST_gcc_test(4, 3) \ _BOOST_mingw_test(4, 2) \ _BOOST_gcc_test(4, 2) \ _BOOST_mingw_test(4, 1) \ _BOOST_gcc_test(4, 1) \ _BOOST_mingw_test(4, 0) \ _BOOST_gcc_test(4, 0) \ "defined __GNUC__ && __GNUC__ == 3 && !defined __ICC \ && (defined WIN32 || defined WINNT || defined _WIN32 || defined __WIN32 \ || defined __WIN32__ || defined __WINNT || defined __WINNT__) @ mgw" \ _BOOST_gcc_test(3, 4) \ _BOOST_gcc_test(3, 3) \ "defined _MSC_VER && _MSC_VER >= 1500 @ vc90" \ "defined _MSC_VER && _MSC_VER == 1400 @ vc80" \ _BOOST_gcc_test(3, 2) \ "defined _MSC_VER && _MSC_VER == 1310 @ vc71" \ _BOOST_gcc_test(3, 1) \ _BOOST_gcc_test(3, 0) \ "defined __BORLANDC__ @ bcb" \ "defined __ICC && (defined __unix || defined __unix__) @ il" \ "defined __ICL @ iw" \ "defined _MSC_VER && _MSC_VER == 1300 @ vc7" \ _BOOST_gcc_test(2, 95) \ "defined __MWERKS__ && __MWERKS__ <= 0x32FF @ cw9" \ "defined _MSC_VER && _MSC_VER < 1300 && !defined UNDER_CE @ vc6" \ "defined _MSC_VER && _MSC_VER < 1300 && defined UNDER_CE @ evc4" \ "defined __MWERKS__ && __MWERKS__ <= 0x31FF @ cw8" do boost_tag_test=`expr "X$i" : 'X\([[^@]]*\) @ '` boost_tag=`expr "X$i" : 'X[[^@]]* @ \(.*\)'` AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #if $boost_tag_test /* OK */ #else # error $boost_tag_test #endif ]])], [boost_cv_lib_tag=$boost_tag; break], []) done AC_LANG_POP([C++])dnl case $boost_cv_lib_tag in #( # Some newer (>= 1.35?) versions of Boost seem to only use "gcc" as opposed # to "gcc41" for instance. *-gcc | *'-gcc ') :;; #( Don't re-add -gcc: it's already in there. gcc*) boost_tag_x= case $host_os in #( darwin*) if test $boost_major_version -ge 136; then # The `x' added in r46793 of Boost. boost_tag_x=x fi;; esac # We can specify multiple tags in this variable because it's used by # BOOST_FIND_LIB that does a `for tag in -$boost_cv_lib_tag' ... boost_cv_lib_tag="$boost_tag_x$boost_cv_lib_tag -${boost_tag_x}gcc" ;; #( unknown) AC_MSG_WARN([[could not figure out which toolset name to use for $CXX]]) boost_cv_lib_tag= ;; esac fi])dnl end of AC_CACHE_CHECK ])# _BOOST_FIND_COMPILER_TAG # _BOOST_GUESS_WHETHER_TO_USE_MT() # -------------------------------- # Compile a small test to try to guess whether we should favor MT (Multi # Thread) flavors of Boost. Sets boost_guess_use_mt accordingly. AC_DEFUN([_BOOST_GUESS_WHETHER_TO_USE_MT], [# Check whether we do better use `mt' even though we weren't ask to. AC_LANG_PUSH([C++])dnl AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #if defined _REENTRANT || defined _MT || defined __MT__ /* use -mt */ #else # error MT not needed #endif ]])], [boost_guess_use_mt=:], [boost_guess_use_mt=false]) AC_LANG_POP([C++])dnl ]) # _BOOST_AC_LINK_IFELSE(PROGRAM, [ACTION-IF-TRUE], [ACTION-IF-FALSE]) # ------------------------------------------------------------------- # Fork of _AC_LINK_IFELSE that preserves conftest.o across calls. Fragile, # will break when Autoconf changes its internals. Requires that you manually # rm -f conftest.$ac_objext in between to really different tests, otherwise # you will try to link a conftest.o left behind by a previous test. # Used to aggressively optimize BOOST_FIND_LIB (see the big comment in this # macro). # # Don't use "break" in the actions, as it would short-circuit some code # this macro runs after the actions. m4_define([_BOOST_AC_LINK_IFELSE], [m4_ifvaln([$1], [AC_LANG_CONFTEST([$1])])dnl rm -f conftest$ac_exeext boost_save_ac_ext=$ac_ext boost_use_source=: # If we already have a .o, re-use it. We change $ac_ext so that $ac_link # tries to link the existing object file instead of compiling from source. test -f conftest.$ac_objext && ac_ext=$ac_objext && boost_use_source=false && _AS_ECHO_LOG([re-using the existing conftest.$ac_objext]) AS_IF([_AC_DO_STDERR($ac_link) && { test -z "$ac_[]_AC_LANG_ABBREV[]_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && { test "$cross_compiling" = yes || $as_executable_p conftest$ac_exeext dnl FIXME: use AS_TEST_X instead when 2.61 is widespread enough. }], [$2], [if $boost_use_source; then _AC_MSG_LOG_CONFTEST fi $3]) ac_objext=$boost_save_ac_objext ac_ext=$boost_save_ac_ext dnl Delete also the IPA/IPO (Inter Procedural Analysis/Optimization) dnl information created by the PGI compiler (conftest_ipa8_conftest.oo), dnl as it would interfere with the next link command. rm -f core conftest.err conftest_ipa8_conftest.oo \ conftest$ac_exeext m4_ifval([$1], [conftest.$ac_ext])[]dnl ])# _BOOST_AC_LINK_IFELSE # Local Variables: # mode: autoconf # End: solarpowerlog-solarpowerlog-0.26/solarpowerlog.1000066400000000000000000000036621444065341000222260ustar00rootroot00000000000000.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .TH "SOLARPOWERLOG" "1" "June 16, 2009" "" "" .SH "NAME" solarpowerlog \- queries data from a photovoltaic inverter .SH "SYNOPSIS" .B solarpowerlog .RI [ options ] .br .SH "DESCRIPTION" The program trackd and logd data from photovoltaic inverters, collect power data and store them. .SH "OPTIONS" The program follow the usual GNU command line syntax, with long options starting with two dashes (`\-'). A summary of options is included below. .TP .B \-h, \-\-help Show summary of options. .TP .B \-v, \-\-version Show version of program. .TP .B \-b, \-\-background Runs the program in the background, detaching it from the console. .TP .B \-c, \-\-conf filename The configuration to be used. Default is solarpowerlog.conf, looked for in the current directory. .TP .B \-\-dumpcfg Dump configuration structure, then exit .TP .B \-\-chdir\fR arg working directory for daemon (only used when running as a daemon). Defaults to / .TP .B \-\-stdout\fR arg redirect stdout to this file (only used when running as a daemon). Defaults to /dev/null .TP .B \-\-stderr\fR arg redirect stderr to this file (only used when running as a daemon). Defaults to /dev/null .TP .B \-\-pidfile\fR arg create a pidfile after the daemon has been started. (only used when running as a daemon). Default: no pid file .SH "SEE ALSO" https://github.com/coldtobi/solarpowerlog solarpowerlog-solarpowerlog-0.26/src/000077500000000000000000000000001444065341000200255ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Connections/000077500000000000000000000000001444065341000223075ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Connections/CAsyncCommand.cpp000066400000000000000000000020371444065341000254740ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2014 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /* * CAsyncCommand.cpp * * Created on: Dec 14, 2009 * Author: tobi */ // this unit is currently empty. #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif solarpowerlog-solarpowerlog-0.26/src/Connections/CAsyncCommand.h000066400000000000000000000044461444065341000251470ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2014 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /* * CAsyncCommand.h * * Created on: Dec 14, 2009 * Author: tobi */ #ifndef CASYNCCOMMAND_H_ #define CASYNCCOMMAND_H_ #include #include "patterns/ICommand.h" #include "configuration/Registry.h" class CAsyncCommand { public: enum Commando { DISCONNECT, /// Tear down a connection CONNECT, /// Connect SEND, /// Send data RECEIVE, /// Try to receive data ACCEPT /// "Server-Mode" for inbound connections. }; /** Constructor which create the object * * \param c Commando to be used * \param callback ICommand used as callback. Must not be NULL. * * \note since solarpowerlog 0.25, the synchronous interface is no longer * supported: So ICommand must no longer be NULL (will be asserted!) */ CAsyncCommand(enum Commando cmd, ICommand *pcallback) : c(cmd), callback(pcallback) { assert(pcallback); } /** Destructor */ ~CAsyncCommand() { } /** Handle this jobs completion by notifying the sender */ inline void HandleCompletion(void) { Registry::GetMainScheduler()->ScheduleWork(callback); } /** Stores the command what to do */ enum Commando c; /** callback for completion handling * In this ICommand, the comand data is stored, results and data... */ ICommand *callback; }; #endif /* CASYNCCOMMAND_H_ */ solarpowerlog-solarpowerlog-0.26/src/Connections/CConnectDummy.cpp000066400000000000000000000026261444065341000255310ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file CConnectDummy.cpp * * Created on: May 22, 2009 * Author: tobi */ #include "CConnectDummy.h" #include #ifdef HAVE_CONFIG_H #include "config.h" #endif using namespace std; CConnectDummy::CConnectDummy(const string &configurationname) : IConnect(configurationname) { } CConnectDummy::~CConnectDummy() { // TODO Auto-generated destructor stub } bool CConnectDummy::CheckConfig(void) { LOGERROR(this->logger, "Unknown communication method:. Please check your comms= setting."); return false; } solarpowerlog-solarpowerlog-0.26/src/Connections/CConnectDummy.h000066400000000000000000000052421444065341000251730ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file CConnectDummy.h * * Created on: May 22, 2009 * Author: tobi */ #ifndef CCONNECTDUMMY_H_ #define CCONNECTDUMMY_H_ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif #include "configuration/Registry.h" /** This is a dummy connection class which only fills the gap if * - the instanciator does not need comms but the * base class wants a instance. (e.g datafilters) */ #include "interfaces/IConnect.h" class CConnectDummy: public IConnect { protected: friend class IConnectFactory; CConnectDummy(const string &configurationname); public: virtual ~CConnectDummy(); private: /** The dummy inverter cannot do anything, so we will just * return an error on every request. * this function does that using the async interface. * NOTE: It is a BUG if any of those routines are called, as th * CConnectDummy must not be used as communication interface. * If a user configured it, the config check of it will fail, * aborting the programm. */ virtual void Dispatch_Error(ICommand *cmd) { assert(cmd); cmd->addData(ICMD_ERRNO, -EIO); cmd->addData(ICMD_ERRNO_STR,std::string("CConnectDummy cannot communicate")); Registry::GetMainScheduler()->ScheduleWork(cmd); } public: virtual void Connect(ICommand *cmd) { this->Dispatch_Error(cmd); } virtual void Disconnect(ICommand *cmd) { this->Dispatch_Error(cmd); } virtual void Send(ICommand *cmd) { Dispatch_Error(cmd); } virtual void Receive(ICommand *cmd) { return this->Dispatch_Error(cmd); } virtual bool AbortAll() { return false; } virtual bool CheckConfig(void); virtual void _main(void) {} virtual bool CanAccept(void) { return false; } }; #endif /* CCONNECTDUMMY_H_ */ solarpowerlog-solarpowerlog-0.26/src/Connections/CConnectSerialAsio.cpp000066400000000000000000000604421444065341000264710ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2010-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file CConnectSerialAsio.cpp * * This interface class abstracts the Comms-Interface (IConnect) to the * boost::asio class * * Boost::asio can act either as a serial class (like RS233, Rs485) * or as TCP class. so copying would mean lots of duplicated code * * Created on: May 16, 2010 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif #ifdef HAVE_COMMS_ASIOSERIAL #define DEBUG_SERIALASIO #include "interfaces/IConnect.h" #include "Connections/CConnectSerialAsio.h" #include #include #include "configuration/CConfigHelper.h" #include #include #include #include #include #include "interfaces/CMutexHelper.h" #include #include #include #include #include using namespace boost::posix_time; using namespace std; using namespace boost::asio; using namespace boost; using namespace libconfig; struct asyncASIOCompletionHandler { asyncASIOCompletionHandler(size_t *b, boost::system::error_code *ec) { bytes = b; this->ec = ec; } void operator()(const boost::system::error_code& e, std::size_t bytes_transferred) { *bytes = bytes_transferred; *ec = e; } // note, we need pointer as boost seems to make a copy of our handler... size_t *bytes; boost::system::error_code *ec; }; /// Helping function for timeout and receive, will be called by boosts' asio. /// this handler just will set the int store with the value value. static void boosthelper_set_result(int* store, int value) { if (store) *store = value; } CConnectSerialAsio::CConnectSerialAsio(const string &configurationname) : IConnect(configurationname),_cfg_characterlen('8'), _cfg_baudrate(9600) { ioservice = new io_service; port = new boost::asio::serial_port(*ioservice); sem_init(&cmdsemaphore, 0, 0); } CConnectSerialAsio::~CConnectSerialAsio() { SetThreadTermRequest(); // Try to shutdown cleanly... // (most likely abortall() has been previously called anyway) boost::system::error_code ec; mutex.lock(); cmds.clear(); ioservice->stop(); if (port->is_open()) { port->cancel(ec); port->close(ec); } mutex.unlock(); // need to post to semaphore to interrupt worker thread to terminate. sem_post(&cmdsemaphore); LOGDEBUG(logger, "Waiting for thread to join"); workerthread.join(); LOGDEBUG(logger, "Joined."); delete port; delete ioservice; sem_destroy(&cmdsemaphore); } void CConnectSerialAsio::Accept(ICommand *callback) { // Accept is the same as Connect for this class Connect(callback); } void CConnectSerialAsio::Connect(ICommand *callback) { CAsyncCommand *commando = new CAsyncCommand(CAsyncCommand::CONNECT, callback); PushWork(commando); } /* Disconnect * * The disconnection is done by the async task. * * \note starting with 0.25 the interface is async only! */ void CConnectSerialAsio::Disconnect(ICommand *callback) { assert(callback); CAsyncCommand *commando = new CAsyncCommand(CAsyncCommand::DISCONNECT, callback); PushWork(commando); } void CConnectSerialAsio::Send(ICommand *callback) { assert(callback); CAsyncCommand *commando = new CAsyncCommand(CAsyncCommand::SEND, callback); PushWork(commando); } /** Receive bytes from the stream -- asynced. * * As with all other methods, will be done by the worker thread, even if * synchronous operation is requested. In this case, we'll just wait for * completion.*/ void CConnectSerialAsio::Receive(ICommand *callback) { CAsyncCommand *commando = new CAsyncCommand(CAsyncCommand::RECEIVE, callback); PushWork(commando); } bool CConnectSerialAsio::IsConnected(void) { if (!port) return false; mutex.lock(); bool ret = port->is_open(); mutex.unlock(); return ret; } bool CConnectSerialAsio::CheckConfig(void) { string setting; bool fail = false; bool portsetting_parseerr = false; CConfigHelper cfghelper(ConfigurationPath); fail |= !cfghelper.CheckConfig("serial_serialportname", libconfig::Setting::TypeString, false); fail |= !cfghelper.CheckConfig("serial_baudrate", libconfig::Setting::TypeInt, false); fail |= !cfghelper.CheckConfig("serial_portparameters", libconfig::Setting::TypeString, true); fail |= !cfghelper.CheckConfig("serial_flowcontrol", libconfig::Setting::TypeString, true); fail |= !cfghelper.CheckConfig("serial_timeout", libconfig::Setting::TypeInt, true); if (cfghelper.CheckConfig("serial_timeout", libconfig::Setting::TypeInt)) { LOGWARN(logger, " Parameter serial_timeout is depreciated."); } fail |= !cfghelper.CheckConfig("serial_interbytetimeout", libconfig::Setting::TypeInt, true); cfghelper.GetConfig("", setting, std::string("none")); boost::algorithm::to_lower(setting); if (setting == "none") { _cfg_flowctrl = boost::asio::serial_port_base::flow_control( boost::asio::serial_port_base::flow_control::none); } else if (setting == "hardware") { _cfg_flowctrl = boost::asio::serial_port_base::flow_control( boost::asio::serial_port_base::flow_control::hardware); } else if (setting == "software") { _cfg_flowctrl = boost::asio::serial_port_base::flow_control( boost::asio::serial_port_base::flow_control::software); } else { fail = true; LOGERROR( logger, "serial_flowcontrol must be \"none\", \"hardware\" or \"software\"."); } cfghelper.GetConfig("serial_portparameters", setting, std::string("8N1")); if (setting.size() != 3) { LOGERROR(logger, "serial_portparameters: Must be exactly three " "characters"); } else { if (setting[0] >= '5' || setting[0] <= '9') { _cfg_characterlen = setting[0] - '0'; } else { fail = true; portsetting_parseerr = true; LOGERROR(logger, "serial_portparameters: Number of bits per " "symbol must be between 5 and 9"); } if (_cfg_characterlen > 8) { fail = true; LOGERROR(logger, "serial_portparameters: 9 bits per symbol currently not supported by solarpowelog"); } switch (setting[1]) { case 'E': case 'e': _cfg_parity = boost::asio::serial_port_base::parity( boost::asio::serial_port_base::parity::even); break; case 'O': case 'o': _cfg_parity = boost::asio::serial_port_base::parity( boost::asio::serial_port_base::parity::odd); break; case 'N': case 'n': _cfg_parity = boost::asio::serial_port_base::parity( boost::asio::serial_port_base::parity::none); break; default: portsetting_parseerr = true; LOGERROR(logger, "serial_portparameter: Invalid parity. Your " "choices are 'E'ven ,'O'dd or 'N'one"); fail = true; break; } // If you are bored, you could implement 1.5 stop bits ;-) if (setting[2] == '1') { _cfg_stopbits = boost::asio::serial_port_base::stop_bits( boost::asio::serial_port_base::stop_bits::one); } else if (setting[2] == '2') { _cfg_stopbits = boost::asio::serial_port_base::stop_bits( boost::asio::serial_port_base::stop_bits::two); } else { portsetting_parseerr = true; fail = true; LOGERROR(logger, "serial_portparameter: Invalid number of stop bits."); } } if (portsetting_parseerr) { // print some explanations... LOGERROR(logger, "serial_portparameter must be of the format like" " \"8N1\" to specify symbol length(e.g for example , parity " "and number of stopbits.)"); } if (fail) return false; // Cache baudrate. cfghelper.GetConfig("serial_baudrate", _cfg_baudrate); StartWorkerThread(); return true; } void CConnectSerialAsio::_main(void) { LOGTRACE(logger, "Starting helper thread"); while (!IsTermRequested()) { int syscallret; // wait for work or signals. syscallret = sem_wait(&cmdsemaphore); if (syscallret == -1) { continue; } // semaphore had work for us. process it. // safety check: really some work? mutex.lock(); if (cmds.empty()) { mutex.unlock(); continue; } CAsyncCommand *donow = cmds.front(); cmds.pop_front(); // reset the ioservice for the next commmand. // (must be done during holding the mutex to avoid a race // with the AbortAll() call. ioservice->reset(); mutex.unlock(); switch (donow->c) { case CAsyncCommand::CONNECT: HandleConnect(donow); break; case CAsyncCommand::DISCONNECT: HandleDisconnect(donow); break; case CAsyncCommand::RECEIVE: HandleReceive(donow); break; case CAsyncCommand::SEND: HandleSend(donow); break; default: LOGDEBUG_SA(logger, __COUNTER__, "Unknown command " << donow->c <<" received."); break; } delete donow; } IConnect::_main(); } bool CConnectSerialAsio::PushWork(CAsyncCommand *cmd) { mutex.lock(); cmds.push_back(cmd); mutex.unlock(); sem_post(&cmdsemaphore); workerthread.interrupt(); return true; } void CConnectSerialAsio::HandleConnect(CAsyncCommand *cmd) { string portname; boost::system::error_code ec; // if connected, ignore the commmand, pretend success. if (IsConnected()) { cmd->callback->addData(ICMD_ERRNO, 0); cmd->HandleCompletion(); return; } CConfigHelper cfghelper(ConfigurationPath); cfghelper.GetConfig("serial_serialportname", portname); // we need first to open the port, before applying the settings to it. ec = port->open(portname, ec); if (ec) { // retrieve error code out of ec object. // Boost doc won't tell if it is negative, so we better make sure int eint = ec.value(); if (eint > 0) eint = -eint; cmd->callback->addData(ICMD_ERRNO, eint); if (!ec.message().empty()) { cmd->callback->addData(ICMD_ERRNO_STR, ec.message()); } cmd->HandleCompletion(); return; } try { port->set_option( boost::asio::serial_port_base::baud_rate(_cfg_baudrate)); port->set_option( boost::asio::serial_port_base::character_size(_cfg_characterlen)); port->set_option(_cfg_stopbits); port->set_option(_cfg_parity); port->set_option(_cfg_flowctrl); } catch (boost::system::system_error &e) { LOGERROR(logger, "Setting serial port options failed: " << e.what();); cmd->callback->addData(ICMD_ERRNO_STR, e.what()); cmd->callback->addData(ICMD_ERRNO, -EIO); port->close(ec); cmd->HandleCompletion(); return; } LOGDEBUG(logger, "Opened " << portname); cmd->callback->addData(ICMD_ERRNO, 0); cmd->HandleCompletion(); return; } void CConnectSerialAsio::HandleDisconnect(CAsyncCommand *cmd) { boost::system::error_code ec, ec2; std::string message; int error = 0; if (!IsConnected()) { cmd->callback->addData(ICMD_ERRNO, 0); cmd->HandleCompletion(); return; } ec = port->cancel(ec); ec2 = port->close(ec2); if (ec) { error = -EIO; message = ec.message(); } if (ec2) { error = -EIO; if (!message.empty()) message = message + " "; message = message + ec2.message(); } cmd->callback->addData(ICMD_ERRNO, error); if (!message.empty()) { cmd->callback->addData(ICMD_ERRNO_STR, ec.message()); } cmd->HandleCompletion(); return; } /** Handle Receive -- asynchronous read from the asio socket with timeout. * * Strategy: * -- get timeout config from caller or configuration (depreciated) * -- spawn timer with timeout setting in background * -- setup async read operation of one byte (to detect incoming communication) * -- check if we got a byte or timeout * -- if got the byte, try to read all available bytes (as the serial ASIO class * cannot tell us the remaining bytes, we need to poll them * -- to detect the end of the message, spawn a "inter byte timeout" timer to * abort reading if no new bytes are arriving. This is derived from the * configuration or from the baidrate, where a compile-time * minimum is enforced. */ void CConnectSerialAsio::HandleReceive(CAsyncCommand *cmd) { boost::system::error_code ec, handlerec; volatile int result_timer = 0; size_t bytes = 0; unsigned long timeout; char buf[2] = {0,0}; struct asyncASIOCompletionHandler read_handler(&bytes, &handlerec); // avoid that the io_service runs out of work as it auto-stops then. boost::asio::io_service::work work(*ioservice); // timeout setup try { timeout = boost::any_cast(cmd->callback->findData( ICONN_TOKEN_TIMEOUT)); } catch (std::invalid_argument &e) { CConfigHelper cfghelper(ConfigurationPath); cfghelper.GetConfig("serial_timeout", timeout, SERIAL_ASIO_DEFAULT_TIMEOUT); } catch (boost::bad_any_cast &e) { LOGDEBUG(logger, "Unexpected exception in HandleReceive: Bad cast" << e.what()); timeout = SERIAL_ASIO_DEFAULT_TIMEOUT; } LOGDEBUG(logger, "timeout "<< timeout); deadline_timer timer(*(this->ioservice)); boost::posix_time::time_duration td = boost::posix_time::millisec(timeout); timer.expires_from_now(td); timer.async_wait(boost::bind(&boosthelper_set_result, (int*)&result_timer, 1)); // socket preparation // async_read. However, boost:asio seems not to allow auto-buffers, // so we will just read one byte and when this is available, we'll // check for if there are some others left // same as with the TCP/IP class: Sometimes the ioservice instantly returns // without any actual work done. (read_handler & timeout handler not called) // Lets try to catch that with the same approach as in the TCP class... // Should file a bug report at booot... size_t num = 0; int maxsleep = 5; // we limit the hack to 5 times. do { bool sleep = false; if (!sleep) { boost::posix_time::time_duration td2 = boost::posix_time::millisec( 25); deadline_timer timer(*ioservice, td2); try { timer.wait(); maxsleep--; } catch (...) { } } sleep = true; port->async_read_some(boost::asio::buffer(&buf, 1), read_handler); num = ioservice->run_one(ec); } while (maxsleep != 0 && !result_timer && 0 == *(read_handler.bytes)); LOGTRACE(logger, "hack needed " << 5-maxsleep << "times."); // ioservice error or timeout if (num == 0 || result_timer) { if (result_timer) { LOGTRACE(logger, "Read timeout"); cmd->callback->addData(ICMD_ERRNO_STR, std::string("Read timeout")); cmd->callback->addData(ICMD_ERRNO, -ETIMEDOUT); } else { LOGTRACE(logger, "IO Service Error: " << ec.message()); cmd->callback->addData(ICMD_ERRNO_STR, std::string("IO-service error " + ec.message())); cmd->callback->addData(ICMD_ERRNO, -EIO); } cmd->HandleCompletion(); timer.cancel(ec); port->cancel(ec); ioservice->poll(); return; } timer.cancel(); ioservice->poll(ec); if (*read_handler.ec) { if (*read_handler.ec != boost::asio::error::eof) { LOGDEBUG(logger, "Async read failed with ec=" << *read_handler.ec << " msg="<< read_handler.ec->message()); cmd->callback->addData(ICMD_ERRNO, -EIO); cmd->callback->addData(ICMD_ERRNO_STR, read_handler.ec->message()); } else { cmd->callback->addData(ICMD_ERRNO, -ENOTCONN); LOGDEBUG(logger, "Received eof on socket read"); } cmd->HandleCompletion(); return; } /* now one byte is read -- we apply the byte-timeout here to read the * remaining bytes. By default deducted from the baudarate, but in this * case a minimum is enforced. */ CConfigHelper cfghelper(ConfigurationPath); cfghelper.GetConfig("serial_interbytetimeout", timeout, 0UL); if (timeout == 0) { // default interbyte timeout is 10 times the time for one byte. // (we allow the inaccuracy and assume 10 bits per byte, which is // valid for 8N1) // however, we ensure a minimum time of 50 ms. // (which is still tough as our OS might idle around for even longer) timeout = (1000 * 10 * 10) / _cfg_baudrate; if (timeout <= SERIAL_ASIO_DEFAULT_INTERBYTETIMEOUT) timeout = SERIAL_ASIO_DEFAULT_INTERBYTETIMEOUT; } std::string received; received = buf[0]; while (1) { // clear previous states. bytes = 0; result_timer = 0; ec.clear(); handlerec.clear(); // prepare timer td = boost::posix_time::millisec(timeout); timer.expires_from_now(td); timer.async_wait( boost::bind(&boosthelper_set_result, (int*)&result_timer, 1)); // read a byte port->async_read_some(boost::asio::buffer(&buf, 1), read_handler); size_t num = ioservice->run_one(ec); // ioservice error or timeout if (num == 0 || result_timer) { if (result_timer) LOGTRACE(logger, "Timeout while reading"); if (0 == num) LOGTRACE(logger, "IO-Service error: " << ec.message()); timer.cancel(ec); port->cancel(ec); ioservice->poll(); LOGTRACE(logger, "break 1"); break; } timer.cancel(); ioservice->poll(ec); // asio async read completed -- check for read error if (*read_handler.ec) { if (*read_handler.ec != boost::asio::error::eof) { // read error occured, which is not timeout. LOGDEBUG(logger, "Async read failed with ec=" << *read_handler.ec << " msg="<< read_handler.ec->message()); cmd->callback->addData(ICMD_ERRNO, -EIO); cmd->callback->addData(ICMD_ERRNO_STR, read_handler.ec->message()); cmd->HandleCompletion(); return; } else { // other end closed connection -- treat that as timeout break; } } // append read byte to string. received.push_back(buf[0]); } // we got at least one byte -- assemble answer. LOGTRACE(logger, "Serial read " << received.length() << " bytes"); cmd->callback->addData(ICONN_TOKEN_RECEIVE_STRING, received); cmd->callback->addData(ICMD_ERRNO, 0); cmd->HandleCompletion(); return; } bool CConnectSerialAsio::AbortAll() { // obtain mutex LOGINFO(logger, "AbortAll()"); mutex.lock(); // abort all pending commands. std::list::iterator it = cmds.begin(); for (it = cmds.begin(); it != cmds.end(); it++) { CAsyncCommand *c = *it; c->callback->addData(ICMD_ERRNO, -ECANCELED); c->HandleCompletion(); } // stop any run ioservices. ioservice->stop(); mutex.unlock(); LOGINFO(logger, "AbortAll() end"); return true; } bool CConnectSerialAsio::CanAccept() { // for the serial, accept is the same as connect return true; } /** handles async sending */ void CConnectSerialAsio::HandleSend(CAsyncCommand *cmd) { boost::system::error_code ec, handlerec; volatile int result_timer = 0; size_t bytes; unsigned long timeout; struct asyncASIOCompletionHandler write_handler(&bytes, &handlerec); // timeout setup std::string s; try { s = boost::any_cast(cmd->callback->findData( ICONN_TOKEN_SEND_STRING)); } #ifdef DEBUG_SERIALASIO catch (std::invalid_argument &e) { LOGDEBUG(logger, "BUG: required " << ICONN_TOKEN_SEND_STRING << " argument not set"); } catch (boost::bad_any_cast &e) { LOGDEBUG(logger, "Unexpected exception in HandleSend: Bad cast" << e.what()); } #else catch (...); #endif try { timeout = boost::any_cast(cmd->callback->findData( ICONN_TOKEN_TIMEOUT)); } #ifdef DEBUG_SERIALASIO catch (std::invalid_argument &e) { CConfigHelper cfghelper(ConfigurationPath); cfghelper.GetConfig("serial_timeout", timeout, SERIAL_ASIO_DEFAULT_TIMEOUT); } catch (boost::bad_any_cast &e) { LOGDEBUG(logger, "Unexpected exception in HandleSend: Bad cast" << e.what()); timeout = SERIAL_ASIO_DEFAULT_TIMEOUT; } #else catch (...) { CConfigHelper cfghelper(ConfigurationPath); cfghelper.GetConfig("tcptimeout", timeout, SERIAL_ASIO_DEFAULT_TIMEOUT); } #endif deadline_timer timer(*(this->ioservice)); boost::posix_time::time_duration td = boost::posix_time::millisec(timeout); timer.expires_from_now(td); timer.async_wait(boost::bind(&boosthelper_set_result, (int*)&result_timer, 1)); // socket preparation // async_write the whole std::string boost::asio::async_write(*port, boost::asio::buffer(s), write_handler); // run one of the scheduled services size_t num = ioservice->run_one(ec); // ioservice error or timeout if (num == 0 || result_timer) { timer.cancel(ec); port->cancel(ec); LOGTRACE(logger, "Async write timeout"); cmd->callback->addData(ICMD_ERRNO, -ETIMEDOUT); cmd->HandleCompletion(); ioservice->poll(); return; } // cancel the timer, and catch the completion handler timer.cancel(); ioservice->poll(ec); if (*write_handler.ec) { if (*write_handler.ec != boost::asio::error::eof) { LOGDEBUG(logger, "Async write failed with ec=" << *write_handler.ec << " msg="<< write_handler.ec->message()); cmd->callback->addData(ICMD_ERRNO, -EIO); cmd->callback->addData(ICMD_ERRNO_STR, write_handler.ec->message()); } else { cmd->callback->addData(ICMD_ERRNO, -ENOTCONN); LOGTRACE(logger, "Received eof on socket write"); } cmd->HandleCompletion(); return; } if (s.length() != *write_handler.bytes) { LOGDEBUG(logger, "Sent " << *write_handler.bytes << " but expected "<< s.length()); cmd->callback->addData(ICMD_ERRNO, -EIO); cmd->HandleCompletion(); return; } cmd->callback->addData(ICMD_ERRNO, 0); cmd->HandleCompletion(); return; } #endif /* HAVE_COMMS_ASIOSERIAL */ solarpowerlog-solarpowerlog-0.26/src/Connections/CConnectSerialAsio.h000066400000000000000000000101111444065341000261220ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2010-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file CConnectSerialAsio.h * * This interface class abstracts the Comms-Interface (IConnect) to the * boost::asio class * * Boost::asio can act either as a serial class (like RS233, Rs485) * or as TCP class. so copying would mean lots of duplicated code * * Created on: May 16, 2010 * Author: tobi */ #ifndef CCONNCECTSERIALASIO_H_ #define CCONNCECTSERIALASIO_H_ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif #ifdef HAVE_COMMS_ASIOSERIAL #include #include #include #include #include "interfaces/IConnect.h" #include "interfaces/CWorkScheduler.h" #include "configuration/Registry.h" #include "patterns/ICommand.h" #include "Connections/CAsyncCommand.h" /// Default timeout for all operations, if not configured #define SERIAL_ASIO_DEFAULT_TIMEOUT (3000UL) #define SERIAL_ASIO_DEFAULT_INTERBYTETIMEOUT (50UL) using namespace std; /** This class implements a method to connect to TCP/IP via the boost ASIO * library. * * For the interface documentation, please see IConnect. * * */ class CConnectSerialAsio: public IConnect { protected: friend class IConnectFactory; CConnectSerialAsio(const string & configurationname); public: virtual ~CConnectSerialAsio(); virtual void Accept(ICommand *callback); virtual void Connect(ICommand *callback); virtual void Disconnect(ICommand *callback); virtual void SetupLogger(const string& parentlogger, const string & = "") { IConnect::SetupLogger(parentlogger, "Comms_Serial_ASIO"); } virtual void Send(ICommand *callback); virtual void Receive(ICommand *callback); virtual bool CheckConfig(void); virtual bool IsConnected(void); virtual bool AbortAll(); virtual bool CanAccept(); private: boost::asio::io_service *ioservice; boost::asio::serial_port *port; char _cfg_characterlen; boost::asio::serial_port_base::parity _cfg_parity; boost::asio::serial_port_base::stop_bits _cfg_stopbits; boost::asio::serial_port_base::flow_control _cfg_flowctrl; unsigned int _cfg_baudrate; virtual void _main(void); /** push some new work to the worker thread. * \note: If the work can be handled synchronously more efficent, the * work might be executed right away. * * \param cmd to be executed. Will take ownership of object and destroy * it after use. (in other words: will be deleted. But only the struct, * not containing objects!) * * \returns false if work could not be pushed or true if it worked out. */ bool PushWork(CAsyncCommand *cmd); /** Handle "Connect-Command" * * Connects to the configured target. * * \returns true, if job can be removed from queue, false, if it needs to * be handled again * * * */ void HandleConnect(CAsyncCommand *cmd); /** Handle the disconnect command. * * \returns true, if job can be removed from queue, false, if it needs to * be handled again */ void HandleDisconnect(CAsyncCommand *cmd); void HandleReceive(CAsyncCommand *cmd); void HandleSend(CAsyncCommand *cmd); list cmds; sem_t cmdsemaphore; }; #endif /* HAVE_COMMS_ASIOSERIAL */ #endif /* CONNECTIONTCP_H_ */ solarpowerlog-solarpowerlog-0.26/src/Connections/CConnectTCPAsio.cpp000066400000000000000000000602611444065341000256770ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file CConnectionTCPAsio.cpp * * Created on: May 21, 2009 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif #ifdef HAVE_COMMS_ASIOTCPIO #define DEBUG_TCPASIO #include "interfaces/IConnect.h" #include "Connections/CConnectTCPAsio.h" #include #include #include "configuration/CConfigHelper.h" #include #include #include #include #include #include "interfaces/CMutexHelper.h" #include #include #include #include #include #include using namespace boost::posix_time; using namespace std; using namespace boost::asio; using namespace boost; using namespace libconfig; struct asyncASIOCompletionHandler { asyncASIOCompletionHandler( size_t *b, boost::system::error_code *ec ) : bytes(b), ec(ec) { } void operator()( const boost::system::error_code& e, std::size_t bytes_transferred ) { *bytes = bytes_transferred; *ec = e; } void operator() (const boost::system::error_code& e) { *ec = e; } private: // note, we need a pointer as boost seems to make a copy of our handler... size_t *bytes; boost::system::error_code *ec; }; /** Helping function for timeout and receive, will be called by boost::asio. * this handler just will set the int store with the value value. */ static void boosthelper_set_result( int* store, int value ) { if (store) *store = value; } CConnectTCPAsio::CConnectTCPAsio( const string &configurationname ) : IConnect(configurationname) { // Generate our own asio ioservice // TODO check if one central would do that too... configured_as_server = false; _connected = false; ioservice = new io_service; sockt = new ip::tcp::socket(*ioservice); sem_init(&cmdsemaphore, 0, 0); } CConnectTCPAsio::~CConnectTCPAsio() { // Request thread to exit. SetThreadTermRequest(); // Try a clean shutdown mutex.lock(); cmds.clear(); ioservice->stop(); if (_connected) { boost::system::error_code ec; sockt->cancel(ec); sockt->close(ec); } mutex.unlock(); // need to post to semaphore to interrupt worker thread to terminate. sem_post(&cmdsemaphore); LOGDEBUG(logger, "Waiting for thread to join"); workerthread.join(); LOGDEBUG(logger, "Joined."); delete sockt; delete ioservice; sem_destroy(&cmdsemaphore); } void CConnectTCPAsio::Connect( ICommand *callback ) { assert(callback); CAsyncCommand *co = new CAsyncCommand(CAsyncCommand::CONNECT, callback); PushWork(co); } void CConnectTCPAsio::Disconnect( ICommand *callback ) { assert(callback); CAsyncCommand *co = new CAsyncCommand(CAsyncCommand::DISCONNECT, callback); PushWork(co); } void CConnectTCPAsio::Send( ICommand *callback) { assert(callback); CAsyncCommand *commando = new CAsyncCommand(CAsyncCommand::SEND, callback); PushWork(commando); } void CConnectTCPAsio::Receive( ICommand *callback ) { assert(callback); CAsyncCommand *commando = new CAsyncCommand(CAsyncCommand::RECEIVE, callback); PushWork(commando); } void CConnectTCPAsio::Accept(ICommand *callback) { assert(callback); CAsyncCommand *commando = new CAsyncCommand(CAsyncCommand::ACCEPT, callback); PushWork(commando); } bool CConnectTCPAsio::IsConnected( void ) { return _connected; } bool CConnectTCPAsio::CheckConfig( void ) { string setting; bool fail = false; CConfigHelper cfghelper(ConfigurationPath); if (cfghelper.GetConfig("tcpmode",setting)) { if (setting == "server") { fail |= !cfghelper.CheckConfig("tcpadr", libconfig::Setting::TypeString,true); fail |= !cfghelper.CheckConfig("tcpport", libconfig::Setting::TypeInt); if (!fail) this->configured_as_server = true; } } if (!configured_as_server) { fail |= !cfghelper.CheckConfig("tcpadr", libconfig::Setting::TypeString); fail |= !cfghelper.CheckConfig("tcpport", libconfig::Setting::TypeString); fail |= !cfghelper.CheckConfig("tcptimeout", libconfig::Setting::TypeInt, true); if (cfghelper.CheckConfig("tcptimeout", libconfig::Setting::TypeInt, true,false)) { LOGWARN(logger,"Tcptimeout configuration parameter has changed and might not work anymore! It will be removed soon."); LOGWARN(logger,"Timeouts are now configured in the inverter class, see documentation."); } } if (!fail) { StartWorkerThread(); return true; } return false; } void CConnectTCPAsio::_main( void ) { LOGTRACE(logger, "Starting helper thread"); std::auto_ptr work(new boost::asio::io_service::work(*ioservice)); while (!IsTermRequested()) { int syscallret; syscallret = sem_wait(&cmdsemaphore); if (syscallret == -1) { continue; } mutex.lock(); // Safety check if there's really work. if (cmds.empty()) { mutex.unlock(); continue; } CAsyncCommand *donow = cmds.front(); cmds.pop_front(); // if the ioservice has been stopped (e.g abortall call) // reset it and spawn a new boost::asio::io_service::work if the ioservice has been stopped. // must be done with mutex held due to a possible race with abortall. if (ioservice->stopped()) { LOGDEBUG(logger, "ioservice stopped"); work.reset(new boost::asio::io_service::work(*ioservice)); ioservice->reset(); } mutex.unlock(); switch (donow->c) { case CAsyncCommand::CONNECT: HandleConnect(donow); break; case CAsyncCommand::DISCONNECT: HandleDisconnect(donow); break; case CAsyncCommand::RECEIVE: HandleReceive(donow); break; case CAsyncCommand::SEND: HandleSend(donow); break; case CAsyncCommand::ACCEPT: HandleAccept(donow); break; default: LOGDEBUG_SA(logger, __COUNTER__, "BUG: Unknown command " << donow->c << " received."); break; } delete donow; } LOGDEBUG(logger, "Thread terminating"); IConnect::_main(); } bool CConnectTCPAsio::PushWork( CAsyncCommand *cmd ) { mutex.lock(); cmds.push_back(cmd); mutex.unlock(); sem_post(&cmdsemaphore); workerthread.interrupt(); return true; } void CConnectTCPAsio::HandleConnect( CAsyncCommand *cmd ) { string strhost, port; volatile int result_timer = 0; boost::system::error_code handler_ec; struct asyncASIOCompletionHandler connect_handler(NULL, &handler_ec); // if already connected, ignore the command, pretend success. if (IsConnected()) { LOGDEBUG_SA(logger, __COUNTER__, __PRETTY_FUNCTION__ << " Already connected"); cmd->callback->addData(ICMD_ERRNO, 0); cmd->HandleCompletion(); return; } // fail, if we are configured for inbound connections. if (configured_as_server) { LOGDEBUG_SA(logger, __COUNTER__, "BUG: Configured as server! Unexpeced connect."); cmd->callback->addData(ICMD_ERRNO, -EPERM); cmd->callback->addData(ICMD_ERRNO_STR, std::string("TCP/IP Comms configured as server. Cannot use connect method!")); cmd->HandleCompletion(); return; } CConfigHelper cfghelper(ConfigurationPath); unsigned long timeout = -1; try { timeout = boost::any_cast( cmd->callback->findData(ICONN_TOKEN_TIMEOUT)); } catch (std::invalid_argument &e) { cfghelper.GetConfig("tcptimeout", timeout, TCP_ASIO_DEFAULT_TIMEOUT); LOGDEBUG_SA(logger, __COUNTER__, "Depreciated fall back to tcptimeout"); } catch (boost::bad_any_cast &e) { LOGDEBUG(logger, "BUG: Handling Connect: Bad cast for " << ICONN_TOKEN_TIMEOUT); timeout = TCP_ASIO_DEFAULT_TIMEOUT; } cfghelper.GetConfig("tcpadr", strhost); cfghelper.GetConfig("tcpport", port); boost::system::error_code ec; ip::tcp::resolver resolver(*ioservice); ip::tcp::resolver::query query(strhost.c_str(), port); // returns on error a default constructed iterator ... ip::tcp::resolver::iterator iter = resolver.resolve(query, ec); ip::tcp::resolver::iterator end; // ... which is a "End marker" itself. boost::asio::deadline_timer timer(*ioservice); boost::posix_time::time_duration td = boost::posix_time::millisec(timeout); timer.expires_from_now(td); timer.async_wait( boost::bind(&boosthelper_set_result, (int*) &result_timer, 1)); while (iter != end) { ip::tcp::endpoint endpoint = *iter++; LOGDEBUG_SA(logger, __COUNTER__, "Connecting to " << endpoint); handler_ec.clear(); sockt->async_connect(endpoint, connect_handler); size_t num = ioservice->run_one(ec); if (num == 0) { LOGDEBUG(logger, __PRETTY_FUNCTION__ << " WTF: no service run!!!"); } if (result_timer) { LOGINFO_SA(logger, LOG_SA_HASH("Connection-error-reason"), "Connection timeout"); cmd->callback->addData(ICMD_ERRNO, -ETIMEDOUT); cmd->callback->addData(ICMD_ERRNO_STR, std::string("Connection timeout")); cmd->HandleCompletion(); sockt->cancel(ec); ioservice->poll(); return; } if (ioservice->stopped()) { LOGINFO_SA(logger, LOG_SA_HASH("Connection-error-reason"), "Connection aborted"); cmd->callback->addData(ICMD_ERRNO, -ECANCELED); cmd->callback->addData(ICMD_ERRNO_STR, std::string("Connection aborted")); cmd->HandleCompletion(); ioservice->poll(); return; } // not timer, so it must be the connect that returned. if (handler_ec.value() == 0) { break; } } timer.cancel(ec); ioservice->poll(ec); if (handler_ec) { cmd->callback->addData(ICMD_ERRNO, -ECONNREFUSED); if (!handler_ec.message().empty()) { LOGDEBUG_SA(logger, __COUNTER__, "Connection error: " << handler_ec.message()); cmd->callback->addData(ICMD_ERRNO_STR, handler_ec.message()); } cmd->HandleCompletion(); return; } LOGINFO(logger, "Connected to " << strhost); _connected = true; cmd->callback->addData(ICMD_ERRNO, 0); cmd->HandleCompletion(); return; } void CConnectTCPAsio::HandleDisconnect( CAsyncCommand *cmd ) { boost::system::error_code ec; std::string message; int error = 0; if (!IsConnected()) { cmd->callback->addData(ICMD_ERRNO, 0); cmd->HandleCompletion(); return ; } // according boost:asio documentation, one should call shutdown before // close // (http://www.boost.org/doc/libs/1_36_0/doc/html/boost_asio/reference/basic_stream_socket/close/overload2.html) sockt->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); if (ec) { error = -EIO; message = ec.message(); LOGDEBUG_SA(logger,LOG_SA_HASH("diconnect_err"), "Disconnect error while shutdown: " << ec.message()); } ec.clear(); sockt->close(ec); if (ec) { error = -EIO; if (!message.empty()) message = message + " "; message = message + ec.message(); LOGDEBUG_SA(logger,LOG_SA_HASH("diconnect_err2"), "Disconnect error while close: " << ec.message()); } _connected = false; cmd->callback->addData(ICMD_ERRNO, error); if (!message.empty()) { cmd->callback->addData(ICMD_ERRNO_STR, message); } cmd->HandleCompletion(); return; } /** Handle Receive -- asynchronous read from the asio socket with timeout. * * Strategy: * -- get timeout config from caller or configuration (depreciated) * -- spawn timer with timeout setting in background * -- run timer in background (async) * -- setup async read operation of one byte (to detect incoming communication) * -- check if we got a byte or timeout * -- if got the byte, try to read all available bytes (ASIO tells us how many * are pending) * -- if got the timer, cancel socket read and return error. */ void CConnectTCPAsio::HandleReceive( CAsyncCommand *cmd ) { boost::system::error_code ec; boost::system::error_code read_handlerec; volatile int result_timer = 0; size_t read_bytes = 0; long timeout = 0; char buf[2]; struct asyncASIOCompletionHandler read_handler(&read_bytes, &read_handlerec); // when running out of work. // on deletion of this object the ioserver might get stopped, if not // externally AbortAll has been called. // timeout setup try { timeout = boost::any_cast( cmd->callback->findData(ICONN_TOKEN_TIMEOUT)); } catch (std::invalid_argument &e) { CConfigHelper cfghelper(ConfigurationPath); cfghelper.GetConfig("tcptimeout", timeout, TCP_ASIO_DEFAULT_TIMEOUT); LOGDEBUG_SA(logger, __COUNTER__, "Depreciated fallback to tcptimeout"); } catch (boost::bad_any_cast &e) { LOGDEBUG_SA(logger, __COUNTER__, "BUG: Unexpected exception in HandleReceive: Bad cast " << e.what()); timeout = TCP_ASIO_DEFAULT_TIMEOUT; } deadline_timer timer(*(this->ioservice)); boost::posix_time::time_duration td = boost::posix_time::millisec(timeout); timer.expires_from_now(td); timer.async_wait( boost::bind(&boosthelper_set_result, (int*) &result_timer, 1)); // socket preparation // async_read: boost:asio seems not to allow auto-buffers, // so we will just read one byte and when this is available, we'll // check for if there are some others left // seen when connecting to localhost: ioservice returns, with ec = eof, // but still connected. So if retry to async_read_some as long as timeout // not expired. (but we will limit to a few times in case of a real eof and // a long timeout) size_t num = 0; int maxsleep = 5; // we limit the hack to 5 times. do { bool sleep = false; if (!sleep) { boost::posix_time::time_duration td2 = boost::posix_time::millisec( 25); deadline_timer timer(*ioservice, td2); try { timer.wait(); maxsleep--; } catch (...) { } } sleep = true; sockt->async_read_some(boost::asio::buffer(&buf, 1), read_handler); num = ioservice->run_one(ec); } while (maxsleep != 0 && !result_timer && 0 == read_bytes); // ioservice error or timeout if (num == 0 || result_timer) { if (result_timer) { LOGDEBUG(logger, "Read timeout"); cmd->callback->addData(ICMD_ERRNO_STR, std::string("Read timeout")); cmd->callback->addData(ICMD_ERRNO, -ETIMEDOUT); } else if (ioservice->stopped()){ LOGDEBUG(logger, "ioservice stopped (1)"); cmd->callback->addData(ICMD_ERRNO_STR, std::string("Aborted 1")); cmd->callback->addData(ICMD_ERRNO, -ECANCELED); } else { LOGDEBUG(logger, "IO Service error: " << ec.message()); cmd->callback->addData(ICMD_ERRNO_STR, std::string("IO-service error " + ec.message())); cmd->callback->addData(ICMD_ERRNO, -EIO); } cmd->HandleCompletion(); timer.cancel(ec); sockt->cancel(ec); ioservice->poll(ec); return; } timer.cancel(); ioservice->poll(ec); if (read_handlerec || ioservice->stopped()) { if (read_handlerec != boost::asio::error::eof) { LOGDEBUG(logger, "Async read failed with ec=" << read_handlerec << " msg="<< read_handlerec.message()); cmd->callback->addData(ICMD_ERRNO, -EIO); cmd->callback->addData(ICMD_ERRNO_STR, read_handlerec.message()); } else if (ioservice->stopped()) { cmd->callback->addData(ICMD_ERRNO_STR, std::string("Aborted 2")); cmd->callback->addData(ICMD_ERRNO, -ECANCELED); LOGDEBUG(logger, "ioservice stopped (2)"); } else { cmd->callback->addData(ICMD_ERRNO, -ENOTCONN); LOGDEBUG(logger, "Received eof on socket read"); } cmd->HandleCompletion(); return; } if (1 != read_bytes) { LOGDEBUG(logger,"Received " << read_bytes << " but expected only 1 byte"); cmd->callback->addData(ICMD_ERRNO, -EIO); cmd->HandleCompletion(); return ; } size_t avail = sockt->available(); size_t tmp; size_t numrecvd = 1; std::string receivestr; LOGTRACE(logger, "There are " << avail << " bytes ready to read"); char recved[256]; receivestr.append(buf,1); while (avail > 0) { size_t readmax = avail > 256 ? avail : 256; tmp = sockt->read_some(asio::buffer(recved, readmax), ec); if (ioservice->stopped()) { cmd->callback->addData(ICMD_ERRNO_STR, std::string("Aborted 3")); cmd->callback->addData(ICMD_ERRNO, -ECANCELED); LOGTRACE(logger, "ioservice stopped. (3)"); cmd->HandleCompletion(); return; } avail = sockt->available(); numrecvd += tmp; if (tmp) receivestr.append(recved, tmp); // check if an error occurred. if (ec) { int error; switch (ec.value()) { case boost::asio::error::eof: error = -ENOTCONN; break; default: error = -EIO; break; } cmd->callback->addData(ICONN_TOKEN_RECEIVE_STRING, receivestr); LOGDEBUG(logger, "Error while read remaining bytes: " << ec.message()); cmd->callback->addData(ICMD_ERRNO_STR, ec.message()); cmd->callback->addData(ICMD_ERRNO, error); cmd->HandleCompletion(); return; } } cmd->callback->addData(ICONN_TOKEN_RECEIVE_STRING, receivestr); cmd->callback->addData(ICMD_ERRNO, 0); cmd->HandleCompletion(); return ; } /** handles async sending */ void CConnectTCPAsio::HandleSend( CAsyncCommand *cmd ) { std::string s; boost::system::error_code ec; boost::system::error_code write_handler_ec; volatile int result_timer = 0; size_t wrote_bytes = 0; unsigned long timeout; struct asyncASIOCompletionHandler write_handler(&wrote_bytes, &write_handler_ec); try { s = boost::any_cast(cmd->callback->findData(ICONN_TOKEN_SEND_STRING)); } catch (std::invalid_argument &e) { LOGDEBUG_SA(logger, __COUNTER__, "BUG: HandleSend: " << ICONN_TOKEN_SEND_STRING << " argument missing"); } catch (boost::bad_any_cast &e) { LOGDEBUG(logger, "BUG: HandleSend: Bad cast " << e.what()); } try { timeout = boost::any_cast(cmd->callback->findData(ICONN_TOKEN_TIMEOUT)); } catch (std::invalid_argument &e) { CConfigHelper cfghelper(ConfigurationPath); cfghelper.GetConfig("tcptimeout", timeout, 3000UL); LOGDEBUG_SA(logger, __COUNTER__, "BUG: Depreciated fallback to tcptimeout"); } catch (boost::bad_any_cast &e) { LOGDEBUG_SA(logger, __COUNTER__, "BUG: Unexpected exception in HandleSend: Bad cast " << e.what()); timeout = TCP_ASIO_DEFAULT_TIMEOUT; } deadline_timer timer(*(this->ioservice)); boost::posix_time::time_duration td = boost::posix_time::millisec(timeout); timer.expires_from_now(td); timer.async_wait(boost::bind(&boosthelper_set_result, (int*) &result_timer, 1)); // socket preparation // async_write the whole std::string boost::asio::async_write(*sockt, boost::asio::buffer(s), write_handler); // run one of the scheduled services size_t num = ioservice->run_one(ec); // ioservice error or timeout if (num == 0 || result_timer) { timer.cancel(ec); sockt->cancel(ec); LOGDEBUG(logger, "Async write timeout"); cmd->callback->addData(ICMD_ERRNO, -ETIMEDOUT); cmd->HandleCompletion(); ioservice->poll(); return; } timer.cancel(); ioservice->poll(); LOGTRACE(logger,"Sent " << wrote_bytes << " Bytes of " << s.length()); if (write_handler_ec) { if (write_handler_ec != boost::asio::error::eof) { LOGDEBUG(logger,"Async write failed with ec=" << write_handler_ec << " msg="<< write_handler_ec.message()); cmd->callback->addData(ICMD_ERRNO, -EIO); cmd->callback->addData(ICMD_ERRNO_STR, write_handler_ec.message()); } else { cmd->callback->addData(ICMD_ERRNO, -ENOTCONN); LOGDEBUG(logger, "Received eof during socket write"); } cmd->HandleCompletion(); return ; } if (s.length() != wrote_bytes) { LOGDEBUG(logger,"Sent " << wrote_bytes << " but expected "<< s.length() ); cmd->callback->addData(ICMD_ERRNO, -EIO); cmd->HandleCompletion(); return ; } cmd->callback->addData(ICMD_ERRNO, 0); cmd->HandleCompletion(); return ; } bool CConnectTCPAsio::AbortAll(void) { // obtain mutex mutex.lock(); LOGDEBUG(logger, __PRETTY_FUNCTION__ << " Aborting " << cmds.size() << " backlog entries"); // abort all pending commands. std::list::iterator it = cmds.begin(); for (it = cmds.begin(); it != cmds.end(); it++) { CAsyncCommand *c = *it; c->callback->addData(ICMD_ERRNO, -ECANCELED); c->HandleCompletion(); } // stop any run ioservices. ioservice->stop(); mutex.unlock(); LOGDEBUG(logger, __PRETTY_FUNCTION__ << " Done"); return true; } // Server mode. Listen to incoming connections. void CConnectTCPAsio::HandleAccept(CAsyncCommand *cmd) { //LOGDEBUG(logger, __PRETTY_FUNCTION__ << " handling " << cmd << "with ICmd " << cmd->callback ); int port; std::string ipadr; // Do not accept if already connected. // Pretend success in this case. if (IsConnected()) { cmd->callback->addData(ICMD_ERRNO, 0); cmd->HandleCompletion(); return; } // Fail if this is not configured as a server. if (!configured_as_server) { cmd->callback->addData(ICMD_ERRNO,-EPERM); cmd->HandleCompletion(); return; } CConfigHelper cfghelper(ConfigurationPath); cfghelper.GetConfig("tcpadr", ipadr,std::string("any")); cfghelper.GetConfig("tcpport", port); boost::scoped_ptr endpoint; if (ipadr == "any") { endpoint.reset(new ip::tcp::endpoint(ip::tcp::v4(),port)); } else if ( ipadr == "any_v6") { endpoint.reset(new ip::tcp::endpoint(ip::tcp::v6(),port)); } else { ip::address adr = ip::address::from_string(ipadr); endpoint.reset(new ip::tcp::endpoint(adr,port)); } boost::system::error_code ec; LOGINFO(logger,"Waiting for inbound connection on " << ipadr << ":" << port); try { ip::tcp::acceptor acceptor(*ioservice, *endpoint); acceptor.listen(); acceptor.accept(*sockt, ec); } catch (boost::system::system_error &e) { std::string errmsg = e.what(); LOGINFO(logger, "Boost: exception received while accepting: " << errmsg); cmd->callback->addData(ICMD_ERRNO,(long)-EIO); cmd->callback->addData(ICMD_ERRNO_STR, errmsg); cmd->HandleCompletion(); return; } if (ec) { int eval = -ec.value(); if (!eval) { eval = -EIO; } cmd->callback->addData(ICMD_ERRNO, eval); if (!ec.message().empty()) { cmd->callback->addData(ICMD_ERRNO_STR, ec.message()); } cmd->HandleCompletion(); LOGINFO( logger, "Connection failed. Error " << eval << "(" << ec.message() << ")"); return; } LOGINFO(logger, "Connected."); _connected = true; cmd->callback->addData(ICMD_ERRNO, 0); cmd->HandleCompletion(); return; } #endif /* HAVE_COMMS_ASIOTCPIO */ solarpowerlog-solarpowerlog-0.26/src/Connections/CConnectTCPAsio.h000066400000000000000000000113641444065341000253440ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file ConnectionTCPAsio.h * * Created on: May 21, 2009 * Author: tobi */ #ifndef CONNECTIONTCPASIO_H_ #define CONNECTIONTCPASIO_H_ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif #ifdef HAVE_COMMS_ASIOTCPIO #include #include #include #include #include "interfaces/IConnect.h" #include "interfaces/CWorkScheduler.h" #include "configuration/Registry.h" #include "patterns/ICommand.h" #include "Connections/CAsyncCommand.h" /// Default timeout for all operations, if not configured #define TCP_ASIO_DEFAULT_TIMEOUT (3000UL) using namespace std; /** This class implements a method to connect to TCP/IP via the boost ASIO * library. * * For the interface documentation, please see IConnect. * * */ class CConnectTCPAsio: public IConnect { protected: friend class IConnectFactory; CConnectTCPAsio( const string & configurationname ); public: virtual ~CConnectTCPAsio(); /** Request Connection * * Please see the base class IConnect for details * * @param callback will be called after completion, containing also the * result * * \sa IConnect::Conncect() */ virtual void Connect(ICommand *callback); /** Request disconnect * * Please see the base class IConnect for details * * @param callback will be called after completion. * * \sa IConnect::Disconncect() */ virtual void Disconnect(ICommand *callback); virtual void Send(ICommand *callback); /** Try to read from stream * * Please see the base class IConnect for details * * @param callback will be called after completion. * * \sa IConnect::Receive() */ virtual void Receive(ICommand *callback); /** * Get current connection status * * \sa IConnect::IsConnected() */ virtual bool IsConnected(void); virtual void Accept(ICommand *cmd); virtual bool CanAccept() { return this->configured_as_server; } /// Aborts all IOs. /// Note: The current executed I/O will also be cancelled, but error /// reporting might report a wrong error. (e.g timeout instead of cancelled) virtual bool AbortAll(void); virtual bool CheckConfig(void); virtual void SetupLogger(const string& parentlogger, const string & = "") { IConnect::SetupLogger(parentlogger, "Comms_TCP_ASIO"); } private: boost::asio::io_service *ioservice; boost::asio::ip::tcp::socket *sockt; virtual void _main(void); /** push some new work to the worker thread. * \note: If the work can be handled synchronously more efficent, the * work might be executed right away. * * \param cmd to be executed. Will take ownership of object and destroy * it after use. (in other words: will be deleted. But only the struct, * not containing objects!) * * \returns false if work could not be pushed or true if it worked out. */ bool PushWork(CAsyncCommand *cmd); /** Handle "Connect-Command" * * Connects to the configured target. * * \returns true, if job can be removed from queue, false, if it needs to * be handled again * * * */ void HandleConnect(CAsyncCommand *cmd); /** Handle the disconnect command. * * \returns true, if job can be removed from queue, false, if it needs to * be handled again */ void HandleDisconnect(CAsyncCommand *cmd); void HandleReceive(CAsyncCommand *cmd); void HandleSend(CAsyncCommand *cmd); void HandleAccept(CAsyncCommand *cmd); list cmds; sem_t cmdsemaphore; bool configured_as_server; // Work-around for https://svn.boost.org/trac/boost/ticket/7392 bool _connected; }; #endif /* HAVE_COMMS_ASIOTCPIO */ #endif /* CONNECTIONTCP_H_ */ solarpowerlog-solarpowerlog-0.26/src/Connections/factories/000077500000000000000000000000001444065341000242665ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Connections/factories/IConnectFactory.cpp000066400000000000000000000046201444065341000300260ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file IConnectFactory.cpp * * \date Created on: May 16, 2009 * \author: Tobias Frost (coldtobi) */ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif #include "configuration/Registry.h" #include "configuration/CConfigHelper.h" #include "Connections/factories/IConnectFactory.h" #include "Connections/CConnectDummy.h" #include #ifdef HAVE_COMMS_ASIOTCPIO #include "Connections/CConnectTCPAsio.h" #endif #ifdef HAVE_COMMS_ASIOSERIAL #include "Connections/CConnectSerialAsio.h" #endif #ifdef HAVE_COMMS_SHAREDCONNECTION #include "Connections/sharedconnection/CSharedConnection.h" #endif using namespace std; /** Facortry for generation of connection methods. * Give it the configurationpath, and out of the config, it will * generate the right class. * * If the class is not known, it will return a dummy connection class. * So you can also create inverters or derived classes without comms. */ IConnect * IConnectFactory::Factory( const string &configurationpath ) { string type = ""; CConfigHelper cfghelper(configurationpath); cfghelper.GetConfig("comms",type); #ifdef HAVE_COMMS_ASIOTCPIO if (type == COMMS_ASIOTCP_ID) { return new CConnectTCPAsio(configurationpath); } #endif #ifdef HAVE_COMMS_ASIOSERIAL if (type == COMMS_ASIOSERIAL_ID ) { return new CConnectSerialAsio(configurationpath); } #endif #ifdef HAVE_COMMS_SHAREDCONNECTION if (type == COMMS_SHARED_ID) { return new CSharedConnection(configurationpath); } #endif return new CConnectDummy(configurationpath); } solarpowerlog-solarpowerlog-0.26/src/Connections/factories/IConnectFactory.h000066400000000000000000000041701444065341000274730ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2015 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file IConnectFactory.h * * \date Created on: May 16, 2009 * \author: Tobias Frost (coldtobi) */ #ifndef CONNECTIONFACTORY_H_ #define CONNECTIONFACTORY_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_COMMS_ASIOTCPIO #define COMMS_ASIOTCP_ID "TCP/IP" #else #define COMMS_ASIOTCP_ID #endif #ifdef HAVE_COMMS_ASIOSERIAL #define COMMS_ASIOSERIAL_ID "RS2xx" #else #define COMMS_ASIOSERIAL_ID #endif #ifdef HAVE_COMMS_SHAREDCONNECTION #define COMMS_SHARED_ID "SharedConnection" #else #define COMMS_SHARED_ID #endif #include "Connections/interfaces/IConnect.h" using namespace std; /** Factory for IConnection-Classes * *\ingroup factories */ class IConnectFactory { public: /** Factory for generation of connection methods. * Give it the configurationpath, and out of the config, it will * generate the right class. * * If the class is not known, it will return a dummy connection class. * So you can also create inverters or derived classes without commms. * * \parameter configurationpath Where to extract the type of the * requested comms? * */ static IConnect* Factory( const string &configurationpath ); protected: IConnectFactory() {}; public: virtual ~IConnectFactory() {}; }; #endif /* CONNECTIONFACTORY_H_ */ solarpowerlog-solarpowerlog-0.26/src/Connections/interfaces/000077500000000000000000000000001444065341000244325ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Connections/interfaces/IConnect.cpp000066400000000000000000000043041444065341000266410ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file IConnect.cpp * * Created on: May 16, 2009 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "IConnect.h" #include "configuration/Registry.h" using namespace std; IConnect::IConnect(const string& configurationname) { ConfigurationPath = configurationname; _thread_is_running = false; _thread_term_request = false; } void IConnect::SetupLogger(const string &parentlogger, const string & spec) { logger.Setup(parentlogger, spec); } IConnect::~IConnect() { mutex.lock(); if (_thread_is_running) { _thread_term_request = true; workerthread.interrupt(); } mutex.unlock(); workerthread.join(); } void IConnect::StartWorkerThread(void) { mutex.lock(); if (!_thread_is_running) { workerthread = boost::thread(boost::bind(&IConnect::_main, this)); _thread_is_running = true; } mutex.unlock(); } bool IConnect::IsTermRequested(void) { mutex.lock(); bool ret = _thread_term_request; mutex.unlock(); return ret; } void IConnect::Noop(ICommand* cmd) { // Standard noop is just to schedule this work with no error set. cmd->addData(ICMD_ERRNO,(int)0); Registry::GetMainScheduler()->ScheduleWork(cmd); } bool IConnect::IsThreadRunning(void) { mutex.lock(); bool ret = _thread_is_running; mutex.unlock(); return ret; } solarpowerlog-solarpowerlog-0.26/src/Connections/interfaces/IConnect.h000066400000000000000000000231671444065341000263160ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file IConnect.h * * Created on: May 16, 2009 * Author: tobi */ #ifndef ICONNECT_H_ #define ICONNECT_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "configuration/ILogger.h" #include #include "patterns/ICommand.h" #include using namespace std; // USED ICOMMAND TOKENS /// Receive-result of the Transaction (std::string) /// might be not present in case of error. #define ICONN_TOKEN_RECEIVE_STRING "ICON_RECEIVE_STRING" /// (private token) Send this string over the connection /// This is used to communicate to the worker thread what it should send. #define ICONN_TOKEN_SEND_STRING "ICON_SEND_STRING" /// Timeout modifier -- with this optional parameter the timeout parameter /// can be overridden from the config for the current operation. /// This allows fine-grade timeouts for any operation /// Note: If not specified, the implementation will /// either use a default value /// or retrieve a configuration value. /// unit is ms. #define ICONN_TOKEN_TIMEOUT "ICON_TIMEOUT" /** SharedComms Request Atomic-Block * * An Atomic Comms block is a series of commands for communication that must * not be interrupted by other communications. * * This is used for SharedComms. * * Use the defines below to request and cease request atomic comms. * (You need to request in any of the comms steps, and the last one in the * atomic block needs to cease the request.) * * datatype needs to be bool. * */ #define ICONN_ATOMIC_COMMS "ICON_ATOMIC_COMMS" /** request the comms to be atomic */ #define ICONN_ATOMIC_COMMS_REQUEST ((bool)true) /** after this request the comms atomic block is ended. */ #define ICONN_ATOMIC_COMMS_CEASE ((bool)false) /** Interface for all communication classes * * This Interface is the API for all concrete communication methods. * The class is abstract, so it cannot be instantiated by itself. * * Anyway, it is intended, that the class is only created by the IConnectFactory. * * Asynchronous operations * * The commands are dispatched using the ICommand interface. In the ICommand * objects the data is placed, identification is possible using the token * * The result is placed into the ICommand-data, also using the token system. * * For example, result codes are placed in the token "ICMD_ERRNO" with the data * as "integer", using the "errno" conventions: * Negative numbers indicates an error. * Please see the documentation of the methods for expected error codes. * (Storage is done with the boost::any classes, see the ICommand interface) * * ICommand ownership * As usual, ICommands that are submitted to this class, will be owned by this class. * So do not delete them, as they will be automatically deleted by the WorkScheduler. * * \note If async operations would be overkill, because the result is * immediately known, one can also implement the async commands synchronously, * as long as it uses the ICommand for notification of the result. * * This can be done by directly setting the result in the provided ICommand and * call Registry::GetMainScheduler()->ScheduleWork(ICommand) afterward. * CConnectDummy does this in its Dispatch_Error() routine. */ class IConnect { public: /// Constructor. /// /// The constructor gets the configuration path to be used to extract /// its configuration. /// IConnect(const string &configurationname); /// Setup the Logger. virtual void SetupLogger(const string& parentlogger, const string &spec = "Comms"); virtual ~IConnect(); /** Connects to the target, establish communication link. * * The target and the settings are retrieved out of the configuration. * * Connect asynchronous and use the ICommand to tell the result in ICMD_ERRNO * * \note If asynchronous operations would be overkill, because the result is * immediately known, one can also implement the asynchronous operations * as synchronous ones as long as it uses the ICommand as notification * for the result. */ virtual void Connect(ICommand *callback) = 0; /** Tear down the connection. * * Disconnect async and use the ICommand to tell the result. * * The result will be placed in the supplied ICommand's data field. * * The value will be usually EIO, as one has to ask himself: What can go wrong here? * * \note Try hard to get the comm into a known state, where connect will * be able to recover, or reconnection might be futile as recovery strategy. * */ virtual void Disconnect(ICommand *callback) = 0; /** Asynchronous send interface * The data needs now to be embedded as data into the ICommand using the * token ICONN_TOKEN_SEND_STRING. * * As usual, results are passed using the ICommand supplied. */ virtual void Send(ICommand *cmd) = 0; /** Receive data from connection and place it into a std::string * * Try to receive data from the other end and place everything readed * into the supplied std::string. * * \param cmd ICommand to be used for async notification. * * The result is presented in the returned ICommand, using those TOKENs: * * ICMD_ERRNO -- for errors: If you receive a value <0 -- error happened. * the type in this boost:any is integer. * ICMD_ERRNO_STR -- optional for an human readable error message. * However, it is recommended to set this token. * the boost:any type is std::string * ICONN_TOKEN_RECEIVE_STRING -- received data from communication. * the boost:any type is std::string * * Regarding ICMD_ERRNO, this errorno are defined and should be used / evaluated * EIO I/O Error on the comms. Reason unknown or something * unexpected happended. (one should close and reopen the connection) * * ETIMEDOUT Read request timed out: No bytes received during * configured timeout. * * ENOTCONN Connection went down, e.g. eof received. */ virtual void Receive(ICommand *cmd) = 0; /** NoOperation * Will just do nothing :) * * Can be used in a derived class for housekeeping, * for example the CSharedConnection use this to provide an easy way to * stop the atomic_block constraint. * \sa CSharedConnectinSlave::Noop()*/ virtual void Noop(ICommand *cmd); /// Check the configuration for validity. Return false on config errors. /// (program will abort then!) virtual bool CheckConfig(void) = 0; /// Check at runtime if the communication class supports the "Accept" /// method. /// \returns true if Accept() works, false if not. virtual bool CanAccept(void) = 0; /// Check if we believe the connection is still active /// Note: if the concrete implementation cannot tell, /// it should always return true, as the default implementation does. /// (the inverter class has to do some kind of timeout-handling anyway) virtual bool IsConnected(void) { return true; } /// Accept an inbound connection to be used for the communication. /// (if supported by the underlying transport mechanism.) /// \param cmd callback object to be used after completion. /// \note cmd must be provided (non-NULL) /// \sa CanAccept() /// \warning If you use Accept() and Connect() on the same object, /// the behavior is undefined. virtual void Accept(ICommand */*cmd*/) { return; } /// Abort all pending commands /// (Try to) abort everything pending, like waiting for all I/O and clear /// all other (queued) commands. /// Answer all unfinished command with an error, preferable ECANCELED. /// returns true if succeeded, false if not supported by the connection /// object virtual bool AbortAll() = 0; protected: /// Storage for the Configuration Path to extract settings. string ConfigurationPath; /// Associated logger. /// \note: By default, it will attached to the root logger. If unwanted, /// use the SetupLogger() call. (As IInverterBase derived classes do) ILogger logger; /// ASYNC OPERATION /// Thread boost::thread workerthread; /// Mutex to protect data boost::recursive_mutex mutex; /// function of the thread. /// \note: if overridden, the overriding function has to call this one /// right before exiting! virtual void _main(void) { mutex.lock(); _thread_is_running = false; mutex.unlock(); } /// Start the Worker thread. virtual void StartWorkerThread(void); /// Check if termination of the worker thread has been requested virtual bool IsTermRequested(void); /// Check if thread is running /// /// \note this does not check if the thread has crashed, only if it has not /// self-terminated or never started.. virtual bool IsThreadRunning(void); protected: virtual void SetThreadTermRequest(void) { mutex.lock(); _thread_term_request = true; mutex.unlock(); } protected: /// bool to check if the thread is started bool _thread_is_running; /// bool to request termination bool _thread_term_request; }; #endif /* ICONNECT_H_ */ solarpowerlog-solarpowerlog-0.26/src/Connections/sharedconnection/000077500000000000000000000000001444065341000256355ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Connections/sharedconnection/CSharedConnection.cpp000066400000000000000000000057431444065341000317030ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2010-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file CSharedConnection.cpp * * Created on: Sep 12, 2010 * Author: tobi * */ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif #ifdef HAVE_COMMS_SHAREDCONNECTION #include "CSharedConnection.h" #include "configuration/CConfigHelper.h" #include "CSharedConnectionMaster.h" #include "CSharedConnectionSlave.h" using namespace libconfig; CSharedConnection::CSharedConnection(const string & configurationname) : IConnect(configurationname) { concreteSharedConnection = NULL; } CSharedConnection::~CSharedConnection() { if (concreteSharedConnection) delete concreteSharedConnection; } bool CSharedConnection::CanAccept(void) { return concreteSharedConnection->CanAccept(); } bool CSharedConnection::CreateSharedConnectionObject() { if (concreteSharedConnection) return true; CConfigHelper cfg(ConfigurationPath); std::string s; if (!cfg.GetConfig("sharedconnection_type", s)) return false; if (s == "master") { LOGDEBUG(this->logger,"Shared connection master requested."); concreteSharedConnection = new CSharedConnectionMaster( this->ConfigurationPath); } else if (s == "slave") { LOGDEBUG(this->logger,"Shared connection slave requested."); concreteSharedConnection = new CSharedConnectionSlave( this->ConfigurationPath); } else { LOGERROR(this->logger,"Shared connection; Slave or master?"); return false; } concreteSharedConnection->SetupLogger(logger.getLoggername()); return true; } bool CSharedConnection::CheckConfig(void) { CConfigHelper cfg(ConfigurationPath); std::string s; if (!cfg.GetConfig("sharedconnection_type", s)) { LOGERROR(logger,"Configuration Error: Sharedconnection_type not defined. Must be master or slave."); return false; } if ( !CreateSharedConnectionObject() ) { LOGERROR(logger,"Configuration Error: Sharedconnection_type must be master or slave."); return false; } if (!concreteSharedConnection->CheckConfig()) { return false; } return true; } void CSharedConnection::SetupLogger(const string& parentlogger, const string &) { IConnect::SetupLogger(parentlogger, "Comms_SharedConnection"); } #endif solarpowerlog-solarpowerlog-0.26/src/Connections/sharedconnection/CSharedConnection.h000066400000000000000000000175561444065341000313550ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2010-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** * \file CSharedConnection.h * \section CSC_DESIGN Design of the (new) sharedcomms The sharedcomms purpose is to share a communication object (doing the talk over one physical line) and keep track that this communication follow the rules the inverters need. For example, there are inverters which communication needs not to be interrupted as they can only handle one request at a time and before another inverter is allowed to use the communication line the first one has to complete the it. Lets call this types "exclusive communication" Other inverters are not that strict, called here "non-exclusive". For those it is not important if between sending and receiving the answer some other inverter sends its own request. The same is true if you have inverters which will send their data without the prior need to request it. For the implementation, "exlucsive comms" needs to implementation a scheme that ensures that only one inverter object can access the communication line at a given time. As it is hard to detect for the communication object when the inverter is done talking, the inverter needs to hint the communication class. This can be done by adding a data to the ICommand telling "I need that the line a while" and signaling this as long as this is needed. However, to differentiate between "exclusive" and "non-exclusive" usage, also "I'done" needs to be signaled. As an inverter is free to queue more than one command (as comms is a FIFO) shared comms needs to keep track of each "atomic commms block", additionally a inverter might issue more than one of those "atomic comms blocks" in the queue, the shared comms must keep track of this individual blocks. It will assign a "ticket" number as soon as the request is made and the tickets numbers will be then used to establish a FIFO to also consider other customers of the sharedcomms. In this atomic blocks, all requests pending can be immediately posted to the real comms, as this will handle them accordingly. The sharedcomms master however needs to know the completion of an atomic block, so it will divert the associated ICommand to call itself as completion handler, forward the ICommand to its real target and then continue with queuing the next atomic block (if any). Non-exclusive inverters have no problems with scheduling the commands, but due to the missing synchronization between the actions (what the atomic blocks realizes), there are race-conditions when it comes to reading from the comms: When two Inverters read at the same time and a telegram for one of them arrives, bot Inverters will complete there read requests. However, the Inverter which is not the recipient will recognize this during parsing and then it will re-issue the read request. In the meantime some bytes might be already lost, when the first Inverter was "faster" and already issued the next read request, which would completed before the second one issue its subsequent. In this case the 1st Inverter would receive the data for the second and then discard this data. The second would never this this telegram. This can be handled by introducing buffers in the sharedcomms: Every reception is buffered and each inverter will receive the buffered data it did not yet see on a read request -- even if the data was received when there was no active read request at the time. Handling of "non-atomic" block reads. As outlined above reads can cause trouble. This needs to be resolved by the SharedComms to have an reliable operation: - Other requests than read requests are issued the normal handling - If a read request without the hint for an atomic block is sent, the behaviour of the shared comms is changed: --> read requests will be issued to the master, but it's completion handler needs to be diverted to the master. --> any subsequent commands received by the master will abort the current read first and then schedule the command with the result diverted again to the master. The master then will forward the result to the original caller and resume if no other command has entered the queue in the meantime. --> additional read-calls from other inverters will subscribe those slaves to the reception queue: Any receptions will be submitted to all recipients, as long as they are subscribed. (even if there is no active read command, in this case the slave will buffer the data until a read requests is issued. In this case the slave will immediately receive the so-far buffered comms. (The inverter needs to filter out the data for the others) Buffered data is reset after a read or after a disconnect. --> the master will also keep track about the timeouts specified by the receive commands and will inform the caller if they expired without receiption. for this timestamps, wallclock time will be used. please note: "Atomic" and "non-atomic" read operations at the same time are not supported. * Created on: Sep 12, 2010 * Author: coldtobi */ #ifndef CSHAREDCONNECTION_H_ #define CSHAREDCONNECTION_H_ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif #ifdef HAVE_COMMS_SHAREDCONNECTION /** SharedComms Atomic-Block Ticket-Token for the SharedCommms * Added */ #define ICONN_SHARED_TICKET "ICON_SHARED_TICKET" #include #include "Connections/interfaces/IConnect.h" #include "CSharedConnectionMaster.h" class CSharedConnection: public IConnect { protected: friend class IConnectFactory; friend class CSharedConnectionSlave; CSharedConnection(const string & configurationname); public: virtual ~CSharedConnection(); virtual void Connect(ICommand *callback) { assert(concreteSharedConnection); concreteSharedConnection->Connect(callback); } virtual void Disconnect(ICommand *callback) { assert(concreteSharedConnection); concreteSharedConnection->Disconnect(callback); } virtual void SetupLogger(const string& parentlogger, const string & = ""); virtual void Send(ICommand *cmd) { assert(concreteSharedConnection); concreteSharedConnection->Send(cmd); } virtual void Receive(ICommand *callback) { assert(concreteSharedConnection); concreteSharedConnection->Receive(callback); } virtual void Accept(ICommand *callback) { assert(concreteSharedConnection); concreteSharedConnection->Accept(callback); } virtual void Noop(ICommand *callback) { assert(concreteSharedConnection); concreteSharedConnection->Noop(callback); } virtual bool CheckConfig(void); virtual bool IsConnected(void) { assert(concreteSharedConnection); return concreteSharedConnection->IsConnected(); } virtual bool AbortAll() { assert(concreteSharedConnection); return concreteSharedConnection->AbortAll(); } virtual bool CanAccept(void); protected: IConnect *GetConcreteSharedConnection(void) { return concreteSharedConnection; } private: bool CreateSharedConnectionObject(); IConnect *concreteSharedConnection; }; #endif #endif /* CSHAREDCONNECTION_H_ */ solarpowerlog-solarpowerlog-0.26/src/Connections/sharedconnection/CSharedConnectionMaster.cpp000066400000000000000000000506751444065341000330630ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2010-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /* * CSharedConnectionMaster.cpp * * Created on: Sep 13, 2010 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif #ifdef HAVE_COMMS_SHAREDCONNECTION #include "configuration/Registry.h" #include "CSharedConnectionMaster.h" #include "Connections/factories/IConnectFactory.h" #include "configuration/Registry.h" #include #include "configuration/CConfigHelper.h" #include "interfaces/CMutexHelper.h" #include "CSharedConnection.h" #include "patterns/ICommand.h" #define STATUS_CONNECTED (1<<0) #define ICONN_SHAREDCOMMS_APIID "ICONN_CSC_APIID" enum { CMD_HANDLEENDOFBLOCK = BasicCommands::CMD_USER_MIN, CMD_NONATOMIC_HANDLEREADCOMPLETION, CMD_NONATOMIC_HANDLETIMEOUT }; CSharedConnectionMaster::CSharedConnectionMaster( const string & configurationname) : IConnect(configurationname) { connection = NULL; last_atomic_cmd = NULL; ticket_cnt = active_ticket = 0; ownslave = new CSharedConnectionSlave(configurationname); ownslave->setMaster(this); non_atomic_mode = false; _nam_interrupted = false; } CSharedConnectionMaster::~CSharedConnectionMaster() { if (connection) delete connection; delete ownslave; } void CSharedConnectionMaster::SetupLogger(const string& parentlogger, const string &specalization) { IConnect::SetupLogger(parentlogger, specalization); ownslave->SetupLogger(logger.getLoggername(),"ownslave"); } void CSharedConnectionMaster::ExecuteCommand(const ICommand *Command) { // LOGDEBUG(logger, __PRETTY_FUNCTION__ << " now handling: " << Command << " (" << Command->getCmd() << ""); // handling end of atomic blocks and issuing pending commands. switch (Command->getCmd()) { case CMD_HANDLEENDOFBLOCK: { // ok, we've got signalled that the last atomic block command has // just been finished. // redirect the answer and clean up our stats. CMutexAutoLock m(mutex); // mutex is needed.... // LOGDEBUG(logger, "Ending Ticket " << active_ticket ); assert(last_atomic_cmd); last_atomic_cmd->mergeData(*Command); // LOGDEBUG(logger, __PRETTY_FUNCTION__ << " scheduling " << last_atomic_cmd); Registry::GetMainScheduler()->ScheduleWork(last_atomic_cmd); last_atomic_cmd = NULL; active_ticket = 0; // answer redirected, now lets check for pending work. // first the "non-atomic-ones" // they can just be queued to the comms object // LOGDEBUG(logger,"non-atomic-backlog:" << non_atomic_icommands_pending.size()); while (non_atomic_icommands_pending.size()) { ICommand *cmd = non_atomic_icommands_pending.front(); non_atomic_icommands_pending.pop(); // LOGDEBUG(logger, "Dispatching1 " << cmd); ICommandDispatcher(cmd); } // LOGDEBUG(logger,"atomic-backlog (blocks):" << atomic_icommands_pending.size()); // after giving the non-atomic priority, resume atomic operation. // we can also queue the next atomic block completely, but we need // again to catch the command completing the block. // as std::maps are ordered, we can just "pop" the first item to // get the next due ticket if (atomic_icommands_pending.size()) { ICommand *cmd; std::map >::iterator it = atomic_icommands_pending.begin(); active_ticket = it->first; std::queue *q = &(it->second); // Convenience only :) while (q->size() > 1) { cmd = q->front(); q->pop(); // LOGDEBUG(logger, "Dispatching2 " << cmd); ICommandDispatcher(cmd); } // now we have one left in the queue, but this could be one // finishing the block (then we need to intercept the result) // or it just one block in the middle of the atomic block // in this case we can just submit the command to the comms. assert(q->size() == 1); cmd = q->front(); bool end_of_block = !boost::any_cast( cmd->findData(ICONN_ATOMIC_COMMS)); if (end_of_block) { ICommand *newcmd = new ICommand(CMD_HANDLEENDOFBLOCK, this); newcmd->mergeData(*cmd); last_atomic_cmd = cmd; cmd->RemoveData(); // free up some memory, data not needed. // LOGDEBUG(logger, "Dispatching3 " << cmd); ICommandDispatcher(newcmd); } else { // LOGDEBUG(logger, "Dispatching4 " << cmd); ICommandDispatcher(cmd); } // LOGDEBUG(logger, "New Ticket " << active_ticket ); // we can delete the map entry for this ticket, as subsequent // commands will be handled directly. atomic_icommands_pending.erase(it); } break; } case CMD_NONATOMIC_HANDLEREADCOMPLETION: { CMutexAutoLock cma(mutex); _nam_interrupted = false; // will be executed if a non-atomic read completes or read // was interrupted with another request. // retrieve error information about this read. int err = 0; try { err = boost::any_cast(Command->findData(ICMD_ERRNO)); } catch (...) { } // Submit everything to the slaves except timeout (handled by slaves) // and the aborted calls. if (err != -ECANCELED && err != -ETIMEDOUT) { // call was not canceled, tha means transmit to all slaves. if (err < 0 ) { LOGDEBUG(logger, "NOT Timeout and NOT CANCELED "<< err); } for (std::list::iterator it = _reading_slaves.begin(); it != _reading_slaves.end(); it++) { ICommand *c = new ICommand( CSharedConnectionSlave::CMD_HANDLEREAD, *it); c->mergeData(*Command); //LOGDEBUG(logger, __PRETTY_FUNCTION__ << "Dispatching receive work to slave comms. " << c << " (slave:" << *it << ")"); Registry::GetMainScheduler()->ScheduleWork(c); } // All other errors than timeouts ends the current read requests. readtimeout = boost::posix_time::not_a_date_time; LOGDEBUG(logger, "not rescheduling read due to err "<< err); return; } if (_reading_slaves.empty()) { // if there are no longer slaves listening, we can cease // reading. readtimeout = boost::posix_time::not_a_date_time; LOGDEBUG(logger, "Not rescheduling read as no reading slaves"); return; } // Restart receive if there is still time for reading. boost::posix_time::ptime pt( boost::posix_time::microsec_clock::universal_time()); if (!readtimeout.is_special() && pt < readtimeout) { boost::posix_time::time_duration d = readtimeout - pt; ICommand *c = new ICommand(CMD_NONATOMIC_HANDLEREADCOMPLETION, this); long timeout = d.total_milliseconds(); if (timeout >= 1000) timeout = 1000; c->addData(ICONN_TOKEN_TIMEOUT, timeout); LOGDEBUG(logger, __PRETTY_FUNCTION__ << " rescheduling read: " << c << " remaining timeout:" << d.total_milliseconds()); connection->Receive(c); } else { LOGDEBUG(logger, __PRETTY_FUNCTION__ << " not rescheduling read"); readtimeout = boost::posix_time::not_a_date_time; } } break; } } void CSharedConnectionMaster::Connect(ICommand *callback) { ownslave->Connect(callback); } void CSharedConnectionMaster::Disconnect(ICommand *callback) { ownslave->Disconnect(callback); } void CSharedConnectionMaster::Send(ICommand *callback) { ownslave->Send(callback); } void CSharedConnectionMaster::Receive(ICommand *callback) { ownslave->Receive(callback); } void CSharedConnectionMaster::Accept(ICommand* callback) { ownslave->Accept(callback); } void CSharedConnectionMaster::Noop(ICommand* callback) { ownslave->Noop(callback); } bool CSharedConnectionMaster::CheckConfig(void) { // Get real configuration path to extract target comms config. string commsconfig = this->ConfigurationPath + ".realcomms"; string s; CConfigHelper h(commsconfig); if (!h.GetConfig("comms", s)) { LOGERROR(logger, "realcomms section: comms missing"); return false; } connection = IConnectFactory::Factory(commsconfig); // connection always valid -- the factory returns a dummy // object if it does not know the comms. if (connection) { connection->SetupLogger(logger.getLoggername()); return connection->CheckConfig(); } LOGFATAL(logger,"Could not create real communication object"); return false; } bool CSharedConnectionMaster::IsConnected(void) { assert(connection); return connection->IsConnected(); } bool CSharedConnectionMaster::AbortAll() { assert(connection); CMutexAutoLock cma(mutex); readtimeout = boost::posix_time::not_a_date_time; return connection->AbortAll(); } long CSharedConnectionMaster::GetTicket() { CMutexAutoLock lock(mutex); ++ticket_cnt; // Overflow protection -- the zero is reserved for "no ticket" if (0 == ticket_cnt) ticket_cnt++; //LOGDEBUG(logger," New ticket requested:" << ticket_cnt); return ticket_cnt; } void CSharedConnectionMaster::Connect(ICommand* callback, CSharedConnectionSlave* s) { assert(callback); assert(s); _HandleNonAtomicReceiveInterrupts(); callback = HandleAtomicBlock(callback, API_CONNECT); if (callback) connection->Connect(callback); } void CSharedConnectionMaster::Disconnect(ICommand* callback, CSharedConnectionSlave* s) { assert(callback); assert(s); // at the moment only the master can disconnect. // and if the master disconnects, we'll disconnect regardless of other // slaves present. // so if someone else disconnects, we'll make a NOOP instead if (s == ownslave) { _HandleNonAtomicReceiveInterrupts(); callback = HandleAtomicBlock(callback, API_DISCONNECT); if (callback) connection->Disconnect(callback); } else { _HandleNonAtomicReceiveInterrupts(); callback->addData(ICMD_ERRNO,0); callback = HandleAtomicBlock(callback, API_NOOP); if (callback) connection->Noop(callback); } } void CSharedConnectionMaster::Send(ICommand* callback, CSharedConnectionSlave* s) { assert(callback); assert(s); _HandleNonAtomicReceiveInterrupts(); callback = HandleAtomicBlock(callback, API_SEND); LOGDEBUG(logger, "CSharedConnectionMaster::Send() ICmd: " << callback); if (callback) connection->Send(callback); } void CSharedConnectionMaster::Receive(ICommand* callback, CSharedConnectionSlave* s) { LOGDEBUG(logger, __PRETTY_FUNCTION__<< " callback:" << callback); bool is_atomic; assert(callback); assert(s); callback = HandleAtomicBlock(callback, API_RECEIVE, &is_atomic); if(is_atomic) { LOGDEBUG(logger, __PRETTY_FUNCTION__<< " sending atomic callback : " << callback); if (callback) connection->Receive(callback); return; } // non-atomic read. if (!non_atomic_mode) { non_atomic_mode = true; // LOGDEBUG(logger,"SharedComms switched to Non-Atomic read mode."); } long timeout; try { timeout = boost::any_cast(callback->findData( ICONN_TOKEN_TIMEOUT)); } catch (...) { LOGDEBUG(logger,"CSharedConnectionMaster::Receive(): Depreciated: Falling back to default timeoout"); timeout = SHARED_CONN_DEFAULTTIMEOUT; } CMutexAutoLock m(mutex); boost::posix_time::ptime ptimetmp = boost::posix_time::microsec_clock::universal_time() + boost::posix_time::milliseconds(timeout); // check if we got already an read request pending. if (readtimeout == boost::posix_time::not_a_date_time) { // LOGDEBUG(logger, "We are not yet reading!"); // not a pending read. // If no read is pending, directly issue a read commmand to the connection, // but divert result to our class for later distribution. // In the Execute member our master will then inform every listening slave // about the incoming comms. // We will need to track timeouts to determine when we can stop // listening. // The slave kept a copy of the read request, so we can use the one // we have been supplied with for our purpose. readtimeout = ptimetmp; callback->setTrgt(this); callback->setCmd(CMD_NONATOMIC_HANDLEREADCOMPLETION); callback->addData(ICONN_TOKEN_TIMEOUT, (long)1000); // LOGDEBUG(logger, __PRETTY_FUNCTION__ << " sending non-atomic to own Execute(): " << callback); connection->Receive(callback); } else { LOGDEBUG(logger, "We are ALREADY reading"); // we've got already a read pending, just check if we need to update // the timeout value if necesary. if( readtimeout < ptimetmp) { // Just record the value, the completion handler will do the rest. readtimeout = ptimetmp; } // in this case we do not need this callback anymore // as we own it, we delete it. // LOGDEBUG(logger, "CSharedConnectionMaster::Receive() deleting: " << callback); delete callback; } } void CSharedConnectionMaster::Noop(ICommand* callback, CSharedConnectionSlave* s) { assert(callback); assert(s); _HandleNonAtomicReceiveInterrupts(); callback = HandleAtomicBlock(callback, API_NOOP); if (callback) connection->Noop(callback); } void CSharedConnectionMaster::Accept(ICommand* callback, CSharedConnectionSlave* s) { assert(callback); assert(s); _HandleNonAtomicReceiveInterrupts(); callback = HandleAtomicBlock(callback, API_ACCEPT); if (callback) connection->Accept(callback); } ICommand* CSharedConnectionMaster::HandleAtomicBlock(ICommand* cmd, enum api_id id, bool *isatomic) { long ticket = 0; try { ticket = boost::any_cast(cmd->findData(ICONN_SHARED_TICKET)); } catch (...) { } if (isatomic) { *isatomic = (0 != ticket); } bool end_of_block = false; try { end_of_block = !boost::any_cast( cmd->findData(ICONN_ATOMIC_COMMS)); } catch (...) { // assert if we could not retrieve this in a signaled atomic block. assert(!ticket); } CMutexAutoLock m(mutex); //LOGDEBUG(logger,"Ticket for this command is: "<< ticket << " (current ticket is " << active_ticket << ")"); // check if this received work is part of an atomic block if (ticket) { // -> yes: check if the active one // check if we currently got an atomic-block if (!active_ticket) { // nope, this will start it. active_ticket = ticket; //LOGDEBUG(logger, "New ticket: "<< ticket); } if (active_ticket == ticket) { //-> yes: check if the current block ends the atomic block if (end_of_block) { //-> yes: this ends the atomic block. // So save Icommand and create a new one to redirect to own // for completion handling in own ICommandTrgt. Send new one to comms. //LOGDEBUG(logger, "Ticket: "<< ticket << " ends soon"); ICommand *newcmd = new ICommand(CMD_HANDLEENDOFBLOCK, this); newcmd->mergeData(*cmd); assert(!last_atomic_cmd); last_atomic_cmd = cmd; cmd->RemoveData(); // free up some memory, this copy wont need // the data. return newcmd; } else { //-> no: just send to comms directly, // the communication will handle it with its fifo. //LOGDEBUG(logger, "Ticket: "<< ticket << " continues"); return cmd; } } else { // -> no, not part of active atomic block, queue for later. // (as currently the interface is occupied) cmd->addData(ICONN_SHAREDCOMMS_APIID, id); // check if this sets a completly new atomic block if (!atomic_icommands_pending.count(ticket)) { //LOGDEBUG(logger, "Ticket: "<< ticket << " new backlog entry"); // Yes, we need a new map-entry. std::pair > p; p.first = ticket; p.second.push(cmd); atomic_icommands_pending.insert(p); } else { // No, just append to queue. //LOGDEBUG(logger, "Ticket: "<< ticket << " new backlog entry to queue"); std::map >::iterator it = atomic_icommands_pending.find(ticket); it->second.push(cmd); } return NULL; } } else { // -> no, not atomic. check if currently an atomic block is locking the comms if (active_ticket) { // -> yes, queue for later cmd->addData(ICONN_SHAREDCOMMS_APIID, id); non_atomic_icommands_pending.push(cmd); return NULL; } else { // -> no, send directly to comms return cmd; } } } void CSharedConnectionMaster::SubscribeSlave(CSharedConnectionSlave* slave, bool subscribe ) { if ( subscribe) { _reading_slaves.push_back(slave); } else { _reading_slaves.remove(slave); } } void CSharedConnectionMaster::ICommandDispatcher(ICommand* cmd) { api_id api = boost::any_cast( cmd->findData(ICONN_SHAREDCOMMS_APIID)); // forward the api calls to the real comms object. switch (api) { case API_CONNECT: connection->Connect(cmd); break; case API_DISCONNECT: connection->Disconnect(cmd); break; case API_RECEIVE: connection->Receive(cmd); break; case API_SEND: connection->Send(cmd); break; case API_ACCEPT: connection->Accept(cmd); break; case API_NOOP: connection->Noop(cmd); break; default: assert(0); break; } } bool CSharedConnectionMaster::CanAccept(void) { assert(connection); return connection->CanAccept(); } void CSharedConnectionMaster::_HandleNonAtomicReceiveInterrupts(void) { return; // due to the bug in boost::asio this method is curently empty. #if 0 // if non-atomic mode, every command during a receive() will interrupt said // read, but this interrupt must only happen once until. CMutexAutoLock cma(&mutex); // only work in non_atomic_mode and if not already interrupted if(!non_atomic_mode || _nam_interrupted) return; _nam_interrupted = true; // ok, we AbortAll() now, this will cancel the receive. connection->AbortAll(); #endif } #endif solarpowerlog-solarpowerlog-0.26/src/Connections/sharedconnection/CSharedConnectionMaster.h000066400000000000000000000132341444065341000325160ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2010-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /* * CSharedConnectionMaster.h * * Created on: Sep 13, 2010 * Author: tobi */ #ifndef CSHAREDCONNECTIONMASTER_H_ #define CSHAREDCONNECTIONMASTER_H_ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif #ifdef HAVE_COMMS_SHAREDCONNECTION #include #include #include #include "Connections/interfaces/IConnect.h" #include "Connections/CAsyncCommand.h" #include "patterns/ICommandTarget.h" #include "patterns/ICommand.h" #include "CSharedConnectionSlave.h" // Token inserted by this or the slave class to specify individual timeouts. // At this timestamp, the command can be considered timed-out. #define SHARED_CONN_TIMEOUTTIMESTAMP "CSharedConnection_Timeout" #define ICONNECT_TOKEN_PRV_ORIGINALCOMMAND "CSharedConnection_Orig_ICommand" #define SHARED_CONN_DEFAULTTIMEOUT (3000UL) class CSharedConnectionMaster : public IConnect , ICommandTarget { protected: friend class CSharedConnection; friend class CSharedConnectionSlave; CSharedConnectionMaster(const string & configurationname); public: virtual ~CSharedConnectionMaster(); void ExecuteCommand(const ICommand *Command); protected: // API Section: Those are from IConnect. They are protected as the // call is allowed only through a CSharedConnection object. virtual void Connect(ICommand *callback); virtual void Disconnect(ICommand *callback); virtual void SetupLogger(const string& parentlogger, const string &spezialisation = ""); virtual void Send(ICommand *cmd); virtual void Receive(ICommand *callback); virtual void Accept(ICommand *callback); virtual bool CanAccept(void); virtual void Noop(ICommand *callback); virtual bool CheckConfig(void); virtual bool IsConnected(void); virtual bool AbortAll(); /// Incoming communication calls from the sharedcomms-slaves. void Connect(ICommand *callback, CSharedConnectionSlave *s); /// Incoming communication calls from the sharedcomms-slaves. void Disconnect(ICommand *callback, CSharedConnectionSlave *s); /// Incoming communication calls from the sharedcomms-slaves. void Send(ICommand *callback, CSharedConnectionSlave *s); /// Incoming communication calls from the sharedcomms-slaves. void Receive(ICommand *callback, CSharedConnectionSlave *s); void Accept(ICommand *callback, CSharedConnectionSlave *s); void Noop(ICommand *callback, CSharedConnectionSlave *s); /// Ticket-Service for atomic-block handling long GetTicket(); /** Register a slave for reading result distribution. * * Adds the slave to the list of recipients * * \param slave to be subscribed or unsubscribed * \param subscribe true to subscribe, false to unsubscribe * * \note multiple calls to subscribe will cause multiple subscriptions. * * \note unsubscribing will remove all subscriptions of this slave.. */ void SubscribeSlave(CSharedConnectionSlave *slave, bool subscribe= true); private: enum api_id { API_DISCONNECT, API_CONNECT, API_SEND, API_RECEIVE, API_ACCEPT, API_NOOP }; /** Atomic-Block Handling for incomming API calls * * Bundled the common handling of slave requests entering the IConnect API. * * \param cmd the callback * \param api_id API ID * \param isatomic if non-null, store the result of the check if it is atomic or not * * \returns the ICommand to be used for the target IConnect or NULL * if no command must be issued at this time. * */ ICommand* HandleAtomicBlock(ICommand *cmd, enum api_id id, bool *isatomic=NULL); /** Helper function to dispatch API calls. */ void ICommandDispatcher(ICommand *cmd); /** Helper function to handle the interruption of non-atomic receives */ void _HandleNonAtomicReceiveInterrupts(void); IConnect *connection; list readcommands; boost::mutex mutex; long ticket_cnt; long active_ticket; /// Proxy to handle the direct calls from the inverter. (code reuse) CSharedConnectionSlave *ownslave; /// list of "free" (non-atomic) commands pending. std::queue non_atomic_icommands_pending; /** container to store all (ticketed) atomic blocks commands that are * pending * map-key is the ticket id, the embedded queue the commands associated * with it. */ std::map > atomic_icommands_pending; /** The ICommand that will end this block.*/ ICommand *last_atomic_cmd; bool non_atomic_mode; /// List of "listening" slaves. std::list _reading_slaves; /// When is the current receive scheduled to timeout? boost::posix_time::ptime readtimeout; bool _nam_interrupted; }; #endif #endif /* CSHAREDCONNECTIONMASTER_H_ */ solarpowerlog-solarpowerlog-0.26/src/Connections/sharedconnection/CSharedConnectionSlave.cpp000066400000000000000000000303321444065341000326660ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2010-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /* * CConnectSlave.cpp * * Created on: Sep 13, 2010 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif #ifdef HAVE_COMMS_SHAREDCONNECTION #include "CSharedConnectionSlave.h" #include "CSharedConnectionMaster.h" #include "configuration/CConfigHelper.h" #include "Inverters/interfaces/InverterBase.h" #include "CSharedConnection.h" #include "configuration/ILogger.h" #include "interfaces/CMutexHelper.h" // Slave Configuration Parameter // useconnection = "name" Name of the inverter having the master connection. CSharedConnectionSlave::CSharedConnectionSlave(const string & configurationname) : IConnect(configurationname) { master = NULL; current_ticket = 0; // 0 == no ticket assigned, slave_registered = false; } CSharedConnectionSlave::~CSharedConnectionSlave() { } void CSharedConnectionSlave::Connect(ICommand *callback) { mutex.lock(); read_buffer.clear(); mutex.unlock(); LOGDEBUG(logger, "CSharedConnectionSlave::Connect: callback:"<Connect(callback, this); } void CSharedConnectionSlave::Disconnect(ICommand *callback) { mutex.lock(); read_buffer.clear(); master->SubscribeSlave(this,false); slave_registered = false; mutex.unlock(); LOGDEBUG(logger, "CSharedConnectionSlave::Disconnect: callback:"<Disconnect(callback, this); } void CSharedConnectionSlave::Send(ICommand *callback) { // LOGDEBUG(logger, __PRETTY_FUNCTION__ << ": work: "<Send(callback, this); } void CSharedConnectionSlave::Receive(ICommand *callback) { // LOGDEBUG(logger, __PRETTY_FUNCTION__ << ": callback: "<Receive(callback, this); return; // Receive within an atomic block. } else { CMutexAutoLock cma(mutex); // check if we have got already some data to return. // (note: to have read_buffer empty we must already in non-atomic mode // and registered already with the master) if (read_buffer.length()) { callback->addData(ICONN_TOKEN_RECEIVE_STRING, read_buffer); callback->addData(ICMD_ERRNO, 0); LOGDEBUG(logger, __PRETTY_FUNCTION__ << ": buffered read available, placing callback: "<ScheduleWork(callback); read_buffer.clear(); return; } // Register as "receiver". if (!slave_registered) { master->SubscribeSlave(this); slave_registered = true; } // Timeout handling is done by the slave. // clone command and redirect to own execute. unsigned long timeout; try { timeout = boost::any_cast( callback->findData(ICONN_TOKEN_TIMEOUT)); } catch (...) { LOGDEBUG(logger, "CSharedConnectionSlave::Receive(): Depreciated: Falling back to default timeout"); timeout = SHARED_CONN_DEFAULTTIMEOUT; } // Set the due-time in the pending reading commands. boost::posix_time::ptime pt( boost::posix_time::microsec_clock::universal_time()); pt += boost::posix_time::milliseconds(timeout); callback->addData(SHARED_CONN_TIMEOUTTIMESTAMP,pt); // Save callback in the pending reads list. pending_reads.push_back(callback); // Handle IO ICommand *cmd; // copy-construct a ICommand for the master comm object and submit work. // note that target and commmand will be rewritten by the master comm // master comm will get ownership of object, it may also delete it! // (note: the icommand has an implicit copy-constructor which is ok) cmd = new ICommand(*callback); // LOGTRACE(logger, __PRETTY_FUNCTION__ << ": submitting work: "<Receive(cmd, this); // Handle Timeout // TODO needs only to be done if the new timeout is shorter than existings. // this will just create a little more overhead. cmd = new ICommand(CMD_HANDLETIMEOUTS, this); timespec ts; ts.tv_sec = timeout / 1000; ts.tv_nsec = (timeout % 1000) * 1000 * 1000UL; // LOGDEBUG(logger, __PRETTY_FUNCTION__ << ": submitting timeout work: "<< cmd); Registry::GetMainScheduler()->ScheduleWork(cmd, ts); } } void CSharedConnectionSlave::Accept(ICommand* callback) { // LOGDEBUG(logger, __PRETTY_FUNCTION__ << ": callback: "<Accept(callback, this); } void CSharedConnectionSlave::Noop(ICommand* cmd) { // LOGDEBUG(logger, "CSharedConnectionSlave::Noop: callback:"<Noop(cmd, this); } bool CSharedConnectionSlave::CheckConfig(void) { CConfigHelper cfg(ConfigurationPath); bool fail = false; std::string s; fail |= !cfg.GetConfig("useconnection", s); fail |= !cfg.CheckConfig("useconnection", libconfig::Setting::TypeString, false); if (fail) return false; // Retrieve the pointer to the CSharedConnectionMaster via the Inverter, // but do checks to ensure that the type is right. IInverterBase *base = Registry::Instance().GetInverter(s); // Check if inverter is known if (!base) { LOGERROR(logger, "useconnection must point to a known Inverter and this " "inverter must be declared first. Inverter not found: " << s;); return false; } // Check if the config of this inverter is a shared comm. CConfigHelper bcfg(base->GetConfigurationPath()); bcfg.GetConfig("comms", s, std::string("")); if (s != "SharedConnection") { LOGERROR(logger, "inverter " << base->GetName() << " does not use a shared connection."); return false; } // Make sure it is a master. bcfg.GetConfig("sharedconnection_type", s); if (s != "master") { LOGERROR(logger, "inverter " << base->GetName() << " does not use a shared master connection"); return false; } // The API of the SharedConnection gives us a IConnect*, promote it to // (CSharedConnectionMaster* (we checked the type above...) master = (CSharedConnectionMaster*)((CSharedConnection*)base->getConnection()) ->GetConcreteSharedConnection(); LOGDEBUG(logger, ConfigurationPath << " slave:" << this << " master " << master); return true; } bool CSharedConnectionSlave::IsConnected(void) { assert(master); return master->IsConnected(); } bool CSharedConnectionSlave::AbortAll() { // We abort only our pending "receives" CMutexAutoLock cma(mutex); for (std::list::iterator it = pending_reads.begin(); it != pending_reads.end(); it++) { (*it)->addData(ICMD_ERRNO, -ECANCELED); Registry::GetMainScheduler()->ScheduleWork(*it); } pending_reads.clear(); return true; } void CSharedConnectionSlave::ExecuteCommand(const ICommand* cmd) { switch (cmd->getCmd()) { case CMD_HANDLETIMEOUTS: { LOGDEBUG(logger, __PRETTY_FUNCTION__ << ": CMD_HANDLETIMEOUTS ICommand " << cmd ); // Check all pending read commands for timeouts. // and remove all expired reads. boost::posix_time::ptime pt( boost::posix_time::microsec_clock::universal_time()); CMutexAutoLock cma(mutex); // check for timeouts in the reading commands list // and if so schedule work with error set to timedout. for (std::list::iterator it = pending_reads.begin(); it != pending_reads.end(); it++) { try { const boost::posix_time::ptime &ppt = boost::any_cast< boost::posix_time::ptime>( cmd->findData(SHARED_CONN_TIMEOUTTIMESTAMP)); if (ppt <= pt) { (*it)->addData(ICMD_ERRNO, -ETIMEDOUT); Registry::GetMainScheduler()->ScheduleWork(*it); it = pending_reads.erase(it); } } catch (...) { // malformed ICommand. LOGDEBUG(logger, __PRETTY_FUNCTION__ << "BUG: Malformated ICommand during CMD_HANDLETIMEOUTS"); it = pending_reads.erase(it); } } } break; case CMD_HANDLEREAD: { LOGDEBUG(logger, __PRETTY_FUNCTION__ << ": CMD_HANDLEREAD ICommand " << cmd ); // will be issued by the master on any reception. // Handling: We will answer all pending reads with this answer... std::string s; CMutexAutoLock cma(mutex); try { s = boost::any_cast( cmd->findData(ICONN_TOKEN_RECEIVE_STRING)); } catch (...) { } if (0 == pending_reads.size()) { read_buffer += s; break; } for (std::list::iterator it = pending_reads.begin(); it != pending_reads.end(); it++) { // merge data and issue work (*it)->mergeData(*cmd); LOGDEBUG(logger, "Scheduling read-work " << *it << " to target " << (*it)->getTrgt()); Registry::GetMainScheduler()->ScheduleWork(*it); } pending_reads.clear(); break; } default: { LOGDEBUG(logger, __PRETTY_FUNCTION__ << ": unknown! ICommand " << cmd ); assert(0); break; } } } bool CSharedConnectionSlave::CanAccept(void) { assert(master); return master->CanAccept(); } bool CSharedConnectionSlave::HandleTickets(ICommand* callback) { // get if this block goes atomic or ends an atomic block. bool is_still_atomic = false; bool is_atomic = false; try { is_still_atomic = boost::any_cast( callback->findData(ICONN_ATOMIC_COMMS)); is_atomic = true; } catch (const std::invalid_argument &e) { // data not contained -- it is a non-atomic block. is_atomic = false; } catch (const boost::bad_any_cast &e) { //"Bad cast -- Programming error." //throw; // abort programm. } if (!is_atomic) { // LOGDEBUG(logger, " Not atomic " << callback); return false; } // New atomic block? if (0 == current_ticket) { current_ticket = master->GetTicket(); //LOGDEBUG(logger, " New ticket "<< current_ticket << " requested for " << callback); } else { if (is_still_atomic) { //LOGDEBUG(logger, " Existing ticket "<< current_ticket << " continued for " << callback); } else { //LOGDEBUG(logger," Existing ticket "<< current_ticket << " ending with " << callback); } } callback->addData(ICONN_SHARED_TICKET, current_ticket); // is this the last astomic request? if (!is_still_atomic) current_ticket = 0; return true; } #endif solarpowerlog-solarpowerlog-0.26/src/Connections/sharedconnection/CSharedConnectionSlave.h000066400000000000000000000063371444065341000323430ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2010-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file CConnectSlave.h * * This is the slave object for the Shared Communication.Please see the * CSharedConnection class for documentation... * * (In few words, this will will proxy requests over to amaster object * which then will do the comms) * * Created on: Sep 13, 2010 * Author: coldtobi */ #ifndef CCONNECTSLAVE_H_ #define CCONNECTSLAVE_H_ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif #ifdef HAVE_COMMS_SHAREDCONNECTION #include "Connections/interfaces/IConnect.h" #include "patterns/ICommandTarget.h" class CSharedConnectionMaster; class CSharedConnectionSlave: public IConnect, protected ICommandTarget { protected: friend class CSharedConnection; friend class CSharedConnectionMaster; CSharedConnectionSlave(const string & configurationname); public: virtual ~CSharedConnectionSlave(); virtual void ExecuteCommand(const ICommand *cmd); protected: void setMaster(CSharedConnectionMaster* master) { LOGDEBUG(logger,"this:" << this << " master:"<master = master; } enum Commands { CMD_HANDLETIMEOUTS = BasicCommands::CMD_USER_MIN, CMD_HANDLEREAD }; protected: virtual void Connect(ICommand *callback); virtual void Disconnect(ICommand *callback); virtual void Send(ICommand *cmd); virtual void Receive(ICommand *callback); virtual void Accept(ICommand *callback); virtual bool CheckConfig(void); virtual bool IsConnected(void); virtual bool AbortAll(); virtual void Noop(ICommand *cmd); virtual bool CanAccept(void); /** Handles the common tasks regarding the ticket system to handler "atomic * blocks" * * It will * - examine if a new ticket is needed * - examine if the ticket is about to be closed * - add the ticket to ICommand for the SharedMaster * * \param callback callback to be fixed * * \returns true if the command requests atomic operation. * */ bool HandleTickets(ICommand *callback); private: CSharedConnectionMaster *master; long current_ticket; bool slave_registered; std::list pending_reads; boost::mutex mutex; /// Buffer for non-atomic reads while no read is pending. /// Will be reset by Connect and Disconnect. std::string read_buffer; }; #endif #endif /* CCONNECTSLAVE_H_ */ solarpowerlog-solarpowerlog-0.26/src/DataFilters/000077500000000000000000000000001444065341000222275ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/DataFilters/CCSVOutputFilter.cpp000066400000000000000000000447521444065341000260740ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2015 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file CCSVOutputFilter.cpp * * Created on: Jun 29, 2009 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif #ifdef HAVE_FILTER_CSVDUMP #include #include #include #include #include #include #include #include #include #include #include "configuration/Registry.h" #include "configuration/CConfigHelper.h" #include "configuration/ConfigCentral/CConfigCentral.h" #include "interfaces/CWorkScheduler.h" #include "Inverters/Capabilites.h" #include "patterns/CValue.h" #include "CCSVOutputFilter.h" #include "Inverters/interfaces/ICapaIterator.h" #define DESCRIPTION_CSVWRITER_INTRO \ "Logger CSVWriter\n" \ "The CSV Data Logger takes some or all data and writes it to a CSV " \ "(comma-separated-values) file as specified in the RFC 4180.\n" \ "The data to be logged can be selected, either by specifying the identifiers " \ "or just logging \"all\" data. " \ "However, \"log all\" will causes that the number of columns can change " \ "during logging, violating the RFC. \n" \ "When some data is unavailable, empty " \ "values will be logged instead. \n" \ "Also, a (ISO 8601)-like timestamp " \ "will be inserted as the first column. \n" \ "To get a CSVWriter, \"type\" below needs to " \ "be set to " \ FILTER_CSVWRITER \ " (as indicated below.)" #define DESCRIPTION_CSVWRITER_FILENAME \ "Defines the target file for this CSV file.\n This setting is dependent on " \ "other parameters:\n" \ "If \"rotate\" is enabled, the logger will start a new logfile at midnight. " \ "To avoid overwriting the old logfile it will add the current date to the " \ "filename, using by default the ISO 8601 format YYYY-MM-DD. " \ "If the parameter contains a \"%s\", the timestamp will be inserted at this " \ "position; if not specified, it will be appended at the end.\n" \ "For example\n" \ "logfile=\"Inverter_1_%s.csv\"\n" \ "will create a logfile like \"Inverter_1_2009-07-04.csv\"\n" \ "To set the format of the timestamp see the option format_timestamp." #define DESCRIPTION_CSVWRITER_ROTATE \ "Rotate: Create a new logfile at midnight." #define DESCRIPTION_CSVWRITER_COMPACTCSV \ "Tries to keep the files compact by suppressing logs when all data is " \ "unchanged.\n" \ "In other words: This option will eliminate lines in the CSV file which are " \ "identical to the previous line, if everything to be logged (except date/time) " \ "has not changed." #define DESCRIPTION_CSVWRITER_FLUSHFILEBUFFER \ "If true, writes to the CSV file are immediate, if false, use the cache provided " \ "by the operating system.\n" \ "If you are \"just logging\" this might be fine to set to false, if you do " \ "some kind of real-time data processing, use false, as it might " \ "take some times for the data to enter the disk. " \ "One use of this option disabled is if you log to flash memory or if " \ "you want to avoid spinning up disks. " \ "Note: Solarpowerlog only hints the operating system to flush the file " \ "buffers. The operating system or hardware (harddisk) still might use some " \ "caching.\n" \ "Note: Up to solarpowerlog 0.21 this setting was by default set to true." #define DESCRIPTION_CSVWRITER_FORMATTIMESTAMP \ "You can customize the timestamp format with this setting. As solarpowerlog is " \ "using boost, please refer to this list for all valid options: " \ "http://www.boost.org/doc/libs/1_57_0/doc/html/date_time/date_time_io.html#date_time.format_flags\n" \ "The default set the date in the ISO 8601 format, e.g.: 2009-12-20 13:34:56." #define DESCRIPTION_CSVWRITER_DATA2LOG \ "This parameter specifies tha data to be logged. There are two modes: " \ "Logging everything by specifing \"all\" here or selective logging by specifing " \ " excplictly the capabilities to be logged in an array." \ "Hint: To retrieve all the capabilites names supported, first use \"all\" and then" \ "examine the created CSV file to select the ones you really want.\n" \ "This setting is mandatory." #define EXAMPLE_CSVWRITER_DATA2LOG \ "data2log= \"all\";\n" \ "data2log= [ \n" \ "\t\"AC grid feeding current (A)\",\n" \ "\t\"AC grid voltage (V)\",\n" \ "\t\"Current Grid Feeding Power\",\n" \ "\t\"DC current in (A)\",\n" \ "\t\"DC voltage in (V)\",\n" \ "\t\"Data Query Interval\",\n" \ "\t\"Data Validity\",\n" \ "\t\"Energy produced cumulated all time (kWh)\",\n" \ "\t\"Energy produced this month (kWh)\",\n" \ "\t\"Energy produced this year (kWh)\",\n" \ "\t\"Energy produced today (kWh)\",\n" \ "\t\"Inverter Overall Status\",\n" \ "\t\"Inverter Power On Hours\",\n" \ "\t\"Inverter Temperature (C)\",\n" \ "\t\"Net frequency (Hz)\"\n" \ "]" using namespace std; using namespace libconfig; using namespace boost::gregorian; CCSVOutputFilter::CCSVOutputFilter( const string & name, const string & configurationpath ) : IDataFilter(name, configurationpath), datavalid(false), capsupdated(false) { headerwritten = false; _cfg_cache_data2log_all = false; _cache_found_all_capas = false; // Schedule the initialization and subscriptions later... ICommand *cmd = new ICommand(CMD_INIT, this); Registry::GetMainScheduler()->ScheduleWork(cmd); // We do not anything on these capabilities, so we remove our list. // any cascaded filter will automatically use the parents one... CCapability *c = IInverterBase::GetConcreteCapability( CAPA_INVERTER_DATASTATE); CapabilityMap.erase(CAPA_INVERTER_DATASTATE); delete c; // Also we wont fiddle with the caps requiring our listeners to unsubscribe. // They also should get that info from our base. c = IInverterBase::GetConcreteCapability(CAPA_CAPAS_REMOVEALL); CapabilityMap.erase(CAPA_CAPAS_REMOVEALL); delete c; // However, to help following plugins, we will publish some data here: // (this enables other plugins to use our files as data source) c = new CCapability(CAPA_CSVDUMPER_FILENAME, new CValue, this); AddCapability(c); // A comma-seperated list of parameters which are currently logged. // note: This list might grow over time, so when parsing the CSV File, // be prepared that there might be not all given from the beginning of the // file c = new CCapability(CAPA_CSVDUMPER_LOGGEDCAPABILITES, new CValue, this); AddCapability(c); Registry::GetMainScheduler()->RegisterBroadcasts(this); } CCSVOutputFilter::~CCSVOutputFilter() { if (file.is_open()) file.close(); } bool CCSVOutputFilter::CheckConfig() { std::auto_ptr cfg(getConfigCentralObject(NULL)); bool fail = !cfg->CheckConfig(logger,configurationpath); // check datasource: The type and existance is already checked, but // we need to see if it really exists -- this is done already in the // baseclass' constructor. if (!fail && !base) { LOGERROR(logger, "Cannot find datassource with the name " << _datasource); fail = true; } // Datatolog -- can be "all" or an array. // ConfigCentral cannot check this complexity... CConfigHelper hlp(configurationpath); bool data2log_fail = false; if (hlp.CheckConfig("data2log", Setting::TypeString, false, false)) { std::string setting; hlp.GetConfig("data2log", setting); if (setting == "all") { _cfg_cache_data2log_all = true; } else { data2log_fail = true; } } else if (!hlp.CheckConfig("data2log", Setting::TypeArray)) { data2log_fail = true; } if (data2log_fail) { LOGERROR(logger, "Configuration Error: data2log must be " "\"all\" or of the type \"Array\"."); fail = true; } return !fail; } void CCSVOutputFilter::Update( const IObserverSubject *subject ) { assert (subject); CCapability *c, *cap = (CCapability *) subject; // Datastate changed. if (cap->getDescription() == CAPA_INVERTER_DATASTATE) { this->datavalid = ((CValue *) cap->getValue())->Get(); return; } // Unsubscribe plea -- we do not offer this Capa, our customers will // ask our base directly. if (cap->getDescription() == CAPA_CAPAS_REMOVEALL) { auto_ptr it(base->GetCapaNewIterator()); while (it->HasNext()) { pair cappair = it->GetNext(); cap = (cappair).second; cap->UnSubscribe(this); } return; } // propagate "caps updated" if (cap->getDescription() == CAPA_CAPAS_UPDATED) { c = IInverterBase::GetConcreteCapability(CAPA_CAPAS_UPDATED); *(CValue *) c->getValue() = *(CValue *) cap->getValue(); c->Notify(); capsupdated = true; return; } } void CCSVOutputFilter::ExecuteCommand( const ICommand *cmd ) { switch (cmd->getCmd()) { case CMD_INIT: { DoINITCmd(cmd); ICommand *ncmd = new ICommand(CMD_CYCLIC, this); struct timespec ts; // Set cyclic timer to the query interval. ts.tv_sec = 5; ts.tv_nsec = 0; CCapability *c = GetConcreteCapability( CAPA_INVERTER_QUERYINTERVAL); if (c && CValue::IsType(c->getValue())) { CValue *v = (CValue *) c->getValue(); ts.tv_sec = v->Get(); ts.tv_nsec = ((v->Get() - ts.tv_sec) * 1e9); } else { LOGINFO(logger, "INFO: The associated inverter does not specify the " "queryinterval. Defaulting to 5 seconds"); } Registry::GetMainScheduler()->ScheduleWork(ncmd, ts); } break; case CMD_CYCLIC: { DoCYCLICmd(cmd); // Set cyclic timer to the query interval. ICommand *ncmd = new ICommand(CMD_CYCLIC, this); struct timespec ts; ts.tv_sec = 5; ts.tv_nsec = 0; CCapability *c = GetConcreteCapability( CAPA_INVERTER_QUERYINTERVAL); if (c && CValue::IsType(c->getValue())) { CValue *v = (CValue *) c->getValue(); ts.tv_sec = v->Get(); ts.tv_nsec = ((v->Get() - ts.tv_sec) * 1e9); } Registry::GetMainScheduler()->ScheduleWork(ncmd, ts); } break; case CMD_ROTATE: DoINITCmd(cmd); break; case CMD_BRC_SHUTDOWN: // shutdown requested, we will terminate soon. // So flush filesystem buffers. if (file.is_open()) { file.flush(); } break; } } void CCSVOutputFilter::DoINITCmd( const ICommand * ) { std::string filename; CCapability *cap; assert(base); cap = base->GetConcreteCapability(CAPA_CAPAS_UPDATED); assert(cap); // this cap is required to have. if (!cap->CheckSubscription(this)) cap->Subscribe(this); cap = base->GetConcreteCapability(CAPA_CAPAS_REMOVEALL); assert(cap); if (!cap->CheckSubscription(this)) cap->Subscribe(this); cap = base->GetConcreteCapability(CAPA_INVERTER_DATASTATE); assert(cap); if (!cap->CheckSubscription(this)) cap->Subscribe(this); // Try to open the file if (file.is_open()) { file.close(); } if (_cfg_cache_rotate) { date today(day_clock::local_day()); //note: the %s will be removed, so +10 is enough. char buf[_cfg_cache_filename.size() + 10]; int year = today.year(); int month = today.month(); int day = today.day(); snprintf(buf, sizeof(buf) - 1, "%s%04d-%02d-%02d%s", _cfg_cache_filename.substr(0, _cfg_cache_filename.find("%s")).c_str(), year, month, day, _cfg_cache_filename.substr(_cfg_cache_filename.find("%s") + 2, string::npos).c_str()); filename = buf; } // Open the file. We use binary mode, as we want end the line ourself (LF+CR) // leaned on RFC4180 file.clear(); // clear errorstates of fstream. file.open(filename.c_str(), fstream::out | fstream::in | fstream::app | fstream::binary); #ifdef HAVE_WIN32_API if (file.fail()) { file.clear(); file.open(tmp.c_str(), fstream::out | fstream::app | fstream::binary); } #endif if (file.fail()) { LOGWARN(logger,"Failed to open file " << filename <<". Logger " << name << " will not work. " ); file.close(); filename = ""; } // Update the filename. If empty, the subsequent plugin knows that there // was a problem. cap = this->GetConcreteCapability(CAPA_CSVDUMPER_FILENAME); ((CValue *) cap->getValue())->Set(filename); cap->Notify(); // a new file needs a new header headerwritten = false; // Technically seen, the file is now empty and the we-are-logging-this // capability CAPA_CSVDUMPER_LOGGEDCAPABILITES is wrong. // But in some seconds, we probably write the same as the last day, // so we set the changes later. // (In other words: I told you, that the file needs not to contain all the // datas we claim to be there here...) // Set a timer to some seconds after midnight, to enforce rotating with correct date boost::posix_time::ptime n = boost::posix_time::second_clock::local_time(); date d = n.date() + days(1); boost::posix_time::ptime tomorrow(d); boost::posix_time::time_duration remaining = tomorrow - n; struct timespec ts; ts.tv_sec = remaining.hours() * 3600UL + remaining.minutes() * 60 + remaining.seconds() + 10; ts.tv_nsec = 0; ICommand *ncmd = new ICommand(CMD_ROTATE, this); Registry::GetMainScheduler()->ScheduleWork(ncmd, ts); } void CCSVOutputFilter::DoCYCLICmd( const ICommand * ) { // bool compact_file, flush_after_write; // std::string format; // CConfigHelper cfg(configurationpath); // cfg.GetConfig("format_timestamp", format, std::string("%Y-%m-%d %T")); // cfg.GetConfig("compact_csv", compact_file, false); // cfg.GetConfig("flush_file_buffer_immediatly", flush_after_write, false); /* Check for data validity. */ if (!datavalid) { return; } /* check if CSV-Header needs to be re-emitted.*/ if (capsupdated || !headerwritten) { capsupdated = false; if (CMDCyclic_CheckCapas()) { headerwritten = false; } } /* check if file is ready */ if (!file.is_open()) { return; } /* output CSV Header*/ if (!headerwritten) { std::stringstream ss_header; last_line.clear(); bool first = true; list::const_iterator it; for (it = CSVCapas.begin(); it != CSVCapas.end(); it++) { if (!first) { ss_header << ","; } else { ss_header << "Timestamp,"; } first = false; ss_header << *(it); } // CSV after RFC 4180 requires CR LF file << ss_header.str() << (char) 0x0d << (char) 0x0a; CCapability *cap = GetConcreteCapability(CAPA_CSVDUMPER_LOGGEDCAPABILITES); assert(cap); ((CValue *)cap->getValue())->Set(ss_header.str()); cap->Notify(); headerwritten = true; } /* finally, output data. */ // make timestamp boost::posix_time::ptime n = boost::posix_time::second_clock::local_time(); // assign facet only to a temporary stringstream. // this avoids having a persistent object. /// time_facet for the formating of the string // note: do not delete the facet. This is done by the locale. // See: http://rhubbarb.wordpress.com/2009/10/17/boost-datetime-locales-and-facets/ // (the locale will delete the object, so there is no leak. If we would // delete, this crashes.) std::stringstream ss; list::const_iterator it; CCapability *c; IValue *v; for (it = CSVCapas.begin(); it != CSVCapas.end(); it++) { ss << ","; c = base->GetConcreteCapability(*it); if (c) { v = c->getValue(); string tmp = (string) *v; if (string::npos != tmp.find('"')) { string t2 = tmp; size_t t; while (string::npos != (t = t2.find('"'))) { tmp = t2.substr(0, t); tmp += '"'; t2 = t2.substr(t, string::npos); } tmp += t2; } if (string::npos != tmp.find(',') || string::npos != tmp.find("\x0d\x0a")) { ss << '"' << tmp << '"'; } else { ss << tmp; } } else { // file << ' '; } } if ( !_cfg_cache_compactcsv || ss.str() != last_line) { last_line = ss.str(); std::stringstream timestamp; boost::posix_time::time_facet *facet = new boost::posix_time::time_facet(_cfg_cache_formattimestap.c_str()); timestamp.imbue(std::locale(ss.getloc(), facet)); timestamp << n; file << timestamp.str() << ss.str() << (char) 0x0d << (char) 0x0a; if (_cfg_cache_flushfb) file << flush; } } bool CCSVOutputFilter::CMDCyclic_CheckCapas( void ) { bool ret = false; if (!_cfg_cache_data2log_all && !_cache_found_all_capas ) { CConfigHelper cfghlp(configurationpath); string tmp; int i = 0; while (cfghlp.GetConfigArray("data2log", i++, tmp)) { if (search_list(tmp)) { continue; } CSVCapas.push_back(tmp); ret = true; } _cache_found_all_capas = true; return ret; } /** check for new capabilites not already in the list. * Add the new ones to the end of the list. */ auto_ptr it(base->GetCapaNewIterator()); pair pair; while (it->HasNext()) { pair = it->GetNext(); if (search_list(pair.first)) { continue; } CSVCapas.push_back(pair.first); ret = true; } return ret; } bool CCSVOutputFilter::search_list( const string id ) const { list::const_iterator it; for (it = CSVCapas.begin(); it != CSVCapas.end(); it++) { if (*it == id) return true; } return false; } CConfigCentral* CCSVOutputFilter::getConfigCentralObject(CConfigCentral *parent) { if (!parent) parent = new CConfigCentral; (*parent) (NULL, DESCRIPTION_CSVWRITER_INTRO); parent = IDataFilter::getConfigCentralObject(parent); (*parent) ("logfile", DESCRIPTION_CSVWRITER_FILENAME, _cfg_cache_filename) ("rotate", DESCRIPTION_CSVWRITER_ROTATE, _cfg_cache_rotate, false) ("compact_csv", DESCRIPTION_CSVWRITER_COMPACTCSV, _cfg_cache_compactcsv, false) ("flush_file_buffer_immediatly", DESCRIPTION_CSVWRITER_FLUSHFILEBUFFER, _cfg_cache_flushfb, false) ("format_timestamp", DESCRIPTION_CSVWRITER_FORMATTIMESTAMP, _cfg_cache_formattimestap, std::string("%Y-%m-%d %T")) ("data2log", DESCRIPTION_CSVWRITER_DATA2LOG, EXAMPLE_CSVWRITER_DATA2LOG) ; parent->SetExample("type", std::string(FILTER_CSVWRITER), false); return parent; } #endif solarpowerlog-solarpowerlog-0.26/src/DataFilters/CCSVOutputFilter.h000066400000000000000000000245061444065341000255340ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2015 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file CCSVOutputFilter.h * \date Jul 1, 2009 * \author Tobias Frost * * \page CVSDataLogger [LOGGER] CVSDataLogger: Logging to CSV Files * * \section DLCSV_Description Overview * The CSV Data Logger takes some or all data and writes it to a regular comma- * sepearated-file. * * The data to be logged can be selected by specifying the identifiers or * all data. * * Solarpowerlog honors the RFC 4180. However, some feature, like the "log all" * feature causes that for example the number of columns changes during runtime. * (However, patches will be accepted to fix this: For example, on a new feature, * one could just reparse the file and add the missing datas. * * If some data is unavailable, it will be logged with an empty value. * * To increase the use of the logfile, a (ISO 8601)-like timestamp * will be inserted as the first column. * * \section DLCSV_Configuration Configuration * * As every logger, the CSV Logger is configured using the Loggers Section. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Option Type Mandatory Default Value Description
name string yes *   Names the Logger. Used to identify the logger.
type string yes *   Selects the LoggerType. To get a CSVLogger, this * must be CSVLogger.
datasource string yes *   Name of the datasource. Must be either a name of a Inverter * (Inverter Section) or a name of another DataFilter/logger.
logfile string yes *   Defines the target file for this CSV file. * See below for additional information.
rotate bool   * false Create a new logfile at midnight. Also see the notes on logfile below. *
compact_csv bool   * false tries to keep the files compact if the data is not changing. * Done by not writing lines with the exact same content. *
flush_file_buffer_immediatly bool   * false if true, do not cache information but immediately write to disk. * If you are "only logging" this might be fine to set to false, if you do * some kind of real-time data processing, make this false, as it might * take some times for the data to enter the disk. * One use of this option disabled is if you log to flash memory or if * you want to avoid spinning up disks. * \note Solarpowerlog only hints the operating system to flush the file * buffers. The OS or hardware (harddisk) still might use some caching. * \note Even if this is setting is false, the operating system will still * take care that the data is written to the disk usually after a few * seconds.) * \note Up to version 0.21 including this setting was default set to true. *
format_timestamp string   * "%Y-%m-%d %T" How should the timestamp be rendered? You can use the options as * described here: * http://www.boost.org/doc/libs/1_37_0/doc/html/date_time/date_time_io.html#date_time.format_flags *
* However, the default set the date in the ISO 8601 format, for example * 2009-12-20 13:34:56. *
data2log string or array   * all If the string reads "all", everything is logged. If an array is * given, the data identified by the array will be logged. * See below for details. *
* * logfile: * With lofile the filename to log to will be specified. * If "rotate" is enabled, this logger will * begin a new logfile at midnight . To avoid overwriting the old logfile it will * add the current date to the filename, using by default an ISO 8601 format: YYYY-MM-DD. * To specify *where* the timestamp should be placed, use "%s". * If %s is not given, it will be appended *at the end* of the filename. * For example * \code * logfile="Inverter_1_%s.csv" * \endcode * will create a logfile like "Inverter_1_2009-07-04.csv" * * To set the format of the timestamp see the option format_timestamp. * * data2log: * * To specify which data should be logged, their identifiers have to be listed in a * array. * * The identifiers supported can be retrieved by either the inverter's * documentation, in the documentation of the intermediate datafilters. * * One can also use the DumbDumper logger or run the CVS Logger in the "all" mode * to obtain a list. * * \note The identifiers are case sensitive! * * Array-Example: * \code * data2log = [ * "Current Grid Feeding Power", * "Energy produced today (kWh)", * "Energy produced this month (kWh)", * "DC voltage in (V)" * ]; * \endcode * * If you want to use the "log all" feature, just say "all" or do not specify the data to log * (as "all" is the default) * * \note When selecting "all features" and new features are detected at runtime, * the CSV-header will be written again with the new data added to a new column. * If you want to avoid having ever-changing tables, please configure * all the data you want to see in the log file. Data which is not present all * the time will then still gets its placeholder in the output file. */ #ifndef CCSVOUTPUTFILTER_H_ #define CCSVOUTPUTFILTER_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_FILTER_CSVDUMP #include #include #include "boost/date_time/local_time/local_time.hpp" #include "DataFilters/interfaces/IDataFilter.h" #include "Inverters/BasicCommands.h" /** This class implements a logger to write the data to a CSV File * * Please see \ref DLCSV_Description for configuration, etc. */ class CCSVOutputFilter : public IDataFilter { protected: friend class IDataFilterFactory; CCSVOutputFilter( const string &name, const string & configurationpath ); public: virtual ~CCSVOutputFilter(); virtual bool CheckConfig(); virtual void Update( const IObserverSubject *subject ); virtual void ExecuteCommand( const ICommand *cmd ); virtual CConfigCentral* getConfigCentralObject(CConfigCentral *parent); private: fstream file; /** Do the initialization of the module * * - subscribe to basic Capabilities * - open logfile (and create filename) * - schedule rotation of the filename at 00:00:05 * - schedule cyclic working * * Also will do the rotating of the logfile*/ void DoINITCmd (const ICommand *); /** does the actual work: * * - maintain the list of columns to be written * (the Capa map is sorted, but we need to make sure that * we won't switch columns) * - write the heade if necessary * - check for data validty. * */ void DoCYCLICmd(const ICommand *); enum Commands { CMD_BRC_SHUTDOWN = BasicCommands::CMD_BRC_SHUTDOWN, CMD_INIT = BasicCommands::CMD_USER_MIN, CMD_CYCLIC, CMD_ROTATE /// CSVCapas; // Helpers to shrink some functions... /** Check if any capas are now available which were not before * (but should be tracked) * Read from configuration which capabilities to be logged and * and assemble the std::list containing everything we want to log. * In case of dynamic logging ("the all feature") check for new features * and append them to the list to be logged (at the end of the list) * * The function will be called whenever the Capa-Updated event * is set via this->Update() * * \return true, if a new Capability was detected and a new CSV Header * should be generated. Otherwise false. * */ bool CMDCyclic_CheckCapas(void); /** search the CSVCapas for a named capa * * \returns true, if in list, else false. */ bool search_list(const string id) const; /// cache: last emitted string without timestamp std::string last_line; /** configuration cache: filename of the CVS log */ std::string _cfg_cache_filename; /** configuration cache: how to format the timestamp */ std::string _cfg_cache_formattimestap; /** configuration cache rotate the logfile at midnight */ bool _cfg_cache_rotate; /** configuration cache: should repeated lines be suppressed? */ bool _cfg_cache_compactcsv; /** configuration cache: should we "flush" after every write * TODO: Maybe change this parameter / add another to specify file buffer * length via std::streambuf::pubsetbuf */ bool _cfg_cache_flushfb; /** configuration cache: is data2log="all"? */ bool _cfg_cache_data2log_all; /** cache, if we created the CapasList already.*/ bool _cache_found_all_capas; }; #endif #endif /* CCSVOUTPUTFILTER_H_ */ solarpowerlog-solarpowerlog-0.26/src/DataFilters/CDumpOutputFilter.cpp000066400000000000000000000177261444065341000263470ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2014 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file CDumpOutputFilter.cpp * * Created on: Jun 1, 2009 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_FILTER_DUMBDUMP #include #include "CDumpOutputFilter.h" #include "DataFilters/interfaces/IDataFilter.h" #include "configuration/Registry.h" #include "configuration/CConfigHelper.h" #include "Inverters/Capabilites.h" #include "patterns/ICommand.h" #include "configuration/Registry.h" #include "interfaces/CWorkScheduler.h" #include "Inverters/interfaces/ICapaIterator.h" #include "patterns/CValue.h" #include using namespace libconfig; CDumpOutputFilter::CDumpOutputFilter( const string &name, const string & configurationpath ) : IDataFilter(name, configurationpath), AddedCaps(0), clearscreen(false) { // Schedule the initialization and subscriptions later... ICommand *cmd = new ICommand(CMD_INIT, this); Registry::GetMainScheduler()->ScheduleWork(cmd); CCapability *c = IInverterBase::GetConcreteCapability( CAPA_INVERTER_DATASTATE); CapabilityMap.erase(CAPA_INVERTER_DATASTATE); delete c; } CDumpOutputFilter::~CDumpOutputFilter() { #if 0 // base may be already destructed! if (base) { auto_ptr it(base->GetCapaNewIterator()); pair pair; while (it->HasNext()) { pair = it->GetNext(); pair.second->UnSubscribe(this); } } #endif } bool CDumpOutputFilter::CheckConfig() { string setting; string str; bool fail = false; CConfigHelper hlp(configurationpath); if (!base) { std::string str; fail |= !hlp.CheckAndGetConfig("datasource", Setting::TypeString, str); if (fail) { LOGERROR(logger, "datassource not found."); } else { LOGERROR(logger, "Cannot find datassource with the name " << str); } fail = true; } fail |= !hlp.CheckConfig("clearscreen", Setting::TypeBoolean, true); hlp.GetConfig("datasource", str, (std::string) ""); IInverterBase *i = Registry::Instance().GetInverter(str); if (!i) { LOGERROR(logger, "Setting in " << configurationpath << "." << name << ": Cannot find datasource with the name " << str ); fail = true; } return !fail; } void CDumpOutputFilter::Update( const IObserverSubject *subject ) { assert (subject); // note: the subject must be a CCapability here. // to avoid neverending casting we do that once. CCapability *parentcap = (CCapability *) subject; CCapability *ourcap; // check for the mandatory Capas now, as they might require // immediate actions. if (parentcap->getDescription() == CAPA_CAPAS_REMOVEALL) { // forward the notification. // but -- to be nice -- update the value first ourcap = IInverterBase::GetConcreteCapability(CAPA_CAPAS_REMOVEALL); assert (ourcap); assert (CValue::IsType(ourcap->getValue())); assert (CValue::IsType(parentcap->getValue())); CValue *a, *b; a = (CValue *) (ourcap->getValue()); b = (CValue *) (parentcap->getValue()); *a = *b; ourcap->Notify(); CheckOrUnSubscribe(false); return; } if (parentcap->getDescription() == CAPA_CAPAS_UPDATED) { // this one will be dereferred, but only if we do // not have one pending. if (AddedCaps) return; AddedCaps = true; ICommand *cmd = new ICommand(CMD_ADDED_CAPAS, this); Registry::GetMainScheduler()->ScheduleWork(cmd); return; } // All others does not need to handled here, its ok in the cyclic. } void CDumpOutputFilter::ExecuteCommand( const ICommand *cmd ) { switch (cmd->getCmd()) { case CMD_INIT: { string tmp; CConfigHelper cfghlp(configurationpath); cfghlp.GetConfig("clearscreen", this->clearscreen); assert(base); CCapability *cap = base->GetConcreteCapability( CAPA_CAPAS_UPDATED); assert(cap); // this is required to have.... cap->Subscribe(this); cap = base->GetConcreteCapability( CAPA_CAPAS_REMOVEALL); assert(cap); cap->Subscribe(this); cap = base->GetConcreteCapability( CAPA_INVERTER_DATASTATE); assert(cap); cap->Subscribe(this); CheckOrUnSubscribe(true); // falling through } case CMD_CYCLIC: { ICommand *cmd = new ICommand(CMD_CYCLIC, this); timespec ts = { 5, 0 }; CCapability *c = GetConcreteCapability( CAPA_INVERTER_QUERYINTERVAL); if (c && CValue::IsType(c->getValue())) { CValue *v = (CValue *) c->getValue(); ts.tv_sec = v->Get(); ts.tv_nsec = ((v->Get() - ts.tv_sec) * 1e9); } Registry::GetMainScheduler()->ScheduleWork(cmd, ts); DoCyclicWork(); break; } case CMD_UNSUBSCRIBE: CheckOrUnSubscribe(false); break; case CMD_ADDED_CAPAS: cout << name << ":" << configurationpath << " New Capability(ies) reported" << endl; AddedCaps = false; CheckOrUnSubscribe(true); GetConcreteCapability(CAPA_CAPAS_UPDATED)->Notify(); break; } } // On serveral updates, we have to go through all subscriptions // and check, if we have to subscribe to any of them // The parameter sets, if we subscribe or unsubscribe. void CDumpOutputFilter::CheckOrUnSubscribe( bool subscribe ) { assert(base); // mmh, i think they are unused... this filter iterates and needs not to // subscribe. CCapability *cap = base->GetConcreteCapability( CAPA_INVERTER_DATASTATE); if (cap) cap->SetSubscription(this, subscribe); #if 0 CCapability *cap = base->GetConcreteCapability( CAPA_INVERTER_MANUFACTOR_NAME); if (cap) cap->SetSubscription(this, subscribe); cap = base->GetConcreteCapability(CAPA_INVERTER_MODEL); if (cap) cap->SetSubscription(this, subscribe); cap = base->GetConcreteCapability(CAPA_INVERTER_ACPOWER_TOTAL); if (cap) cap->SetSubscription(this, subscribe); cap = base->GetConcreteCapability(CAPA_INVERTER_KWH_Y2D); if (cap) cap->SetSubscription(this, subscribe); cap = base->GetConcreteCapability(CAPA_INVERTER_KWH_M2D); if (cap) cap->SetSubscription(this, subscribe); #endif } /// This simple Data-Dumper will just dump all info over all capas it has. /// It shows also how a filter can use the iterators to get all the capps of /// the parent filters. void CDumpOutputFilter::DoCyclicWork( void ) { #if 0 // shows how to browser through our caps. cout << configurationpath << "." << name << " Own Capabilities:" << endl << endl; map::iterator it = GetCapabilityIterator(); while (it != GetCapabilityLastIterator()) { cout << (*it).first << ' ' << flush; for (int i = (*it).first.length() + 1; i < 60; i++) cout << '.'; cout << DumpValue((*it).second->getValue()) << endl; it++; } #endif if (clearscreen) { cout << "\033[2J" << "\033[1;1H"; } cout << endl << configurationpath << "." << name << " Known Capabilities:" << endl << endl; auto_ptr cit(GetCapaNewIterator()); while (cit->HasNext()) { pair cappair = cit->GetNext(); cout << (cappair).first << ' ' << flush; for (int i = (cappair).first.length() + 1; i < 60; i++) cout << '.'; cout << " " << (std::string) *(cappair.second->getValue()) << " (Capa of: " << cappair.second->getSource()->GetName() << ")" << endl; } cout << endl; } #endif solarpowerlog-solarpowerlog-0.26/src/DataFilters/CDumpOutputFilter.h000066400000000000000000000104671444065341000260070ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2015 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file CDumpOutputFilter.h * \date Jun 1, 2009 * \author Tobias Frost * */ /** * \page CDumpOutputFilter [LOGGER] DumpDumper: A Simple Data Logger * * This Logger writes the data to standard oputput. * * This is filter can be seen as a reference implementation to see how things * work. * * \section DLDUMP_Description Overview * * This Logger simply queries all Capbilities available and dumps their value * periodically to cout. * * The filter automatically determines how often the values updates (via the * Inverters period) and adapts to this value. * If this value is not available, it defaults to 5 seconds. * * \section DLDUMP_Configuration Configuration * * As every logger, this Logger is configured using the Loggers Section. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Option Type Mandatory Default Value Description
name string x * - Names the Logger.
type string "DumbDumper" * -/td> * Selects the LoggerType. To get a DumpOutput Logger, this * must be DumbDumper .
datasource string x * - Name of the datasource. Must be eighter a name of a Inverter * (Inverter Section) * or a name of another DataFilter.
clearscreen boolean "CSVLogger" * false Specify, if the logger should clean the terminal before starting * to write.
* * \subsection DLDUMPCONF_example Configuration Example * * \code * loggers = ( { # This dumper is known as (required) name = "Simple Dumper 1"; # It is of type type = "DumbDumper"; # And gets its data from datasource = "Inverter_1"; # Yes, it should clean the screen before dumping # (optional. Defaults to false (off) # use true to enable it. clearscreen = true; }; * * * \endcode * */ #ifndef CDUMPOUTPUTFILTER_H_ #define CDUMPOUTPUTFILTER_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_FILTER_DUMBDUMP /** \fixme COMMENT ME * * * TODO DOCUMENT ME! */ #include "DataFilters/interfaces/IDataFilter.h" #include "Inverters/interfaces/CNestedCapaIterator.h" #include "Inverters/BasicCommands.h" class CDumpOutputFilter : public IDataFilter { protected: friend class IDataFilterFactory; CDumpOutputFilter(const string &name, const string & configurationpath); public: virtual ~CDumpOutputFilter(); virtual bool CheckConfig(); virtual void Update(const IObserverSubject *subject); /** This DataFilter uses the CWorkScheduler, so it needs to implement * this function. \sa ICommandTarget::ExecuteCommand */ virtual void ExecuteCommand(const ICommand *cmd); #warning implement me! virtual CConfigCentral* getConfigCentralObject(void) { return NULL; } private: void CheckOrUnSubscribe(bool subscribe = true); void DoCyclicWork(void); enum Commands { CMD_INIT = BasicCommands::CMD_USER_MIN, CMD_CYCLIC, CMD_UNSUBSCRIBE, CMD_ADDED_CAPAS }; bool AddedCaps; bool clearscreen; }; #endif #endif /* CDUMPOUTPUTFILTER_H_ */ solarpowerlog-solarpowerlog-0.26/src/DataFilters/DBWriter/000077500000000000000000000000001444065341000237115ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/DataFilters/DBWriter/CDBWHSpecialTokens.cpp000066400000000000000000000047431444065341000277410ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2014 Tobias Frost 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 . ---------------------------------------------------------------------------- */ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif #ifdef HAVE_FILTER_DBWRITER #include "CDBWHSpecialTokens.h" bool operator==(std::tm t1, std::tm t2) { if (t1.tm_sec != t2.tm_sec) return false; if (t1.tm_min != t2.tm_min) return false; if (t1.tm_hour != t2.tm_hour) return false; if (t1.tm_mday != t2.tm_mday) return false; if (t1.tm_mon != t2.tm_mon) return false; if (t1.tm_year != t2.tm_year) return false; return true; } bool operator!=(std::tm t1, std::tm t2) { if ((t1.tm_sec == t2.tm_sec) && (t1.tm_min == t2.tm_min) && (t1.tm_hour == t2.tm_hour) && (t1.tm_mday == t2.tm_mday) && (t1.tm_mon == t2.tm_mon) && (t1.tm_year == t2.tm_year)) return false; return true; } bool CDBHST_Timestamp::Update(const std::tm &tm) { bool ret; ret = (Get() != tm); Set(tm); return ret; } bool CDBHST_Year::Update(const std::tm &tm) { bool ret; long v; v = tm.tm_year; ret = (Get() != v); Set(v); return ret; } bool CDBHST_Month::Update(const std::tm &tm) { bool ret; long v; v = tm.tm_mon; ret = (Get() != v); Set(v); return ret; } bool CDBHST_Day::Update(const std::tm &tm) { bool ret; long v; v = tm.tm_mday; ret = (Get() != v); Set(v); return ret; } bool CDBHST_Hour::Update(const std::tm &tm) { bool ret; long v; v = tm.tm_hour; ret = (Get() != v); Set(v); return ret; } bool CDBHST_Minute::Update(const std::tm &tm) { bool ret; long v; v = tm.tm_min; ret = (Get() != v); Set(v); return ret; } #endif solarpowerlog-solarpowerlog-0.26/src/DataFilters/DBWriter/CDBWHSpecialTokens.h000066400000000000000000000135701444065341000274040ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2014 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /* * \file CDBWHSpecialTokens.h * * Created on: 06.07.2014 * Author: tobi * * This classes handles the "special database tokens", starting with "%" * * The classes itself using the IValue interface. */ #ifndef CDBWHSPECIALTOKENS_H_ #define CDBWHSPECIALTOKENS_H_ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif #ifdef HAVE_FILTER_DBWRITER #include #include #include "patterns/CValue.h" bool operator==(struct tm t1, struct tm t2); bool operator!=(struct tm t1, struct tm t2); // Template specialisation required for tm template<> class CValue : public IValue { public: CValue() : IValue(MagicNumbers::magic_number_for()) { memset(&value,0,sizeof(std::tm)); } CValue(const struct tm &set) : IValue(MagicNumbers::magic_number_for()) { value = set; timestamp = boost::posix_time::second_clock::local_time(); } /// Serves as a virtual copy constructor. virtual CValue* clone() { return new CValue(*this); } virtual IValue& operator=(const IValue &v) { if (&v == this) return *this; if (this->IsType(&v)) { CValue *rv = (CValue*)&v; this->value = rv->value; this->timestamp = rv->timestamp; return *this; } throw std::bad_cast(); return *this; } void Set(std::tm value, boost::posix_time::ptime timestamp = boost::posix_time::second_clock::local_time()) { this->timestamp = timestamp; this->value = value; SetValid(); } struct tm Get(void) const { return value; } virtual void operator=(const std::tm& val) { timestamp = boost::posix_time::second_clock::local_time(); value = val; } virtual void operator=(const CValue &val) { timestamp = val.GetTimestamp(); value = val.Get(); } virtual boost::posix_time::ptime GetTimestamp(void) const { return timestamp; } virtual operator std::string() { char tmp[128]; std::string ret; std::strftime(tmp,sizeof(tmp),"%c %Z",&value); ret = tmp; return ret; } virtual bool operator==(IValue &v) { if (GetInternalType() == v.GetInternalType()) { CValue &realv = (CValue&)v; return (realv.Get() == Get()); } return false; } virtual bool operator!=(IValue &v) { if (GetInternalType() == v.GetInternalType()) { CValue &realv = (CValue&)v; return (realv.Get() != Get()); } return false; } /** Static interface function to determine at runtime the type of the CValue * object. * Usage example: * CValue cv_int; * IValue *iv1 = &cv_int; * cout << CValue::IsType(iv1);*/ static bool IsType(const IValue *totest) { if (MagicNumbers::magic_number_for() == totest->GetInternalType()) { return true; } return false; } private: std::tm value; boost::posix_time::ptime timestamp; }; #if 1 class IDBHSpecialToken { public: virtual ~IDBHSpecialToken() {}; /// Update the underlaying CValue /// \return true if value indeed chagned, false if unchanged. virtual bool Update(const struct tm &) = 0; virtual const std::string GetString() = 0; }; #endif template class CDBHSpecialToken : public IDBHSpecialToken, public CValue { public: virtual ~CDBHSpecialToken() {}; //virtual bool Update(const struct tm &) =0; virtual const std::string GetString() { return CValue::operator std::string(); } }; class CDBHST_Timestamp : public CDBHSpecialToken { public: virtual bool Update(const struct tm &tm); }; class CDBHST_Year : public CDBHSpecialToken { public: virtual bool Update(const struct tm &tm); }; class CDBHST_Month : public CDBHSpecialToken { public: virtual bool Update(const struct tm &tm); }; class CDBHST_Day : public CDBHSpecialToken { public: virtual bool Update(const struct tm &tm); }; class CDBHST_Hour : public CDBHSpecialToken { public: virtual bool Update(const struct tm &tm); }; class CDBHST_Minute : public CDBHSpecialToken { public: virtual bool Update(const struct tm &tm); }; class CDBHSpecialTokenFactory { public: static IDBHSpecialToken *Factory(const std::string &id) { if (id == "TIMESTAMP") return new CDBHST_Timestamp; if (id == "YEAR") return new CDBHST_Year; if (id == "MONTH") return new CDBHST_Month; // FIXME // if (id == "%WEEK") return new CDBHST_Week; if (id == "DAY") return new CDBHST_Day; if (id == "HOUR") return new CDBHST_Hour; if (id == "MINUTE") return new CDBHST_Minute; return NULL; } }; #endif #endif /* CDBWHSPECIALTOKENS_H_ */ solarpowerlog-solarpowerlog-0.26/src/DataFilters/DBWriter/CDBWriterFilter.cpp000066400000000000000000000577551444065341000273730ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2014 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file CDBWriterFilter.cpp * * Created on: Jun 28, 2014 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif #ifdef HAVE_FILTER_DBWRITER #include "DataFilters/DBWriter/CDBWriterFilter.h" #include "configuration/CConfigHelper.h" #include "interfaces/CWorkScheduler.h" #include "Inverters/Capabilites.h" #include "patterns/CValue.h" #include #include "configuration/ConfigCentral/CConfigCentral.h" static const char *Description_DBWriter_Intro = "This logger allows logging to a SQL database. For the moment, please see the " "examples for configuration hints, as this documentation is incomplete.\n" "\n" "As this logger uses the cppdb database abstraction library, it can be " "configured with many SQL systems. Please note that the required parameters " "are much depending on the exact SQL system to be used, see the configuration " "options for default.\n" "Note that an empty configuration entry usually means that the libraries " "default is used." ; static const char *Description_DBWriter_db_type = "Selects the database type/backend to be used.\n" "Solarpowerlog uses libcppdb as database abstraction library, therefore all " "SQL engines supported by this library should work" "This parameter selects which backend should be configured, as the parameters " " are dependent on this selection.\n" "You can see information about " "the library and its supported backends here: " "http://cppcms.com/sql/cppdb/backendref.html\n" "The following db_types are currently understood:\n" "mysql, postgresql, sqlite3, odbc.\n" "Another type, \"custom\" can be used for advanced use. In this case " "db_cppdb_options will be soley passed to the library as connection string."; static const char *Description_DBWriter_db_host = "The hostname of the database server.\n" "Supported by the mysql and postgresql backends. " "For MySQL, do not specify both and db_hostname db_unixsocket." ; static const char *Description_DBWriter_db_port = "The port to connect to the database server.\n" "Supported by the mysql and postgresql backends. " "If not set or set to an empty value, the default value for the backend is used." ; static const char *Description_DBWriter_db_unixsocket = "For MySQL, this specifies the path to the unix socket to be used for the " "connection.\n" "Supported by the mysql backend. " "Do not specify both db_hostname and db_unixsocket" ; static const char *Description_DBWriter_db_mode = "For SQLite3 databases, this parameter set the mode in which the database should " "be openend. Sensible values are \"create\" and \"readwrite\".\n" "The difference between \"readwrite\" and \"create\" " "that if the database does not exist the connection fails.\n" "Supported for SQLite and " "defaults for SQLite to create."; static const char *Description_DBWriter_db_user = "The username to be used to log on the database server.\n " "Supported by the mysql and postgresql backends, " "also mandatory for those.\n"; static const char *Description_DBWriter_db_passwd = "The password needed to connect to the server.\n" "Supported by the mysql and postgresql backends, " "also mandatory for those.\n"; static const char *Description_DBWriter_db_database = "The database to be used.\n" "Supported by the sqlite3, mysql and postgresql backends, " "and mandatory for those.\n" "For SQLite3 this specify the path and filename of the database file.\n"; static const char *Description_DBWriter_db_cppdb_options = "Option string which will be appended to the connection string.\n" "See your backend config http://cppcms.com/sql/cppdb/backendref.html " "and http://cppcms.com/sql/cppdb/connstr.html for cppdb options.\n" "Only use if you know what you are doing. There'll be dragons.\n" "Mandatory for the ODBC backend, as this is the only way to specify " "its connections settings." "If you use the custom mode, you also need to specify the driver name" "as documented in the cppdb documentation.\n"; using namespace libconfig; // small config checker helper static void _missing_req_parameter(ILogger &logger, std::string type, std::string parameter) { LOGERROR(logger, "Database backend " << type << " requires parameter " << parameter); } static void _wrong_parameter(ILogger &logger, std::string type, std::string parameter) { LOGERROR(logger, "Database backend " << type << " does not support parameter " << parameter); } CDBWriterFilter::CDBWriterFilter( const std::string & name, const std::string & configurationpath ) : IDataFilter(name, configurationpath) { _datavalid = false; // Schedule the initialization and subscriptions later... ICommand *cmd = new ICommand(CMD_INIT, this); Registry::GetMainScheduler()->ScheduleWork(cmd); // We do not anything on these capabilities, so we remove our list. // any cascaded filter will automatically use the parents one... CCapability *c = IInverterBase::GetConcreteCapability( CAPA_INVERTER_DATASTATE); CapabilityMap.erase(CAPA_INVERTER_DATASTATE); delete c; // Also we wont fiddle with the caps requiring our listeners to unsubscribe. // They also should get that info from our base. c = IInverterBase::GetConcreteCapability(CAPA_CAPAS_REMOVEALL); CapabilityMap.erase(CAPA_CAPAS_REMOVEALL); delete c; // Also we do not update capas on our own.. c = IInverterBase::GetConcreteCapability(CAPA_CAPAS_UPDATED); CapabilityMap.erase(CAPA_CAPAS_UPDATED); delete c; Registry::GetMainScheduler()->RegisterBroadcasts(this); } CDBWriterFilter::~CDBWriterFilter() { std::vector::iterator it; for (it = _dbwriterhelpers.begin(); it != _dbwriterhelpers.end(); it++) { delete *it; } } bool CDBWriterFilter::CheckConfig() { auto_ptr cc(getConfigCentralObject(NULL)); bool fail = !cc->CheckConfig(logger,configurationpath); // checking logical combinations for each backend. // mysql: required: user, password, database, host or socket, not port and socket if (_cfg_cache_db_type == "mysql") { if (_cfg_cache_db_user.empty()) { _missing_req_parameter(logger, _cfg_cache_db_type, "db_user"); fail = true; } if (_cfg_cache_db_passwd.empty()) { _missing_req_parameter(logger, _cfg_cache_db_type, "db_password"); fail = true; } if (_cfg_cache_db_database.empty()) { _missing_req_parameter(logger, _cfg_cache_db_type, "db_database"); fail = true; } if (!_cfg_cache_db_mode.empty()) { _wrong_parameter(logger, _cfg_cache_db_type, "db_mode"); fail = true; } if (_cfg_cache_db_host.empty() && _cfg_cache_db_unixsocket.empty()) { LOGERROR(logger, "Either db_host or db_unixsocket must be specified for MySQL"); fail = true; } if (!_cfg_cache_db_port.empty() && ! _cfg_cache_db_unixsocket.empty()) { fail = true; LOGERROR(logger, "both db_port and db_unixsocket cannot be used at the same time for MySQL."); } } else if (_cfg_cache_db_type == "postgresql") { if (_cfg_cache_db_host.empty()) { _missing_req_parameter(logger, _cfg_cache_db_type, "db_host"); fail = true; } if (_cfg_cache_db_user.empty()) { _missing_req_parameter(logger, _cfg_cache_db_type, "db_user"); fail = true; } if (_cfg_cache_db_passwd.empty()) { _missing_req_parameter(logger, _cfg_cache_db_type, "db_password"); fail = true; } if (_cfg_cache_db_database.empty()) { _missing_req_parameter(logger, _cfg_cache_db_type, "db_database"); fail = true; } if (!_cfg_cache_db_mode.empty()) { _wrong_parameter(logger, _cfg_cache_db_type, "db_mode"); fail = true; } if (!_cfg_cache_db_unixsocket.empty()) { _wrong_parameter(logger, _cfg_cache_db_type, "db_unixsocket"); fail = true; } } else if (_cfg_cache_db_type == "sqlite3") { // mode is optional, defaults to create when not given // database is required // all other not supported. if (!_cfg_cache_db_host.empty()) { _wrong_parameter(logger, _cfg_cache_db_type, "db_host"); fail = true; } if (!_cfg_cache_db_port.empty()) { _wrong_parameter(logger, _cfg_cache_db_type, "db_port"); fail = true; } if (!_cfg_cache_db_user.empty()) { _wrong_parameter(logger, _cfg_cache_db_type, "db_user"); fail = true; } if (!_cfg_cache_db_passwd.empty()) { _wrong_parameter(logger, _cfg_cache_db_type, "db_password"); fail = true; } if (_cfg_cache_db_database.empty()) { _missing_req_parameter(logger, _cfg_cache_db_type, "db_database"); fail = true; } if (!_cfg_cache_db_unixsocket.empty()) { _wrong_parameter(logger, _cfg_cache_db_type, "db_unixsocket"); fail = true; } } else if (_cfg_cache_db_type == "_cfg_cache_odbc" || _cfg_cache_db_type == "custom") { if (!_cfg_cache_db_host.empty() || !_cfg_cache_db_user.empty() || !_cfg_cache_db_database.empty() || !_cfg_cache_db_mode.empty() || !_cfg_cache_db_port.empty() || !_cfg_cache_db_unixsocket.empty() || !_cfg_cache_db_passwd.empty() || _cfg_cache_db_cppdb_options.empty()) { LOGERROR(logger, _cfg_cache_db_type << " please use db_cppdb_options to specify the connection string."); fail = true; } } else { LOGERROR(logger, "unknown database backend type " << _cfg_cache_db_type); fail = true; } if (!base) { LOGERROR(logger, "Cannot find datassource with the name " << _datasource); fail = true; } if (fail) return false; bool add_semicolon = false; std::string dbgstring; if (_cfg_cache_db_type != "custom") { _connectionstring = _cfg_cache_db_type + ":"; }; // check: mysql, postgresql, (sqlite3) ok if (!_cfg_cache_db_host.empty()) { _connectionstring += "host=\'" + _cfg_cache_db_host + "\'"; add_semicolon = true; } // check: mysql, postgresql, (sqlite3) ok if (!_cfg_cache_db_user.empty()) { if (add_semicolon) _connectionstring += ";"; _connectionstring += "user=\'" + _cfg_cache_db_user + "\'"; add_semicolon = true; } // check: mysql, postgresql, (sqlite3) ok if (!_cfg_cache_db_database.empty()) { std::string db; if (_cfg_cache_db_type == "sqlite3") db = "db="; if (_cfg_cache_db_type == "mysql") db = "database="; if (_cfg_cache_db_type == "postgresql") db = "dbname="; if (add_semicolon) _connectionstring += ";"; _connectionstring += db + _cfg_cache_db_database + "\'"; add_semicolon = true; } // check: mysql, postgresql, (sqlite3) ok if (!_cfg_cache_db_port.empty()) { if (add_semicolon) _connectionstring += ";"; _connectionstring += "port=\'" + _cfg_cache_db_port + "\'"; add_semicolon = true; } // check: mysql, (postgresql), (sqlite3) ok if (!_cfg_cache_db_unixsocket.empty()) { if (add_semicolon) _connectionstring += ";"; _connectionstring += "unix_socket=\'" + _cfg_cache_db_unixsocket + "\'"; add_semicolon = true; } dbgstring = _connectionstring; // check: mysql, (postgresql), (sqlite3) ok if (!_cfg_cache_db_passwd.empty()) { if (add_semicolon) _connectionstring += ";"; dbgstring += "password=\'***\'"; _connectionstring += "password=\'" + _cfg_cache_db_passwd + "\'"; add_semicolon = true; } // check: (mysql), (postgresql), sqlite3 ok if (!_cfg_cache_db_mode.empty()) { if (add_semicolon) _connectionstring += ";"; _connectionstring += "mode=\'" + _cfg_cache_db_mode + "\'"; add_semicolon = true; } // check: mysql, (postgresql), (sqlite3) ok if (!_cfg_cache_db_cppdb_options.empty()) { std::string tmp; if (add_semicolon) tmp = ";"; tmp += _cfg_cache_db_cppdb_options; _connectionstring += tmp; dbgstring += tmp; add_semicolon = true; } LOGTRACE(logger, "DBWriter: connectionstring for CppDB is: " << dbgstring); CConfigHelper hlp(configurationpath); // Checking config for the tables aggregate int i=0; CDBWriterHelper *dbwh = NULL; do { std::string table, mode, createmode; bool logchangedonly; float logevery; logchangedonly = false; logevery = 0; dbwh = NULL; hlp = CConfigHelper(configurationpath, "db_jobs", i); if (!hlp.isExisting()) { if (i == 0) { LOGFATAL(logger, "db_jobs missing in configuration."); } break; } if (!hlp.CheckAndGetConfig("db_table", Setting::TypeString, table)) { LOGFATAL(logger, "db_table missing in entry number " << i << " of the tables list."); fail = true; i++; continue; } LOGINFO(logger, "Parsing configuration for table " << table); if (!hlp.CheckAndGetConfig("db_create_table", Setting::TypeString, createmode, true)) { LOGFATAL(logger, "db_create_table is of wrong type for table " << table); fail = true; } else if (!createmode.empty()) { if (createmode == "YES") { LOGWARN(logger, "db_create_table is YES -- will create this table later: " << table); } else if (createmode == "YES-WIPE-MY-DATA") { LOGWARN(logger, "db_create_table is YES-WIPE-MY-DATA -- will *DESTROY* and create this table later: " << table); } else if (createmode == "print-sql-statement") { LOGWARN(logger, "Will print creation statement for " << table); } else if (createmode == "no") { createmode = ""; } else { LOGWARN(logger, "db_create_table is neither YES, YES-WIPE-MY-DATA, print-sql-statement or no. Ignored"); createmode = ""; } } if (!hlp.GetConfig("db_operation_mode", mode)) { fail = true; } else { if (!(mode == "continuous" || mode == "single" || mode == "cumulative")) { LOGFATAL(logger, "Setting operation_mode for table " << table << " must be either continuous, single or cumulative, not " << mode); fail = true; } else { LOGDEBUG(logger, "Setting operation_mode for table " << table << " is " << mode); } } if (!hlp.CheckConfig("db_logchangedonly", Setting::TypeBoolean, true)) { fail = true; } else { hlp.GetConfig("db_logchangedonly", logchangedonly, false); } bool allow_sparse; if (!hlp.CheckConfig("db_allowsparse", Setting::TypeBoolean, true)) { fail = true; } else { hlp.GetConfig("db_allowsparse", allow_sparse, false); } #warning FIXME logevery is optional and should be derived from the inverter if not specified. #warning THIS IS A RELEASE GOAL FOR 0.26 if (!hlp.CheckConfig("db_logevery", Setting::TypeFloat)) { fail = true; } else { hlp.GetConfig("db_logevery", logevery); } hlp = CConfigHelper(hlp.GetCfgPath(),"db_layout"); if (!hlp.isExisting()) { LOGFATAL(logger, "Configuration error: Entry db_layout missing for table " << table); i++; continue; } // note, on misconfiguration (no valid data source!) base *can* be NULL here! if (base) { dbwh = new CDBWriterHelper(base, logger, table, mode, createmode, logchangedonly, logevery, allow_sparse); } else { continue; } int j=0; bool k = true; // Iterate over all datasets to be logged. do { std::string capa,column; capa.clear(); column.clear(); CConfigHelper hlp2(hlp.GetCfgPath(),j); k = hlp2.isExisting(); if (!k) break; if (!(hlp.GetConfigArray(j, 0, capa) && hlp.GetConfigArray(j, 1, column))) { LOGFATAL(logger, "Config Error in entry " << j << " of " << table << "::" << "db_layout"); fail = true; j++; continue; } LOGDEBUG(logger, "j=" << j << " capa=" << capa << " column=" << column); if (capa.empty()) { LOGFATAL(logger, "Capability in entry " << j << " of " << table << "::" << "db_layout must not be empty"); fail = true; j++; continue; } if (column.empty()) { LOGFATAL(logger, "Column in entry " << j << " of " << table << "::" << "db_layout must not be empty"); fail = true; j++; continue; } if (!dbwh->AddDataToLog(capa,column)) {fail = true;} j++; } while(k); if (!j) { LOGFATAL(logger, "no layout for table " << table); fail = true; } _dbwriterhelpers.push_back(dbwh); i++; } while(true); return !fail; } void CDBWriterFilter::Update( const IObserverSubject *subject ) { // TODO check if we can completely empty this member function // NOTE: The Observer-Pattern-Handling is delegated to the helper claa // CDBWriterHelper assert(subject); CCapability *cap = (CCapability *)subject; // Datastate changed. if (cap->getDescription() == CAPA_INVERTER_DATASTATE) { _datavalid = ((CValue *)cap->getValue())->Get(); return; } // Unsubscribe plea -- we do not offer this Capa, our customers will // ask our base directly. if (cap->getDescription() == CAPA_CAPAS_REMOVEALL) { auto_ptr it(base->GetCapaNewIterator()); while (it->HasNext()) { pair cappair = it->GetNext(); cap = (cappair).second; cap->UnSubscribe(this); } return; } } void CDBWriterFilter::ScheduleCyclicWork(void) { ICommand *ncmd = NULL; struct timespec ts; std::vector::iterator it; for (it = _dbwriterhelpers.begin(); it != _dbwriterhelpers.end(); it++) { ncmd = new ICommand(CMD_CYCLIC, this); ncmd->addData("DB_WORK", *it); float logevery = (*it)->getLogevery(); ts.tv_sec = logevery; ts.tv_nsec = (logevery - ts.tv_sec) * 1e9; Registry::GetMainScheduler()->ScheduleWork(ncmd, ts); } } void CDBWriterFilter::ExecuteCommand( const ICommand *cmd ) { // LOGERROR(logger, __PRETTY_FUNCTION__ << " Warning not finished / implemented"); switch (cmd->getCmd()) { case CMD_INIT: { DoINITCmd(cmd); ScheduleCyclicWork(); } break; case CMD_CYCLIC: { DoCYCLICmd(cmd); CDBWriterHelper* helper = NULL; try { helper = boost::any_cast( cmd->findData("DB_WORK")); } catch (...) { LOGDEBUG(logger, __PRETTY_FUNCTION__ << "Unexpected exception while getting " "object out of cmd"); } assert(helper); LOGTRACE(logger, "now handling: " << helper->GetTable()); if (!_sqlsession.is_open()) { LOGDEBUG(logger, "Trying to open database."); try { _sqlsession.open(_connectionstring); } catch (std::exception const &e) { LOGWARN(logger, "Exception while opening database: " << e.what()); } } if (_sqlsession.is_open()) { try { // The helper will pass exceptions by the cppdb library // to give access to the error message. helper->ExecuteQuery(_sqlsession); } catch (const std::exception &e) { LOGWARN(logger, "Exception while handling database access:" << e.what() ); LOGWARN(logger, "Closing DB connection to try error recovery."); _sqlsession.close(); } } ICommand *ncmd = new ICommand(CMD_CYCLIC, this); struct timespec ts; ncmd->addData("DB_WORK", helper); float logevery = helper->getLogevery(); ts.tv_sec = logevery; ts.tv_nsec = (logevery - ts.tv_sec) * 1e9; Registry::GetMainScheduler()->ScheduleWork(ncmd, ts); } break; case CMD_BRC_SHUTDOWN: // shutdown requested, we will terminate soon. try { if (_sqlsession.is_open()) _sqlsession.close(); } catch (std::exception const &e) { LOGWARN(logger, " Exception while closing sqlsession. " << e.what()); } break; } } void CDBWriterFilter::DoINITCmd( const ICommand * ) { assert(base); CCapability *cap; cap = base -> GetConcreteCapability(CAPA_CAPAS_UPDATED); assert(cap); cap->Subscribe(this); cap = base->GetConcreteCapability(CAPA_CAPAS_REMOVEALL); assert(cap); cap->Subscribe(this); cap = base->GetConcreteCapability(CAPA_INVERTER_DATASTATE); assert(cap); cap->Subscribe(this); return; } void CDBWriterFilter::DoCYCLICmd( const ICommand * ) { // LOGERROR(logger, __PRETTY_FUNCTION__ << " not implemented"); return; } CConfigCentral* CDBWriterFilter::getConfigCentralObject(CConfigCentral* parent) { if (!parent) parent = new CConfigCentral; (*parent)(NULL, Description_DBWriter_Intro); parent = IDataFilter::getConfigCentralObject(parent); (*parent) ("db_type", Description_DBWriter_db_type, _cfg_cache_db_type) ("db_host", Description_DBWriter_db_host, _cfg_cache_db_host, std::string("")) ("db_port", Description_DBWriter_db_port, _cfg_cache_db_port, std::string("")) ("db_unixsocket", Description_DBWriter_db_unixsocket, _cfg_cache_db_unixsocket, std::string("")) ("db_mode", Description_DBWriter_db_mode, _cfg_cache_db_mode, std::string("")) ("db_user", Description_DBWriter_db_user, _cfg_cache_db_user, std::string("")) ("db_password", Description_DBWriter_db_passwd, _cfg_cache_db_passwd, std::string("")) ("db_database", Description_DBWriter_db_database, _cfg_cache_db_database, std::string("")) ("db_cppdb_options", Description_DBWriter_db_cppdb_options, _cfg_cache_db_cppdb_options, std::string("")) ; parent->SetExample("type", std::string(FILTER_DBWRITER), false); return parent; } #endif solarpowerlog-solarpowerlog-0.26/src/DataFilters/DBWriter/CDBWriterFilter.h000066400000000000000000000053111444065341000270150ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2015 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file CDBWriterFilter.h * \date Jul 28, 2014 * \author Tobias Frost * * TODO Dokumentation is missing!!!!! */ #ifndef CDBWriterFilter_H_ #define CDBWriterFilter_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_FILTER_DBWRITER #include "DataFilters/interfaces/IDataFilter.h" #include "CDBWriterHelper.h" #include /** This class implements a logger to write the data to a CSV File * * Please see \ref DLCSV_Description for configuration, etc. */ class CDBWriterFilter : public IDataFilter { protected: friend class IDataFilterFactory; CDBWriterFilter( const std::string &name, const std::string & configurationpath ); public: virtual ~CDBWriterFilter(); virtual bool CheckConfig(); virtual void Update( const IObserverSubject *subject ); virtual void ExecuteCommand( const ICommand *cmd ); private: /** Do the initialization of the module */ void DoINITCmd (const ICommand *); /** does the actual work: */ void DoCYCLICmd(const ICommand *); void ScheduleCyclicWork(void); virtual CConfigCentral* getConfigCentralObject(CConfigCentral* parent); enum Commands { CMD_BRC_SHUTDOWN = BasicCommands::CMD_BRC_SHUTDOWN, CMD_INIT = BasicCommands::CMD_USER_MIN, CMD_CYCLIC }; std::string _connectionstring; std::string _table; std::vector _dbwriterhelpers; bool _datavalid; cppdb::session _sqlsession; // Configuration cache std::string _cfg_cache_db_type; std::string _cfg_cache_db_host; std::string _cfg_cache_db_port; std::string _cfg_cache_db_unixsocket; std::string _cfg_cache_db_mode; std::string _cfg_cache_db_user; std::string _cfg_cache_db_passwd; std::string _cfg_cache_db_database; std::string _cfg_cache_db_cppdb_options; }; #endif #endif /* CDBWriterFilter_H_ */ solarpowerlog-solarpowerlog-0.26/src/DataFilters/DBWriter/CDBWriterHelper.cpp000066400000000000000000000752061444065341000273540ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2014 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /* * CDBWriterHelper.cpp * * Created on: 05.07.2014 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif #ifdef HAVE_FILTER_DBWRITER #include "CDBWriterHelper.h" #include "Inverters/Capabilites.h" #include "Inverters/interfaces/ICapaIterator.h" #include "patterns/CValue.h" #include "interfaces/CMutexHelper.h" #include "CDBWHSpecialTokens.h" #include "configuration/ConfigCentral/CConfigCentral.h" #include #define SQL_ESCAPE_CHAR_OPEN "" #define SQL_ESCAPE_CHAR_CLOSE "" CDBWriterHelper::CDBWriterHelper(IInverterBase *base, const ILogger &parent, const std::string &table, const std::string &mode, const std::string &createmode, bool logchangedonly, float logevery, bool allow_sparse, std::string configurationpath) { logger.Setup(parent.getLoggername(), "CDBWriterHelper(" + table + ")"); _table = table; _table_sanizited = false; _logevery = logevery; _logchangedonly = logchangedonly; _base = base; _datavalid = false; _createtable_mode = CDBWriterHelper::cmode_no; _allow_sparse = allow_sparse; _lastlogged = boost::posix_time::min_date_time; if (createmode == "YES") { _createtable_mode = CDBWriterHelper::cmode_yes; } else if (createmode == "YES-WIPE-MY-DATA") { _createtable_mode = CDBWriterHelper::cmode_yes_and_drop; } else if (createmode == "print-sql-statement") { _createtable_mode = CDBWriterHelper::cmode_print_statment; } if (mode == "continuous") { _mode = CDBWriterHelper::continuous; } else if (mode == "single") { _mode = CDBWriterHelper::single; } else if (mode == "cumulative") { _mode = CDBWriterHelper::cumulative; } else { // mode must be one of the above options. If the assertion triggers, // ConfigCheck has failed. assert(false); } if (base) { CCapability *cap; cap = base->GetConcreteCapability(CAPA_CAPAS_UPDATED); assert(cap); cap->Subscribe(this); cap = base->GetConcreteCapability(CAPA_CAPAS_REMOVEALL); assert(cap); cap->Subscribe(this); cap = base->GetConcreteCapability(CAPA_INVERTER_DATASTATE); assert(cap); cap->Subscribe(this); } } CDBWriterHelper::~CDBWriterHelper() { std::map::iterator it; for (it = _dbinfo.begin(); it != _dbinfo.end(); it++) { delete it->second; } _dbinfo.clear(); } bool CDBWriterHelper::CheckConfig(void) { } CConfigCentral* CDBWriterHelper::getConfigCentralObject(CConfigCentral *parent) { #warning FIXME CConfigCentral not completly implemented for CDBWriterHelper if (!parent) parent = new CConfigCentral; // array "jobs" (*parent)("db_jobs", "db_jobs", "(\t{") ("db_table","Description_db_table", _cfg_cache_db_table) ("db_create_table","Desription_db_create_table", _cfg_cache_db_create_table, std::string("no")) ("db_operation_mode","Description_db_operation_mode", _cfg_cache_db_operation_mode) ("db_logchangedonly", "Description_db_logchangedonly", _cfg_cache_db_logchangedonly, false) ("db_allowsparse", "Description_db_allowsparse", _cfg_cache_db_allowsparse, false) ("db_logevery", "Description_db_logevery", _cfg_cache_db_logevery) ("db_layout", "Description_db_layout", "example db layout") (NULL, "Description of special tokens etc") (NULL, "closing brackets, additional jobs explained", " } /* { .. addtional jobs .. */ );") ; return parent; } /** Add the tuple Capability, Column to the "should be logged information" * Returns "FALSE" if the combination of Capability and Column is already there. */ bool CDBWriterHelper::AddDataToLog(const std::string &Capability, const std::string &Column) { assert(!Capability.empty()); assert(!Column.empty()); // Check if we need to complain on the tablename... if (!_table_sanizited && !issane(_table)) { LOGFATAL(logger, "Tablename is not sane: " << _table); return false; } _table_sanizited = true; if (!issane(Column)) { LOGFATAL(logger, "Columname is not sane: " << Column); return false; } // Check if column already used, std::map::iterator it; for (it = _dbinfo.begin(); it != _dbinfo.end(); it++) { if (it->second->Column == Column) { LOGFATAL(logger, "Table " << _table << ": Column " << Column <<" already used as target:"); return false; } } LOGDEBUG(logger, "\"" << Capability << "\" will be logged to column \"" << Column << "\""); // Create new entry in the Cdbinfo map. Cdbinfo &last = *new Cdbinfo(Capability, Column); _dbinfo.insert(pair(Capability, &last)); // Create special tokens. if (Capability[0] == '%' || Capability[0] == '!') { LOGDEBUG(logger, "Special token " << Capability); IDBHSpecialToken *nt = CDBHSpecialTokenFactory::Factory( Capability.substr(1)); if (!nt) { LOGFATAL(logger, "Cannot create special token object " << Capability); return false; } time_t t = time(0); struct tm *tm = localtime(&t); nt->Update(*tm); last.Value = dynamic_cast(nt); assert(last.Value); last.isSpecial = true; } // Create selector tokens if (Capability[0] == '$') { LOGDEBUG(logger, "Selector token " << Capability); if (Capability.length() <= 1) { LOGERROR(logger, "Selector token must not be empty."); return false; } } return true; } bool CDBWriterHelper::issane(const std::string s) { // needle is selected from mysqli.real-escape-string // I added the brackets, which might be overkill... const char *needle = "\"\'`[]\0\r\n\x1a% "; if (std::string::npos != s.find_first_of(needle, 0, sizeof(*needle))) { return false; } return true; } void CDBWriterHelper::Update(const class IObserverSubject * subject) { #if 0 { std::multimap::iterator jt; LOGTRACE_SA(logger, LOG_SA_HASH("debug-code"), " in dbinfo:"); for (jt = _dbinfo.begin(); jt != _dbinfo.end(); jt++) { CMutexAutoLock cma(mutex); Cdbinfo &dut = *jt->second; LOGTRACE_SA(logger, LOG_SA_HASH("debug-code") + (long)(&dut), "cap=" << dut.Capability << " col=" << dut.Column); } } #endif assert(subject); CCapability *cap = (CCapability *)subject; #if 0 LOGTRACE(logger, "#############"); LOGTRACE(logger, "Capabilty=" << cap->getDescription()); LOGTRACE(logger, "##############"); { std::map::iterator jt; std::string current_subscriptions; for (jt = _dbinfo.begin(); jt != _dbinfo.end(); jt++) { Cdbinfo &cit = *jt->second; if (cit.previously_subscribed) { current_subscriptions += "\""; current_subscriptions += cit.Capability; current_subscriptions += "\" "; } } LOGTRACE(logger, "Current subscriptions: " << current_subscriptions); } LOGTRACE(logger, "##############"); #endif // Datastate changed. if (cap->getDescription() == CAPA_INVERTER_DATASTATE) { _datavalid = ((CValue *)cap->getValue())->Get(); if (_datavalid) { LOGTRACE_SA(logger, LOG_SA_HASH("update()-datastate"), "Update() Datastate valid"); } else { LOGTRACE_SA(logger, LOG_SA_HASH("update()-datastate"), "Update() Datastate invalid"); } return; } // Unsubscribe plea -- we do not offer this Capa, our customers will // ask our base directly. if (cap->getDescription() == CAPA_CAPAS_REMOVEALL) { LOGDEBUG(logger, "Update() CAPA_CAPAS_REMOVEALL received"); auto_ptr it(_base->GetCapaNewIterator()); while (it->HasNext()) { pair cappair = it->GetNext(); cap = (cappair).second; if (cap->getDescription() == CAPA_CAPAS_REMOVEALL) continue; if (cap->getDescription() == CAPA_CAPAS_UPDATED) continue; LOGTRACE(logger, "Update() unsubscribing " << cap->getDescription()); cap->UnSubscribe(this); } CMutexAutoLock cma(mutex); std::multimap::iterator jt; for (jt = _dbinfo.begin(); jt != _dbinfo.end(); jt++) { Cdbinfo &cit = *(*jt).second; logger.sa_forgethistory( LOG_SA_HASH("update-subs-state") + (long)(&cit)); cit.previously_subscribed = false; if (cit.Value && !cit.isSpecial) { LOGTRACE(logger, "Update() Remove-All deleting cit.Value for " << cit.Capability); delete cit.Value; cit.Value = NULL; } } return; } // "caps updated" -- there might be new capabilities... // iterate through all our needed capas and if not yet subscribed, do so. if (cap->getDescription() == CAPA_CAPAS_UPDATED) { LOGDEBUG(logger, "Update() CAPA_CAPAS_UPDATED received"); CMutexAutoLock cma(mutex); std::multimap::iterator jt; for (jt = _dbinfo.begin(); jt != _dbinfo.end(); jt++) { Cdbinfo &ci = *(jt->second); if (!ci.previously_subscribed) { CCapability *cap = _base->GetConcreteCapability(jt->first); if (cap) { LOGTRACE_SA(logger, LOG_SA_HASH("update-subs-state") + (long)(jt->second), "Update() Subscribing to " << jt->first); cap->Subscribe(this); ci.previously_subscribed = true; } } } return; } // OK, some caps has been updated. Lets get the value :) std::string capaname = cap->getDescription(); LOGTRACE(logger, capaname << " updated."); CMutexAutoLock cma(mutex); std::pair::iterator, std::multimap::iterator> ret = _dbinfo.equal_range(capaname); std::multimap::iterator it; for (it = ret.first; it != ret.second; it++) { Cdbinfo &cdi = *it->second; if (!cdi.Value) { cdi.Value = cap->getValue()->clone(); LOGTRACE_SA(logger, __COUNTER__ + (long)(&cdi), "1st Update(): " << capaname << " for column" << cdi.Column); } else { IValue &_old = *cdi.Value; IValue &_new = *(cap->getValue()); _old = _new; *cdi.Value = *cap->getValue(); } } } void CDBWriterHelper::ExecuteQuery(cppdb::session &session) { // Strategy // only log if data is marked as valid // // For "continuous" mode just add new columns // "sparse mode" -> add NULL if no data is there (log even if all_available is false) // If creating table, "sparse mode" cannot be used -- datatypes are needed to assemble the create statement. // In "single mode" the statement will need to have a selector to update the right row (smth like WHERE column == zzz) // This selektors are defined by a leading "$" in the Capability and the remaining part will be (after checking if it // is "sane") added to the satement. The column specifies the column, you guessed right :). // "Cumulative" mode first needs to try to update the row in question, and if that fails, try to add the row. // paranoid safety checks if (!_table_sanizited) { LOGDEBUG_SA(logger, __COUNTER__, "BUG: " << _table << " not sanitized"); return; } // Nothing to do... if (!_datavalid) { LOGDEBUG_SA(logger, LOG_SA_HASH("ExecuteQuery_datavalid"), "ExecuteQuery() " <<_table << " Data invalid"); return; } else { LOGDEBUG_SA(logger, LOG_SA_HASH("ExecuteQuery_datavalid"), "ExecuteQuery() " << _table << " Data valid"); } // We need to lock the mutex to ensure data consistency // ( Precautionious -- solarpowerlog is currently only single task, if it // comes to processing; but there are ideas to put db handling in a thread, // and then it will be needed.) CMutexAutoLock cma(mutex); // if lastsatementfailed is true, we have in any case do the work // (as this indicates that the last query failed.) bool any_updated = false; bool all_available = true; bool special_updated = false; time_t t = time(0); struct tm *tm = localtime(&t); boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); // check what we've got so far std::multimap::iterator it; for (it = _dbinfo.begin(); it != _dbinfo.end(); it++) { Cdbinfo &cit = *it->second; if (cit.Capability[0] == '$') { // don't consider $-selectors here. continue; } if (cit.isSpecial) { IDBHSpecialToken *st = dynamic_cast(cit.Value); if (st->Update(*tm)) { // only consider updated special vlaues if they are used. if (cit.Capability[0] == '!') special_updated = true; } LOGTRACE_SA(logger, LOG_SA_HASH("cit-updated") + (long)(&cit), "Updated " << cit.Capability << " to value " << st->GetString()); continue; } if (!cit.Value) { LOGDEBUG_SA(logger, LOG_SA_HASH("value-not-avail") + (long)(&cit), "Value for \"" << cit.Capability << "\" is not available"); all_available = false; continue; } else { LOGDEBUG_SA(logger, LOG_SA_HASH("value-not-avail") + (long)(&cit), "Value for \"" << cit.Capability << "\" is available"); } if (!cit.Value->IsValid()) { LOGDEBUG_SA(logger, LOG_SA_HASH("value-not-valid") + (long)(&cit), "Value for " << cit.Capability << " is not valid"); all_available = false; continue; } else { LOGDEBUG_SA(logger, LOG_SA_HASH("value-not-valid") + (long)(&cit), "Value for " << cit.Capability << " is valid"); } // use the IValue's timestamp to see if we've got any updates since // we logged the last time. if (cit.Value->GetTimestamp() >= _lastlogged) { any_updated = true; } } LOGTRACE_SA(logger, __COUNTER__, "Status: all_available=" << all_available << " any_updated=" << any_updated << " special_updated=" << special_updated); // Part 1 -- create table if necessary. if (_createtable_mode != CDBWriterHelper::cmode_no) { LOGTRACE_SA(logger, __COUNTER__, "_createtable_mode != no"); // Creation of the table is only possible if we have all data, as we need to know the // datatyptes if (!all_available) { LOGDEBUG_SA(logger, LOG_SA_HASH("not-all-data-there"), "Not all data available for CREATE TABLE " << _table << ". Retrying later."); return; } else { LOGDEBUG_SA(logger, LOG_SA_HASH("not-all-data-there"), "All data available for CREATE TABLE " << _table); } // Iterate through all specs and assemble table. std::string tablestring; for (it = _dbinfo.begin(); it != _dbinfo.end(); it++) { Cdbinfo &info = *it->second; if (!tablestring.empty()) { tablestring += ", "; } tablestring += SQL_ESCAPE_CHAR_OPEN + info.Column + SQL_ESCAPE_CHAR_CLOSE + " "; if (info.Capability[0] == '$') { // Special Select-Token we going to ignore LOGWARN_SA(logger, LOG_SA_HASH("create-info1") + (long)(&info), "When creating tables, $-Selector columns will be created " " with the datatype \"TEXT\". " "If necessary, please correct it yourself: Column \"" << info.Column << "\" using Selector \"" << info.Capability.substr(1) << "\"."); LOGWARN_SA(logger, LOG_SA_HASH("create-info2") + (long)(&info), "Logging might fail until then!"); tablestring += "TEXT"; continue; } // Try to determine datatype. // Note: This list might be needed to be updated when new Capabilities are added // using new datatypes! assert(info.Value); if (CValue::IsType(info.Value) || CValue::IsType(info.Value)) { LOGTRACE(logger, info.Capability <<" is FLOAT and so will be column " << info.Column); tablestring += "FLOAT"; } else if (CValue::IsType(info.Value)) { LOGTRACE(logger, info.Capability <<" is BOOLEAN and so will be column " << info.Column); tablestring += "BOOLEAN"; } else if (CValue::IsType(info.Value)) { LOGTRACE(logger, info.Capability <<" is INTEGER and so will be column " << info.Column); tablestring += "INTEGER"; } else if (CValue::IsType(info.Value)) { LOGTRACE(logger, info.Capability <<" is TEXT and so will be column " << info.Column); tablestring += "TEXT"; } else if (CValue::IsType(info.Value)) { LOGTRACE(logger, info.Capability <<" is TIMESTAMP and so will be column " << info.Column); tablestring += "TIMESTAMP"; } else { LOGERROR(logger, "unknown datatype for " << info.Capability); LOGERROR(logger, "Its C++ typeid is " << typeid(*(info.Value)).name()); LOGERROR(logger, "Will NOT create table. Logging will not work. Please report a bug."); _createtable_mode = CDBWriterHelper::cmode_no; return; } } // we can't unlock the mutex for the sql transaction ... // as the drop/create table could introduce a race // with the later insert -- we evaluated the data states earlier // and this could change. std::string tmp; if (_createtable_mode == CDBWriterHelper::cmode_yes_and_drop) { tmp = "DROP TABLE IF EXISTS "; tmp += SQL_ESCAPE_CHAR_OPEN + _table + SQL_ESCAPE_CHAR_CLOSE + ";"; LOGDEBUG(logger, "Executing query: "<< tmp); session << tmp << cppdb::exec; } tmp = "CREATE TABLE IF NOT EXISTS "; tmp += SQL_ESCAPE_CHAR_OPEN + _table + SQL_ESCAPE_CHAR_CLOSE + " (" + tablestring + ");"; if (_createtable_mode == CDBWriterHelper::cmode_print_statment) { LOGINFO(logger, "Your CREATE statement for table " << _table << " is:" << endl << tmp); } else { LOGDEBUG(logger, "Executing query: " << tmp); // NOTE: Exceptions are passed to the caller to provide error information session << tmp << cppdb::exec; LOGWARN(logger, "Table created. Make sure to disable table creation in the config!"); LOGWARN(logger, "Otherwise, solarpowerlog might stomp on your database" " the next time you start it!"); } _createtable_mode = CDBWriterHelper::cmode_no; } // Part 2 -- create sql statement for adding / replacing ... data. // Check data availability / freshness. // if not everything is available and we do not doing a sparse table: if (!_allow_sparse && !all_available) { LOGDEBUG_SA(logger, LOG_SA_HASH("executequery-sparse"), "Only sparse dataset available."); return; } else { LOGDEBUG_SA(logger, LOG_SA_HASH("executequery-sparse"), "Dataset is not sparse (anymore)."); } // on continuous mode and single mode logchangedonly is not affected by special_updated. if ((_mode == CDBWriterHelper::continuous || _mode == CDBWriterHelper::single) && _logchangedonly && !any_updated) { LOGDEBUG_SA(logger, LOG_SA_HASH("executequery-changeddata"), "Not logging, as data is unchanged."); return; } else { LOGDEBUG_SA(logger, LOG_SA_HASH("executequery-changeddata"), "Logging, as data has changed."); } // logchangedonly on cumulative mode: // we'll always need to insert a new row if a special has changed. if (_mode == CDBWriterHelper::cumulative && _logchangedonly) { if (!any_updated && !special_updated) { LOGDEBUG_SA(logger, LOG_SA_HASH("executequery-cumulative-datachange"), "Not logging, as data is unchanged. (cumulative)"); return; } else { LOGDEBUG_SA(logger, LOG_SA_HASH("executequery-cumulative-datachange"), "Logging, as data has changed. (cumulative)"); } } // If we are still here: Seems some job to be done. // Case 1: continuous if (_mode == CDBWriterHelper::continuous) { // Create INSERT INTO ... statement, if not existing. // on sparse, we cannot use the cache if (!all_available) _insert_cache.clear(); // Step one: Create sql string. if (_insert_cache.empty()) { _insert_cache = "INSERT INTO "; _insert_cache += SQL_ESCAPE_CHAR_OPEN + _table + SQL_ESCAPE_CHAR_CLOSE + " " + _GetValStringForInsert() + ';'; LOGTRACE(logger, "SQL Statement: " << _insert_cache); } // second step: bind values. cppdb::statement stat; stat = session << _insert_cache; if (!_BindValues(stat)) { LOGDEBUG(logger, "bind for continuous failed."); return; } // OK, step 2 done --- step 3 is execute the query. // as now the data consistency is guaranteed, we can unlock the mutex cma.unlock(); // again, exceptions are passed to the caller. stat.exec(); // Still here? Then it must have worked... _lastlogged = now; LOGTRACE(logger, "SQL: Last_insert_id=" << stat.last_insert_id() << " Affected rows=" << stat.affected()); return; } else if (_mode == CDBWriterHelper::single || _mode == CDBWriterHelper::cumulative) { if (_mode == CDBWriterHelper::single) { LOGTRACE_SA(logger, __COUNTER__, "SINGLE MODE"); } if (_mode == CDBWriterHelper::cumulative) { LOGTRACE_SA(logger, __COUNTER__, "CUMULATIVE MODE"); } // single mode // updates a single row in the table, determined by the use of one or // more selectors. // We will first check if the dataset already exists to be able to select // either a UPDATE of INSERT statement. This needs only to be done // once, as once UPDATEd we can always UPDATE. // cumulative mode // very similar to the single mode (indeed we could merge them :)) // but there are additional selectors with non-static selecting values // This are "%"-special types, but the prefix is "!" to differentiate. // Step 1) Assemble data ppart std::string cols, selectors; cols = _GetValStringForUpdate(); if (cols.empty()) { LOGERROR_SA(logger, __COUNTER__, "No columns found for table " << _table); return; } for (it = _dbinfo.begin(); it != _dbinfo.end(); it++) { Cdbinfo &info = *it->second; if (info.Capability[0] == '$' || (_mode == CDBWriterHelper::cumulative && info.Capability[0] == '!')) { // Selector if (!selectors.empty()) { selectors += " AND "; } selectors += SQL_ESCAPE_CHAR_OPEN + info.Column + SQL_ESCAPE_CHAR_CLOSE + "=?"; //selectors += info.Capability.substr(1); // len of capability ensured in cfg check. } } if (selectors.empty()) { LOGERROR_SA(logger, __COUNTER__, "no selectors found for table " << _table); return; } std::string query_common = SQL_ESCAPE_CHAR_OPEN + _table + SQL_ESCAPE_CHAR_CLOSE + " SET " + cols + " "; std::string update_query = "UPDATE " + query_common + "WHERE " + selectors + ";"; LOGTRACE(logger, "Update-query=" << update_query); cppdb::statement stat; stat = session << update_query; if (!_BindValues(stat)) { LOGDEBUG_SA(logger, __COUNTER__, "bind failed (cumulative/single)."); return; } // now binding the selectors. for (it = _dbinfo.begin(); it != _dbinfo.end(); it++) { Cdbinfo &info = *it->second; if (info.Capability[0] == '$') { // Selector stat.bind(info.Capability.substr(1)); } else if (_mode == CDBWriterHelper::cumulative && info.Capability[0] == '!') { if (!_BindSingleValue(stat, info)) { LOGDEBUG_SA(logger, __COUNTER__, "Binding single value failed"); return; } } } LOGDEBUG(logger, "Binding done."); LOGDEBUG(logger, "Trying UPDATE query"); stat.exec(); _lastlogged = now; LOGDEBUG(logger, "UPDATE tried. " << stat.affected() << " affected row(s)"); if (stat.affected() == 0) { LOGDEBUG(logger, "no affected rows. Trying INSERT instead."); stat.clear(); // we try now INSERT INTO table (col1,col2,col3) VALUES (1,2,3); // as above in the continuous mode. std::string insert_query = "INSERT INTO [" + _table + "] " + _GetValStringForInsert(true) + ';'; LOGTRACE(logger, "SQL Statement: " << insert_query); stat = session << insert_query; LOGTRACE(logger, "Binding values and selectors ..."); if (!_BindValues(stat, true)) { LOGDEBUG_SA(logger, __COUNTER__, "Bind failed (single, insert)"); return; } LOGDEBUG(logger, "About to execute INSERT Query (single mode)"); stat.exec(); _lastlogged = now; LOGDEBUG(logger, "Insert tried. " << stat.affected() << " rows affected." << "Last ID=" << stat.last_insert_id()); return; } } return; } std::string CDBWriterHelper::_GetValStringForUpdate(void) { std::string ret; std::multimap::iterator it; for (it = _dbinfo.begin(); it != _dbinfo.end(); it++) { Cdbinfo &info = *it->second; if (info.Capability[0] == '$') { continue; } else { if (!ret.empty()) ret += ","; if (info.Value && info.Value->IsValid()) { ret += SQL_ESCAPE_CHAR_OPEN + info.Column + SQL_ESCAPE_CHAR_CLOSE + "=?"; } } } return ret; } //(col1,col2,col3) VALUES (?,?,?) std::string CDBWriterHelper::_GetValStringForInsert(bool with_selector) { std::multimap::iterator it; std::string vals, cols; for (it = _dbinfo.begin(); it != _dbinfo.end(); it++) { Cdbinfo &info = *it->second; if ((info.Value && info.Value->IsValid()) || (with_selector && info.Capability[0] == '$')) { if (!cols.empty()) cols += ','; if (!vals.empty()) vals += ','; cols += SQL_ESCAPE_CHAR_OPEN + info.Column + SQL_ESCAPE_CHAR_CLOSE; vals += "?"; }; } return '(' + cols + ") VALUES (" + vals + ')'; } bool CDBWriterHelper::_BindValues(cppdb::statement &stat, bool with_selector) { std::multimap::iterator it; for (it = _dbinfo.begin(); it != _dbinfo.end(); it++) { Cdbinfo &info = *it->second; if (with_selector) { if (info.Capability[0] == '$') { stat.bind(info.Capability.substr(1)); continue; } } if (!info.Value) continue; if (!info.Value->IsValid()) continue; if (!_BindSingleValue(stat, info)) return false; } return true; } bool CDBWriterHelper::_BindSingleValue(cppdb::statement &stat, Cdbinfo &info) { // Bind the values considering their datatypes. if (CValue::IsType(info.Value)) { stat.bind((double)((CValue *)info.Value)->Get()); } else if (CValue::IsType(info.Value)) { stat.bind(((CValue *)info.Value)->Get()); } else if (CValue::IsType(info.Value)) { stat.bind(((CValue *)info.Value)->Get()); } else if (CValue::IsType(info.Value)) { stat.bind(((CValue *)info.Value)->Get()); } else if (CValue::IsType(info.Value)) { stat.bind(((CValue *)info.Value)->Get()); } else if (CValue::IsType(info.Value)) { stat.bind(((CValue *)info.Value)->Get()); } else { LOGERROR_SA(logger, __COUNTER__, "unknown datatype for " << info.Capability); LOGERROR_SA(logger, __COUNTER__, "Its C++ typeid is " << typeid(*(info.Value)).name()); LOGERROR_SA(logger, __COUNTER__, "Cannot put data to database."); return false; } return true; } #endif solarpowerlog-solarpowerlog-0.26/src/DataFilters/DBWriter/CDBWriterHelper.h000066400000000000000000000147421444065341000270170ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2014 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /* * \file CDBWriterHelper.hpp * * Created on: 05.07.2014 * Author: tobi */ #ifndef DDBWRITER_HELPER_HPP_ #define DDBWRITER_HELPER_HPP_ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif #ifdef HAVE_FILTER_DBWRITER #include #include #include #include "configuration/ILogger.h" #include "patterns/IObserverObserver.h" #include "Inverters/interfaces/InverterBase.h" #include "CdbInfo.h" class CConfigCentral; /** Handling class for one db job (one table) * * This class maintains one table, retrieving data from the inverters, * validty checking and SQL statement issuing when conditions are met. */ class CDBWriterHelper : public IObserverObserver { public: CDBWriterHelper(IInverterBase *base, const ILogger &parent, const std::string &table, const std::string &mode, const std::string &createmode, bool logchangedonly, float logevery, bool allow_sparse, std::string configurationpath =""); virtual ~CDBWriterHelper(); // The SQL Magic.... virtual void ExecuteQuery(cppdb::session &session); /// Add the tuple Capability, Column to the "should be logged information" /// Returns "FALSE" if the combination of Capabilty and Column is alreaedy there. virtual bool AddDataToLog(const std::string &Capability, const std::string &Column); virtual void Update(const class IObserverSubject * subject); virtual const std::string &GetTable(void) { return _table; } /** Getter for logevery */ virtual float getLogevery() const { return _logevery; } /** Setter for logevery */ virtual void setLogevery(float logevery) { _logevery = logevery; } virtual bool CheckConfig(void); virtual CConfigCentral* getConfigCentralObject(CConfigCentral *parent); private: /** Assemble a value-string from the dbinfos * the generated form is: * [col1]=?, [col2]=?, ... [colx]=? * The "?" are for the values to be bound * \returns the generated value string */ std::string _GetValStringForUpdate(void); /** Assemble a column-value-string from the dbinfos * * the generated form is: * (col1,col2,col3) VALUES (?,?,?) * The "?" are for the values to be bound * * \returns the generated value string */ std::string _GetValStringForInsert(bool with_selector = false); /** Bind all "?" in the (previously calculated) value string * * \param s statement for the cppdb interaction * \param with_selector should the selectors skipped (false) or also bound. * * \returns true on sucess, false on error. */ bool _BindValues(cppdb::statement &s, bool with_selector = false); /** Bind a single value to the CppDB statement * * * \param stat statement for the cppdb interaction * \param with_selector should the selectors skipped (false) or also bound. * * \returns true on sucess, false on error. */ bool _BindSingleValue(cppdb::statement &stat, Cdbinfo &info); /** Check if a string is save to avoid SQL injections. * * * \returns false if a "forbidden" character is encountered. */ bool issane(const std::string s); // Definitions. /*** enum for the basic operational modes. */ enum omode { continuous, /**< "continuous" mode */ single, /**< "single" mode */ cumulative /**< "cumulative" mode */ }; /** enum for the "create the table" mode */ enum cmode { cmode_no, /**< do not create table */ cmode_yes, /**< try create table */ cmode_yes_and_drop, /**< destroy and recreate table */ cmode_print_statment /**< do not create table, just print the statement to create the table */ }; // Class state data / internal objects. /** The DB-Writer's parent inverter (or datafilter) */ IInverterBase *_base; /** The logger instance */ ILogger logger; /** mutex to protect concurrent access to this class data */ boost::mutex mutex; /** Caching the information if the table name is sane */ bool _table_sanizited; /** Caching the information if the inverter's data is marked valid */ bool _datavalid; /** Caching the config for create table and also state variable: * will be set to cmode_no once create table succeed. */ cmode _createtable_mode; /// Storage for the individual data sets to be stored (one per column) std::multimap _dbinfo; /// when the last logging took place (to determine changes in the dataset) boost::posix_time::ptime _lastlogged; /** Cache for "regular" insert sql statements -- we don't need to recalculate * them all over * (TODO check if we should also cache the cppdb::statement object) */ std::string _insert_cache; // Configuration cache etc. /** The table to act on */ std::string _table; /** Caching the operational mode */ omode _mode; /** Caching the config if we are allow sparse logging */ bool _allow_sparse; /** Caching the config if we should only log on changed data. */ bool _logchangedonly; /** Configuration cache: How often to log. */ float _logevery; #warning FIXME new config cache ... currently not hooked up, needs refactoring later. std::string _cfg_cache_db_table; std::string _cfg_cache_db_create_table; std::string _cfg_cache_db_operation_mode; bool _cfg_cache_db_logchangedonly; bool _cfg_cache_db_allowsparse; float _cfg_cache_db_logevery; }; #endif #endif /* DDBWRITER_HELPDER_HPP_ */ solarpowerlog-solarpowerlog-0.26/src/DataFilters/DBWriter/CdbInfo.cpp000066400000000000000000000024421444065341000257230ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2014 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /* * CdbInfo.cpp * * Created on: Jul 19, 2014 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif #ifdef HAVE_FILTER_DBWRITER #include "CdbInfo.h" Cdbinfo::~Cdbinfo() { if (Value) delete Value; }; Cdbinfo::Cdbinfo(std::string Capability, std::string Column) : Capability(Capability), Column(Column), Value(NULL), previously_subscribed(false), isSpecial(false) {}; #endif solarpowerlog-solarpowerlog-0.26/src/DataFilters/DBWriter/CdbInfo.h000066400000000000000000000036571444065341000254010ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2014 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /* \file CdbInfo.h * * Created on: Jul 19, 2014 * Author: tobi */ #ifndef CDBINFO_H_ #define CDBINFO_H_ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif #ifdef HAVE_FILTER_DBWRITER #include #include "patterns/IValue.h" /** Helper class to encapsulate the data for the db entry. * * This helper class bundles one set of information, one column-data tuple * for the database to be logged. * * Used only by CDBWriterHelper. */ class Cdbinfo { protected: friend class CDBWriterHelper; Cdbinfo(std::string Capability, std::string Column); ~Cdbinfo(); /// String of the capability std::string Capability; /// Column of the table std::string Column; /// Copy of the current value. IValue *Value; /// this field is just to suppress a debug-message during observer pattern /// handling bool previously_subscribed; /// is the IValue a special "%" or "!" value (and therefore save to upcast) bool isSpecial; }; #endif #endif /* CDBINFO_H_ */ solarpowerlog-solarpowerlog-0.26/src/DataFilters/HTMLWriter/000077500000000000000000000000001444065341000241705ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/DataFilters/HTMLWriter/CHTMLWriter.cpp000066400000000000000000000541341444065341000267470ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2015 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file CHTMLWriter.cpp * * Created on: Dec 20, 2009 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif #ifdef HAVE_FILTER_HTMLWRITER #include #include #include #include "DataFilters/HTMLWriter/CHTMLWriter.h" #include "configuration/Registry.h" #include "configuration/CConfigHelper.h" #include "configuration/ConfigCentral/CConfigCentral.h" #include "interfaces/CWorkScheduler.h" #include "Inverters/Capabilites.h" #include "Inverters/interfaces/ICapaIterator.h" #include "patterns/CValue.h" #include "configuration/ILogger.h" #include "DataFilters/HTMLWriter/formatter/IFormater.h" #include static const char *Description_HTMLWriter_Intro = "HTMLWriter Logger\n" "As the name suggests already, this logger creates HTML file which then can be " "served by a web server. To separate presentation from program logic, the page " "will be generated using a template, so the look and feel can be freely customized.\n" "The template library used for this is ctemplate: " "https://libctemplate.sourceforge.net. This site has also some documentation " "about the features and the syntax it expects in the template files. " "Please see the example template for an idea how to create your own. " "The logger is designed to work with the CVSLogger, so the HTML file can " "utilize this as well. " "To get a HTMLWriter, \"type\" below needs to " "be set to " FILTER_HTMLWRITER " (as indicated below.)"; static const char *Description_HTMLWriter_writeevery = "Defines how often the html page should be written in seconds. If the value " "is unset (or the default value), the timing will derived from the timing of the data source: " "The page will then written in the same interval as the inverter queries the data.\n" "Note: If the source does not report the update frequency and this parameter is unset/0, " "300 seconds is used instead."; static const char *Description_HTMLWriter_htmlfile = "The filename where to store the generated html file. " "The parameter may contain a \"%s\", which will be replaced by the current date " "in the format YYYY-MM-DD, generating a new file every day."; static const char *Description_HTMLWriter_generatetemplate = "To assist generating your own template files, the HTML writer can generated " "a template showing all the parameters and their names which can be used in " "writing the template.\n" "Set this parameter to true to turn the feature on."; static const char *Description_HTMLWriter_templatefile = "This parameter sets the filename where the generated template will be stored. " "Note that this parameter only defines the base of the filename, the program will " "append the HTMLWriter's name and \".hmtl\" to it."; static const char *Description_HTMLWriter_generatetemplate_dir = "This parameter sets the directory where the template generator should store " "its output."; static const char *Description_HTMLWriter_Formatter = "Sometimes the raw data in the Capabilities needs to be modified to be suitable " "for the HTMLWriter. For this several small helper exists. " "Those helper are declared with the formatters section.\n" "The example below shows the syntax of the array.\n" "The first column specifies the Capability the formatters should act on,\n" "the second column specify the formatter to be used,\n" "the third column allows to set the result to another template variable, if unset" "the result will be reported with the capability name in column 1 instead, " "overwritting that information.\n" "The other columns are parameters to the formatter, see below for details." ; static const char *Description_HTMLWriter_Formatter_example = "(\n" "##[ what_capability,\tformating_operation,\twhere_to_store,\tparameters ],\n" "# [ \"CSVDumper::Filename\", \"stripwebroot\", \"\" , \"/var/www\" ],\n" "# [ \"CSVDumper::LoggedCaps\", \"searchcvsentry\", \"powernow\", \"Current Grid Feeding Power\" ],\n" "# [ \"CSVDumper::LoggedCaps\", \"searchcvsentry\", \"kwhtoday\", \"Energy produced today (kWh)\" ]\n" "#)"; static const char* Description_HTMWriter_Frmter_searchwebroot = "The formatter searchwebroot modifies a pathname in a capability to strip any " "prefix from this path. As the name suggest this was design to aid serving " "a datafiles, for example a CVS file from the CVS Logger " "This is necessary as the URL-path will be different " "from the complete filename stored on the server.\n" "The searchwebroot formatter takes one parameter: the component of the path " "to be removed. It has a default value of /var/www. " "If it cannot find this component, it will not modify the path and report an " "error to the logs.\n." "As an example, the rule below will reformat the path /var/www/spl/ " "to the relative one, which can be served from the webserver as " "http:///spl/.\n" "example: [ \"CSVDumper::Filename\", \"stripwebroot\", \"\" , \"/var/www\" ]"; static const char* Description_HTMLWriter_Frmter_searchCVSentry = "The formatter searchcvsentry works in conjuntion with the CVS Logger to allow " "the template to easier find the data it is interested in the CVS file.\n" "It enumerates in which field the Capability is currently logged and stores this " "information -- the column the data is in -- in the given template variable.\n" "Usually it will always act on CSVDumper::LoggedCaps, as this Capability is " "reported from the CVS Logger. " "The parameter contains the name of the capability to-be-looked-for.\n" "example:\n" "[ \"CSVDumper::LoggedCaps\", \"searchcvsentry\", \"powernow\", \"Current Grid Feeding Power\" ]\n"; // helper, because we do not want the multi map for the formatters sorted. #warning unused, remove after resolving the switch to another container (see warning below) struct unsortedmultimap { //bool operator ()( const std::vector, const std::vector) const { bool operator ()(const std::string &, const std::string &) const { return false; } }; /// local helpers: Bundle the cyclic-event calculation here... /// the following function schedules the next CMD_CYCLIC Command, out of config /// or out of a Capability void CHTMLWriter::ScheduleCyclicEvent(enum Commands cmd) { // set all members ICommand *ncmd = new ICommand(cmd, this); CCapability *c; struct timespec ts; if (_cfg_writevery <= 0.001 ) { c = GetConcreteCapability(CAPA_INVERTER_QUERYINTERVAL); if (c && CValue::IsType(c->getValue())) { CValue *v = (CValue *)c->getValue(); ts.tv_sec = v->Get(); ts.tv_nsec = ((v->Get() - ts.tv_sec) * 1e9); } else { LOGINFO_SA(logger, __COUNTER__, "INFO: The associated inverter does not specify the " "interval of its querie. Defaulting to 300 seconds. Consider specifying the HTMLWriter's parameter writeevery"); ts.tv_sec = 300; ts.tv_nsec = 0; } } else { ts.tv_sec = _cfg_writevery; ts.tv_nsec = ((long)(_cfg_writevery - ts.tv_sec)) * 1e9; } Registry::GetMainScheduler()->ScheduleWork(ncmd, ts); } CHTMLWriter::CHTMLWriter(const string & name, const string & configurationpath) : IDataFilter(name, configurationpath), updated(false), datavalid(false) { _cfg_writevery = 0; _cfg_generate_template = false; updated = 0; datavalid = 0; // Schedule the initialization and subscriptions later... ICommand *cmd = new ICommand(CMD_INIT, this); Registry::GetMainScheduler()->ScheduleWork(cmd); // We do not anything on these capabilities, so we remove our list. // any cascaded filter will automatically use the parents one... CCapability *c = IInverterBase::GetConcreteCapability( CAPA_INVERTER_DATASTATE); CapabilityMap.erase(CAPA_INVERTER_DATASTATE); delete c; // Also we wont fiddle with the caps requiring our listeners to unsubscribe. // They also should get that info from our base. c = IInverterBase::GetConcreteCapability(CAPA_CAPAS_REMOVEALL); CapabilityMap.erase(CAPA_CAPAS_REMOVEALL); delete c; } CHTMLWriter::~CHTMLWriter() { // TODO Auto-generated destructor stub } bool CHTMLWriter::CheckConfig() { #warning this is incomplete! does not check the reformatter etc. std::auto_ptr cc(getConfigCentralObject(NULL)); bool fail = !cc->CheckConfig(logger, configurationpath);(cc->CheckConfig(logger, configurationpath)); if (!fail && !base) { LOGERROR(logger, "Cannot find datassource with the name " << _datasource); fail = true; } size_t s; s = _cfg_gen_template_dir.length(); if (s && _cfg_gen_template_dir[s - 1] != '/') { _cfg_gen_template_dir += '/'; } return !fail; } void CHTMLWriter::Update(const IObserverSubject *subject) { // note: the subject must be a CCapability here. // to avoid neverending casting we do that once. CCapability *parentcap = (CCapability *) subject; // check for the mandatory Capas now, as they might require // immediate actions. if (parentcap->getDescription() == CAPA_CAPAS_REMOVEALL) { CCapability *ourcap; // forward the notification. // but -- to be nice -- update the value first ourcap = IInverterBase::GetConcreteCapability(CAPA_CAPAS_REMOVEALL); assert (ourcap); assert (CValue::IsType(ourcap->getValue())); assert (CValue::IsType(parentcap->getValue())); CValue *a, *b; a = (CValue *) (ourcap->getValue()); b = (CValue *) (parentcap->getValue()); *a = *b; ourcap->Notify(); CheckOrUnSubscribe(false); return; } if (parentcap->getDescription() == CAPA_CAPAS_UPDATED) { CCapability *c = IInverterBase::GetConcreteCapability( CAPA_CAPAS_UPDATED); *(CValue *) c->getValue() = *(CValue *) parentcap->getValue(); c->Notify(); // there are new caps, but currently we won't do anything with it // later we want to check the list if we have attach ourself to the // listernes. return; } if (parentcap->getDescription() == CAPA_INVERTER_DATASTATE) { datavalid = ((CValue *) parentcap->getValue())->Get(); return; } if (!updated) { ICommand *cmd = new ICommand(CMD_UPDATED, this); Registry::GetMainScheduler()->ScheduleWork(cmd); } updated = true; } void CHTMLWriter::ExecuteCommand(const ICommand *cmd) { switch (cmd->getCmd()) { case CMD_INIT: { string tmp; CConfigHelper cfghlp(configurationpath); CCapability *c; // Subscribe to this->base inverter, all the required ones... assert(base); c = base->GetConcreteCapability(CAPA_CAPAS_UPDATED); assert(c); // this is required to have.... c->Subscribe(this); c = base->GetConcreteCapability(CAPA_CAPAS_REMOVEALL); assert(c); c->Subscribe(this); c = base->GetConcreteCapability(CAPA_INVERTER_DATASTATE); assert(c); c->Subscribe(this); ScheduleCyclicEvent(CMD_CYCLIC); } break; case CMD_CYCLIC: DoCyclicCmd(cmd); ScheduleCyclicEvent(CMD_CYCLIC); break; case CMD_UPDATED: // prepared for the next version. // "do on update mode" break; } } void CHTMLWriter::DoCyclicCmd(const ICommand *) { ofstream fs; // C-Template vars TMPL_varlist *tmpl_looplist = NULL; /**< list for the inverter loop */ TMPL_loop *tmpl_loop = NULL; /**< loop */ TMPL_varlist *tmpl_list = NULL; /**< list for generationg the out */ if (!datavalid) { return; } if (_cfg_generate_template) { std::string s; s = _cfg_gen_template_dir + _cfg_name + ".html"; fs.open(s.c_str(), fstream::out | fstream::trunc | fstream::binary); #ifdef HAVE_WIN32_API if (fs.fail()) { fs.clear(); fs.open(s.c_str(), fstream::out | fstream::app | fstream::binary); } #endif if (fs.fail()) { LOGWARN(logger,"Template-Assistant: Failed to open file: " << s); fs.close(); } fs << "

This are the variables known to " "the template system:

" << endl << "" ""; } // Get the configuration to the formatting specs and map them #warning make this only once; it is also not config-checked! #warning note, this multimap is sorted; switch to other container like vector to avoid this \ as the formatters should be "ordered" -- see the unused struct unsortedmultimap above multimap > formattermap; std::string s = configurationpath + ".formatters"; std::string s1, s2; CConfigHelper formatters(s); for (int i = 0;; i++) { // LOG_TRACE(logger, "In formatter loop: i="<'); } else { break; } } // reminder: this first version only attaches one inverter! // Step one: // loop over all capabilites and add it to the list // Add the number of the inverter to the exported vars, as the snippets // probably want to do something special on the first one only. // TODO FIXME currently, there is only one. So we make this dynamic later. tmpl_looplist = TMPL_add_var(tmpl_looplist, "iteration", "0", NULL); if (_cfg_generate_template) { fs << "\n"; } auto_ptr cit(GetCapaNewIterator()); while (cit->HasNext()) { multimap >::iterator it; pair cappair = cit->GetNext(); // get the value of the capability std::string value = *(cappair.second->getValue()); // cache the name of the capability std::string templatename = cappair.first.c_str(); // LOG_TRACE(logger, "Capability: " << cappair.first << "\tValue: "<< value); // TODO Rip this part into its own function -- this way, we can also do some daisy-chain // formatting: Modify value x to value y, modify value y to value z .... #if 0 // debug code: dump the multimap find results. for (it = formattermap.find(cappair.first); it != formattermap.end(); it++) { LOGTRACE(logger, "***** " << templatename <<": found 1st=" << (*it).first << " 2nd " << (*it).second[0]); } #endif for (it = formattermap.find(cappair.first); it != formattermap.end(); it++) { IFormater *frmt; // the multimap returns everything after the first result // so we have to recheck if we really want this result. // FIXME the multimap seems not to be best for the task, so maybe // code should be reworked to use another container. if (cappair.first != (*it).first ) break; string formatter_to_create = (*it).second[0]; LOGTRACE(logger, "reformatting " << templatename << " with a " << (*it).second[0] ); if ((frmt = IFormater::Factory(formatter_to_create))) { if (!frmt->Format(value, value, (*it).second)) { LOGERROR(logger,"Could not reformat " << cappair.first << ": Formatter reported error."); } // check if we should store the result to another template // variable not the original one. if ((*it).second.size() > 2 && (*it).second[1] != "") { // yes, store the result to the new template var. tmpl_looplist = TMPL_add_var(tmpl_looplist, (*it).second[1].c_str(), value.c_str(), NULL); if (_cfg_generate_template) { fs << "\n"; } // in this case, restore the original value before checking // out a possible next formatter. value = *(cappair.second->getValue()); } delete frmt; } else { LOGERROR(logger,"Failed to create formatter " << formatter_to_create ); } } tmpl_looplist = TMPL_add_var(tmpl_looplist, templatename.c_str(), value.c_str(), NULL); if (_cfg_generate_template) { fs << "\n"; } } // adding the loop entry to the loop varlist. tmpl_loop = TMPL_add_varlist(tmpl_loop, tmpl_looplist); tmpl_looplist = NULL; // (here, we would add the other inverters linked to this story) // now adding all the stuff to the main list. tmpl_list = TMPL_add_loop(tmpl_list, "inverters", tmpl_loop); if (_cfg_generate_template) { fs << "" "" << endl; } map::const_iterator it; for (it = CapabilityMap.begin(); it != CapabilityMap.end(); it++) { pair cappair = *it; std::string tmpstring = *(cappair.second->getValue()); tmpl_list = TMPL_add_var(tmpl_list, cappair.first.c_str(), tmpstring.c_str(), NULL); if (_cfg_generate_template) { fs << "\n"; } } // we add some others here.... tmpl_list = TMPL_add_var(tmpl_list, "spl_version", PACKAGE_VERSION, NULL); // template assistance is now done, so close the file. if (_cfg_generate_template) { fs << "
Name of the AttributeCurrent Value
iteration " << "0" << "
" << (*it).second[1].c_str() << "

reformatted from " << templatename << "

" << value << "
" << cappair.first << " " << value << "
HTMWriter Capabilities(these are outside of the loop)
" << cappair.first << " " << tmpstring << "
"; fs.close(); } // okay, now open a file in memory to catch template errors. // (if supported at least) FILE *errfile = NULL; FILE *out = NULL; #ifdef HAVE_OPEN_MEMSTREAM char *ptr = NULL; size_t size = 0; #endif #ifdef HAVE_OPEN_MEMSTREAM errfile = open_memstream(&ptr, &size); #else #warning Note: opem_memstream not available on this platform. Will not be able to tell you template errors #endif // generate filename of the output file if (_cfg_html_file.find("%s") != std::string::npos) { boost::gregorian::date today(boost::gregorian::day_clock::local_day()); char buf[_cfg_html_file.size() + 10]; //note: the %s will be removed, so +10 is enough. int year = today.year(); int month = today.month(); int day = today.day(); snprintf(buf, sizeof(buf) - 1, "%s%04d-%02d-%02d%s", _cfg_html_file.substr(0, _cfg_html_file.find("%s")).c_str(), year, month, day, _cfg_html_file.substr(_cfg_html_file.find("%s") + 2, string::npos).c_str()); out = fopen(buf, "w+"); if (out == 0) { LOGERROR(logger, "Could not open filename "<< buf); } } else { out = fopen(_cfg_html_file.c_str(), "w+"); if (out == NULL) { LOGERROR(logger, "Could not open filename "<< _cfg_html_file); } } if (out && -1 == TMPL_write(_cfg_template_file.c_str(), NULL, NULL, tmpl_list, out, errfile)) { // error while writing. lets examine errfile. // we need first to close the file to update ptr and size. #ifdef HAVE_OPEN_MEMSTREAM fclose(errfile); errfile = NULL; // avoid closing a 2nd time later. LOGERROR(logger, "Error while writing html file." " The template library reported this: " << ptr); #else // esp. for the cygwin 1.5 port, we cannot tell the reason why it happened. // patches are welcome to open a temporary file on disk instead and then // (when cygwin 1.7 comes out, this is no longer an issue: They implemented // the GNU extended syscall) LOGERROR(logger, "Error while writing html file (template error)"); #endif } else if (out) { LOGTRACE(logger, "Done writing HTML File. Wrote " << ftell(out) << " Bytes"); } // cleanup./ if (tmpl_list) TMPL_free_varlist(tmpl_list); if (out) fclose(out); #ifdef HAVE_OPEN_MEMSTREAM if (errfile) fclose(errfile); if (ptr) free(ptr); #endif } void CHTMLWriter::CheckOrUnSubscribe(bool subscribe) { assert(base); CCapability *cap = base->GetConcreteCapability(CAPA_INVERTER_DATASTATE); if (cap) cap->SetSubscription(this, subscribe); } CConfigCentral* CHTMLWriter::getConfigCentralObject(CConfigCentral *parent) { if (!parent) parent = new CConfigCentral; (*parent) (NULL, Description_HTMLWriter_Intro); parent = IDataFilter::getConfigCentralObject(parent); assert(parent); (*parent) ("writevery", Description_HTMLWriter_writeevery, _cfg_writevery, 0.0F , 0.0F ,FLT_MAX) ("htmlfile", Description_HTMLWriter_htmlfile, _cfg_html_file) ("generate_template", Description_HTMLWriter_generatetemplate, _cfg_generate_template, false) ("templatefile", Description_HTMLWriter_templatefile, _cfg_template_file, std::string("htmltemplate")) ("generate_template_dir", Description_HTMLWriter_generatetemplate_dir, _cfg_gen_template_dir, std::string("/tmp/")) ; // The following entries cannot be auto-checked, as too complex for // ConfigCentral. (*parent) // formatters ("formatters", Description_HTMLWriter_Formatter, Description_HTMLWriter_Formatter_example) // and some docs for the formatter types and their parameters ("searchwebroot formatter", Description_HTMWriter_Frmter_searchwebroot) ("searchcvsentry", Description_HTMLWriter_Frmter_searchCVSentry); parent->SetExample("type", std::string(FILTER_HTMLWRITER), false); return parent; } #endif solarpowerlog-solarpowerlog-0.26/src/DataFilters/HTMLWriter/CHTMLWriter.h000066400000000000000000000046761444065341000264220ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2015 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /* * CHTMLWriter.h * * Created on: Dec 20, 2009 * Author: tobi */ #ifndef CHTMLWRITER_H_ #define CHTMLWRITER_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_FILTER_HTMLWRITER #include "DataFilters/interfaces/IDataFilter.h" extern "C" { #include "ctemplate/ctemplate.h" } #include "Inverters/BasicCommands.h" /** Writes data prepared by other plugins / inverters * to a template, which can be HTML, for example * * This plugin does load a template, and replaces magic * values with the ones from Capabilites. * * It also can combine them from seveal inverters. * (planned) * */ class CHTMLWriter: public IDataFilter { public: CHTMLWriter(const string &name, const string & configurationpath); virtual ~CHTMLWriter(); virtual bool CheckConfig(); virtual void Update(const IObserverSubject *subject); virtual void ExecuteCommand(const ICommand *cmd); enum Commands { CMD_INIT = BasicCommands::CMD_USER_MIN, CMD_UPDATED, CMD_CYCLIC, }; virtual CConfigCentral* getConfigCentralObject(CConfigCentral *parent); private: void CheckOrUnSubscribe( bool subscribe = true ); // Configuration cache float _cfg_writevery; bool _cfg_generate_template; std::string _cfg_gen_template_dir; std::string _cfg_html_file; std::string _cfg_template_file; std::string _cfg_name; bool updated; bool datavalid; /// Helper: Generated the cyclic ICommand cmd and attach it. void ScheduleCyclicEvent(enum CHTMLWriter::Commands cmd); /// DoCyclicCmd -- makes the page. void DoCyclicCmd(const ICommand *); }; #endif #endif /* CHTMLWRITER_H_ */ solarpowerlog-solarpowerlog-0.26/src/DataFilters/HTMLWriter/formatter/000077500000000000000000000000001444065341000261735ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/DataFilters/HTMLWriter/formatter/CFormaterWebRootStrip.cpp000066400000000000000000000034671444065341000331170ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2010-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /* * CFormaterWebRootStrip.cpp * * Created on: Jan 4, 2010 * Author: tobi */ #include "DataFilters/HTMLWriter/formatter/IFormater.h" #include "DataFilters/HTMLWriter/formatter/CFormaterWebRootStrip.h" #include "configuration/CConfigHelper.h" CFormaterWebRootStrip::CFormaterWebRootStrip() { } CFormaterWebRootStrip::~CFormaterWebRootStrip() { // TODO Auto-generated destructor stub } bool CFormaterWebRootStrip::Format(const std::string &what, std::string &store, const std::vector ¶meters) { std::string strip; size_t pos; if (parameters.size() < 3) { strip = "/var/www"; } else { strip = parameters[2]; } // basic check: what must be bigger than strip. if (strip.length() > what.length()) return false; // make sure that the string is there pos = what.find(strip); if (pos != std::string::npos) { store = what.substr(strip.length(), std::string::npos); return true; } return false; } solarpowerlog-solarpowerlog-0.26/src/DataFilters/HTMLWriter/formatter/CFormaterWebRootStrip.h000066400000000000000000000030521444065341000325520ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2010-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /* * CFormaterWebRootStrip.h * * Created on: Jan 4, 2010 * Author: tobi */ #ifndef CFORMATERWEBROOTSTRIP_H_ #define CFORMATERWEBROOTSTRIP_H_ #include "DataFilters/HTMLWriter/formatter/IFormater.h" /// Formater for: Strip a webroot-path from the front of a capability class CFormaterWebRootStrip: public IFormater { public: virtual ~CFormaterWebRootStrip(); virtual bool Format(const std::string &what, std::string &store, const std::vector ¶meters); protected: // make the factory class my friend to instantiate the object. friend class IFormater; CFormaterWebRootStrip(); }; #endif /* CFORMATERWEBROOTSTRIP_H_ */ solarpowerlog-solarpowerlog-0.26/src/DataFilters/HTMLWriter/formatter/CFormatterSearchCSVEntry.cpp000066400000000000000000000037761444065341000335060ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2010-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /* * CFormatterSearchCSVEntry.cpp * * Created on: Jan 5, 2010 * Author: tobi */ #include "DataFilters/HTMLWriter/formatter/CFormatterSearchCSVEntry.h" #include using namespace std; bool CFormatterSearchCSVEntry::Format(const std::string & what, std::string & store, const std::vector & parameters) { // the task is to get a comma-seperated value by what // and extract the position for the element given by the parameter // [2] and store the number in store. std::string s; try { s = parameters[2]; } catch (...) { return false; } // What we got: // value, value2, value3 // We count the seperators, and we are set. size_t pos = what.find(s); if (pos == std::string::npos) return false; int count = 0; for (size_t i = 0; i < pos; i++) { if (what[i] == ',' && (i == 0 || what[i - 1] != '\\')) { count++; } } std::stringstream ss; ss << count; ss >> store; return true; } CFormatterSearchCSVEntry::CFormatterSearchCSVEntry() { // TODO Auto-generated constructor stub } CFormatterSearchCSVEntry::~CFormatterSearchCSVEntry() { // TODO Auto-generated destructor stub } solarpowerlog-solarpowerlog-0.26/src/DataFilters/HTMLWriter/formatter/CFormatterSearchCSVEntry.h000066400000000000000000000027711444065341000331450ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2010-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /* * CFormatterSearchCSVEntry.h * * Created on: Jan 5, 2010 * Author: tobi */ #ifndef CFORMATTERSEARCHCSVENTRY_H_ #define CFORMATTERSEARCHCSVENTRY_H_ #include "DataFilters/HTMLWriter/formatter/IFormater.h" class CFormatterSearchCSVEntry: public IFormater { public: virtual ~CFormatterSearchCSVEntry(); virtual bool Format(const std::string &what, std::string &store, const std::vector ¶meters); protected: // make the factory class my friend to instanciate the object. friend class IFormater; CFormatterSearchCSVEntry(); }; #endif /* CFORMATTERSEARCHCSVENTRY_H_ */ solarpowerlog-solarpowerlog-0.26/src/DataFilters/HTMLWriter/formatter/IFormater.cpp000066400000000000000000000025621444065341000305740ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2010-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /* * IFormater.cpp * * Created on: Jan 4, 2010 * Author: tobi */ #include "DataFilters/HTMLWriter/formatter/IFormater.h" #include "DataFilters/HTMLWriter/formatter/CFormaterWebRootStrip.h" #include "DataFilters/HTMLWriter/formatter/CFormatterSearchCSVEntry.h" IFormater *IFormater::Factory(const std::string &spec) { if (spec == "stripwebroot") { return new CFormaterWebRootStrip(); } if (spec == "searchcvsentry") { return new CFormatterSearchCSVEntry(); } return NULL; } solarpowerlog-solarpowerlog-0.26/src/DataFilters/HTMLWriter/formatter/IFormater.h000066400000000000000000000037711444065341000302440ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2010-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /* * IFormater.h * * Created on: Jan 4, 2010 * Author: tobi */ #ifndef IFORMATER_H_ #define IFORMATER_H_ #include #include /** IFormater a strategies for reformatting capabilites for the CHTML Writer * output. They take a string, a configuration and return the reformatted * result. * * It also contains its factory as a static member function. * */ class IFormater { public: static IFormater* Factory(const std::string &spec); /** This is the workhorse of the stragegy pattern: * The format takes a refrence string of the content to be modified, a * reference where it should suppose to write the result (note: might be * same refrence.) * and a vector of the formatters' configuration. * The vector contains: * [0] - formatter name (needed by caller, ignore) * [1] - where to stored (needed by caller, ignore) * [2...] - parameters supplied by user */ virtual bool Format(const std::string &what, std::string &store, const std::vector ¶meters) = 0; virtual ~IFormater() { } protected: IFormater() { } }; #endif /* IFORMATER_H_ */ solarpowerlog-solarpowerlog-0.26/src/DataFilters/interfaces/000077500000000000000000000000001444065341000243525ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/DataFilters/interfaces/IDataFilter.cpp000066400000000000000000000075261444065341000272200ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2015 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file IDataFilter.cpp * \date Jun 1, 2009 * \author Tobias Frost (coldtobi) */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "DataFilters/interfaces/IDataFilter.h" #include "Inverters/interfaces/CNestedCapaIterator.h" #include "configuration/CConfigHelper.h" #include "configuration/ConfigCentral/CConfigCentral.h" #define DESCRIPTION_DATAFILTER_INTRO \ "Like inverters, loggers and datafilters needs some basic configuration parameter:\n" \ "\"name\", \"type\" and \"datasource\"" #define DESCRIPTION_DATAFILTER_NAME \ "This parameter names the logger. The name are used internally to identify " \ "the logger and thus needs to be unique." #define DESCRIPTION_DATAFILTER_DATASOURCE \ "This parameter needs to state the name another logger or inverter -- " \ "the one which will supply data to this logger." #define DESCRIPTION_DATAFILTER_TYPE \ "Tells solarpowerlog the type of the logger to be created. This version of " \ "solarpowerlog supports the following loggers (and datafilters): " \ FILTER_DUMBDUMPER " " \ FILTER_CSVWRITER " " \ FILTER_HTMLWRITER " " \ FILTER_DBWRITER " " IDataFilter::IDataFilter(const string &name, const string & configurationpath) : IInverterBase(name, configurationpath, "datafilter"), base(0) { // try to setup base to be more error-robust. // this way, Datafilter have already setup their base early on. // The child can always override later, e.g after CMD_INIT. CConfigHelper hlp(configurationpath); std::string str; if (hlp.CheckAndGetConfig("datasource", libconfig::Setting::TypeString, str)) { this->base = Registry::Instance().GetInverter(str); } } IDataFilter::~IDataFilter() { // TODO Auto-generated destructor stub } ICapaIterator *IDataFilter::GetCapaNewIterator() { return new CNestedCapaIterator(this, base); } CCapability *IDataFilter::GetConcreteCapability( const string & identifier ) { CCapability *c; if ((c = IInverterBase::GetConcreteCapability(identifier))) { // TODO cleanup debug code // cout << "DEBUG: found " << identifier << " in " << GetName() // << endl; return c; } else { // TODO cleanup debug code // cout << "DEBUG: searching " << identifier << " in base class " // << base->GetName() << endl; return base->GetConcreteCapability(identifier); } } // datasource is config from the baseclass.. CConfigCentral* IDataFilter::getConfigCentralObject(CConfigCentral *parent) { static std::string dummy_string; if (!parent) parent = new CConfigCentral; (*parent)(NULL, DESCRIPTION_DATAFILTER_INTRO) ("name", DESCRIPTION_DATAFILTER_NAME, dummy_string) ("type", DESCRIPTION_DATAFILTER_TYPE, dummy_string) ("datasource", DESCRIPTION_DATAFILTER_DATASOURCE, _datasource); // Override optional setting and set a sane example. parent->SetExample("name", std::string(""), false); parent->SetExample("datasource", std::string(""), false); parent->SetExample("type", std::string(""), false); return parent; } solarpowerlog-solarpowerlog-0.26/src/DataFilters/interfaces/IDataFilter.h000066400000000000000000000104671444065341000266630ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2014 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \defgroup DataFilter Loggers and DataFilters: Description and Configuration * * This page documents the available loggers / datafilters. * * \ref CVSDataLogger * * \ref CDumpOutputFilter * * */ /** \file IDataFilter.h * * Created on: Jun 1, 2009 * Author: tobi * * Interface for all filters, data enhancers and finally all data-sinks * (which do not implement the ObserverSubject pattern) * * How it works: * - A filter, when instanciatesd, gets its configuration its own name * - Using that configuration, it connects to the data source (type IInverterBase) * and subscribes at least to the CAPA Notifications of the data source * - the filter is aware, that most likely the capabilites will change over * runtime. It will only serve the * - If a cdownsteram filter queries for something which is not * modified or created, the request is simply forwarded upstream */ #ifndef IDATAFILTER_H_ #define IDATAFILTER_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif /** \file IDatafilter.h * * A Datafilter is a processor for inverter datas, or a logger which will sinks * the data to some file or database, .... * * For details describing how inverters, capabilites and datafilter work * together, please see \ref IInverterBase "this page". * * TODO DOCUMENT ME! */ #include "patterns/IObserverObserver.h" #include "patterns/IObserverSubject.h" #include "Inverters/interfaces/InverterBase.h" #include "patterns/ICommand.h" #include "DataFilters/interfaces/factories/IDataFilterFactory.h" class IDataFilter : public IObserverObserver , public IInverterBase // The inverter, though the capabilites, also provides the // interface for being a subject (observer pattern) { protected: IDataFilter( const string &name, const string & configurationpath ); public: virtual ~IDataFilter(); /** Filters need not to use the CommandQueue facility, so defaulting to * doing nothing * */ virtual void ExecuteCommand( const ICommand *) { } // Just a reminder: You need to override that. virtual bool CheckConfig() = 0; // Just another reminder. Update is called by the subject, // when new data arrives. So you defintly want to override this. virtual void Update( const IObserverSubject *subject ) = 0; /** When iterating over all the Capabilities, also transparently iterate * over all parent objects as well. See INestedCapaIterator for details. * * \returns A "nested" CapaIterator, (CNestetCapaIterator), which * traverses thourh all layers. */ virtual ICapaIterator* GetCapaNewIterator(); /** Check if the current DataFilter has the capability, if not ask its * parent. * * Overriden from IInverterbase, this function checks first its own * list and then asks its parent for the Capability * * \param identifier Capability to be looked for. * */ virtual CCapability *GetConcreteCapability( const string &identifier ); // datasource is config from the baseclass.. virtual CConfigCentral* getConfigCentralObject(CConfigCentral *parent); protected: /// Inverter to connect to. Can also be a another DataFilter /// (as data are exchanged over the IInverterBase Interface, /// this does not matter. /// Note: The child has to set this to the proper value. /// If the child wants to reveive data from multiple / different sources, /// it also might use its own implementation IInverterBase *base; protected: std::string _datasource; }; #endif /* IDATAFILTER_H_ */ solarpowerlog-solarpowerlog-0.26/src/DataFilters/interfaces/factories/000077500000000000000000000000001444065341000263315ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/DataFilters/interfaces/factories/IDataFilterFactory.cpp000066400000000000000000000047331444065341000325240ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2015 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file IDataFilterFactory.cpp * * \date Jun 1, 2009 * \author Tobias Frost (coldtobi) */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "DataFilters/interfaces/factories/IDataFilterFactory.h" #ifdef HAVE_FILTER_DUMBDUMP #include "DataFilters/CDumpOutputFilter.h" #endif #ifdef HAVE_FILTER_CSVDUMP #include "DataFilters/CCSVOutputFilter.h" #endif #ifdef HAVE_FILTER_HTMLWRITER #include "DataFilters/HTMLWriter/CHTMLWriter.h" #endif #ifdef HAVE_FILTER_DBWRITER #include "DataFilters/DBWriter/CDBWriterFilter.h" #endif #include "configuration/Registry.h" #include "configuration/CConfigHelper.h" #include "DataFilters/interfaces/IDataFilter.h" IDataFilter *IDataFilterFactory::FactoryByName(const std::string &type, const std::string &name, const std::string &configurationpath) { #ifdef HAVE_FILTER_DUMBDUMP if (type == FILTER_DUMBDUMPER) { return new CDumpOutputFilter(name, configurationpath); } #endif #ifdef HAVE_FILTER_CSVDUMP if (type == FILTER_CSVWRITER) { return new CCSVOutputFilter(name, configurationpath); } #endif #ifdef HAVE_FILTER_HTMLWRITER if (type == FILTER_HTMLWRITER) { return new CHTMLWriter(name, configurationpath); } #endif #ifdef HAVE_FILTER_DBWRITER if (type == FILTER_DBWRITER) { return new CDBWriterFilter(name, configurationpath); } #endif return NULL; } IDataFilter *IDataFilterFactory::Factory(const string & configurationpath) { string type, name; CConfigHelper cfghlp(configurationpath); cfghlp.GetConfig("type", type); cfghlp.GetConfig("name",name); return FactoryByName(type, name, configurationpath); } solarpowerlog-solarpowerlog-0.26/src/DataFilters/interfaces/factories/IDataFilterFactory.h000066400000000000000000000036471444065341000321740ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2015 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file IDataFilterFactory.h * * \date Jun 1, 2009 * \author Tobias Frost (coldtobi) */ #ifndef IDATAFILTERFACTORY_H_ #define IDATAFILTERFACTORY_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #ifdef HAVE_FILTER_DUMBDUMP #define FILTER_DUMBDUMPER "DumpDumper" #else #define FILTER_DUMBDUMPER #endif #ifdef HAVE_FILTER_CSVDUMP #define FILTER_CSVWRITER "CVSWriter" #else #define FILTER_CSVWRITER #endif #ifdef HAVE_FILTER_HTMLWRITER #define FILTER_HTMLWRITER "HTMLWriter" #else #define FILTER_HTMLWRITER #endif #ifdef HAVE_FILTER_DBWRITER #define FILTER_DBWRITER "DBWriter" #else #define FILTER_DBWRITER #endif class IDataFilter; /** Factory for Data-Filters * *\ingroup factories */ class IDataFilterFactory { public: virtual IDataFilter *FactoryByName(const std::string &type, const std::string &name, const std::string &configurationpath); virtual IDataFilter* Factory( const std::string &configurationpath ); IDataFilterFactory() {}; virtual ~IDataFilterFactory() {}; }; #endif /* IDATAFILTERFACTORY_H_ */ solarpowerlog-solarpowerlog-0.26/src/Inverters/000077500000000000000000000000001444065341000220065ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Inverters/BasicCommands.h000066400000000000000000000042361444065341000246670ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ #ifndef _BASIC_COMMANDS_H #define _BASIC_COMMANDS_H #ifdef HAVE_CONFIG_H #include "config.h" #endif namespace BasicCommands { enum BasicCommands { // The events 0 - CMD_BROADCAST_MAX are reserved for eg. broadcast events. // Any event in this range will be distributed to any inverter and any // datafilter using the main CWorkScheduler (##Registry::GetMainScheduler()) // and are registered to receive broadcast events. /// SIG_TERM has been received and we are asked to terminate as soon as possible /// (For example, generally get read to close down, flush and close files, /// abort I/O if possible...) /// \note that you might receive other events until the programm really terminates /// (for example, aborting I/Os might generate events) /// \note after receiving this event, "timed work" /// (submitted via \sa CWorkSchedule::ScheduleWork()) /// will be accepted but never handled. However, immediately due events /// added after receiption of this event will still be handled. CMD_BRC_SHUTDOWN, CMD_BROADCAST_MAX, CMD_INVALID, /// can be used for fire-and-forget ICommands. // The events between CMD_BROADCAST_MAX and CMD_USER_MIN are reserved // at this moment. CMD_USER_MIN = 1000 }; } #endif solarpowerlog-solarpowerlog-0.26/src/Inverters/Capabilites.h000066400000000000000000000347501444065341000244100ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2015 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file Capabilites.h * * This file declares the cpabilites, a inverter might have. * (Of course also the ones a inverter "have to have") * The names defined are to be used for generation and lookup. * * The strings are for "human information" * * * * Created on: May 22, 2009 * Author: Tobias Frost * * Contributors: * E.A.Neonakis */ #ifndef CAPABILITES_H_ #define CAPABILITES_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif /** The Capabilites list has been updated. * This "pseudo" capabilites can be used to tell observers, that the * data provider might have detected new capabilites and therefore * check the presence of new interesting data. * * A common use is, if the InverterBase-client can do autodetection * of capabilities during runtime, or as a result of auto-detecting the * excact model of a Inverter Family. * * Every Observer should subscribe to this one. * *\note The associated concrete value is unused. * * \sa The pseudo cap "force unsubscribe" is related. * * THIS CAPABILITY IS REQUIRED -- EVERY INVERTER HAS THIS ONE. */ #define CAPA_CAPAS_UPDATED "CapabilityList Updated" #define CAPA_CAPAS_UPDATED_TYPE bool /** Some Capabilites are now void and the observers have to unsubscribe. * * This "pseudo" capabilites can be used to tell observers, that the * data provider detected a situation, where the Capbailities List have * to be recreated. * * For this to happen safely, every Subscriber must remove itself from the * subscription list on every capability -- except on the remove and add pseudo * capability. * *\note The associated concrete value is unused. * * Common situation swhere this will be used: * - Configuration reload * - Program termination * * In case of a reload, a "CAPA_CAPAS_UPDATED" will follow. * * \sa The pseudo cap "force unsubsribe" is related. * * THIS CAPABILITY IS REQUIRED -- EVERY INVERTER HAS THIS ONE.*/ #define CAPA_CAPAS_REMOVEALL "CapabilityList Please Unsubscribe" #define CAPA_CAPAS_REMOVEALL_TYPE bool /** Is data the provided by the inverter valid? * If the inverter is "power down" (like the Sputnik at night), this * is the master switch telling that all data is now invalid. * (if the associated value is false) * * THIS IS A MUST CAPABILITY -- EVERY INVERTER HAS THIS ONE. */ #define CAPA_INVERTER_DATASTATE "Data Validity" #define CAPA_INVERTER_DATASTATE_TYPE bool /** How often are the datas queried * * How often are the datas queried, if done cyclic. Unit is seconds. * * Type: float * * optional */ #define CAPA_INVERTER_QUERYINTERVAL "Data Query Interval" #define CAPA_INVERTER_QUERYINTERVAL_TYPE float /** Basic information for the user -- these information are usually not * updated. But, as a exception to this, a inverter class might do runtime * detection of these parameters, if the inverter supports them. * * This one is the "human readable" manufacturer of the Inverter. * * \depreciated this capa should no longer be used. Use * CAPA_INVERTER_MANUFACTURER_NAME instead */ #define CAPA_INVERTER_MANUFACTOR_NAME "Inverter Manufactor" #define CAPA_INVERTER_MANUFACTOR_TYPE std::string /** Basic information for the user -- these information are usually not * updated. But, as a exception to this, a inverter class might do runtime * detection of these parameters, if the inverter supports them. * * This one is the "human readable" manufacturer of the Inverter. */ #define CAPA_INVERTER_MANUFACTURER_NAME "Inverter Manufacturer" #define CAPA_INVERTER_MANUFACTURER_TYPE std::string /** Basic information for the user -- these information are usually not * updated. But, as a exception to this, a inverter class might do runtime * detection of these parameters, if the inverter supports them. * * This one is the "human readable" model of the Inverter. */ #define CAPA_INVERTER_MODEL "Inverter Model" #define CAPA_INVERTER_MODEL_TYPE std::string /** Basic information again -- how is the inverter named in the config file * Note: This has to be added by the concrete inverter class. */ #define CAPA_INVERTER_CONFIGNAME "Inverter Name" #define CAPA_INVERTER_CONFIGNAME_TYPE std::string /** Firmware version information. * * If available, can contain a human-readable info about the * inverters firmware * * type: string * * Optional. * */ #define CAPA_INVERTER_FIRMWARE "Firmware Version" #define CAPA_INVERTER_FIRMWARE_TYPE std::string /** Total power feeding AC,DC * * On inverters which feeds more than one phase, this is the * sum of all phases. * * Type: float * * Recommended for every inverter, but still optional */ #define CAPA_INVERTER_ACPOWER_TOTAL "Current Grid Feeding Power" #define CAPA_INVERTER_ACPOWER_TOTAL_TYPE float #define CAPA_INVERTER_DCPOWER_TOTAL "DC Power" #define CAPA_INVERTER_DCPOWER_TOTAL_TYPE float /** Power On Hours * * Counts the hours a inverter was powered. * * Type: float * * Optional. */ #define CAPA_INVERTER_PON_HOURS "Inverter Power On Hours" #define CAPA_INVERTER_PON_HOURS_TYPE float /** Total Inverter Startups * * Counts inverter startups * * Type: Integer * * Optional. */ #define CAPA_INVERTER_STARTUPS "Inverter Startups" #define CAPA_INVERTER_STARTUPS_TYPE long /** Feeded Energy Y2D * * This year the inverter has produced this amount of energy. (kWh) * * Type: float * * Optional. */ #define CAPA_INVERTER_KWH_Y2D "Energy produced this year (kWh)" #define CAPA_INVERTER_KWH_Y2D_TYPE float /** Feeded Energy M2D * * This month the inverter has produced this amount of energy. (kWh) * * Type: float * * Optional. */ #define CAPA_INVERTER_KWH_M2D "Energy produced this month (kWh)" #define CAPA_INVERTER_KWH_M2D_TYPE float /** Feeded Energy Today,Yesterday * * Today the inverter has produced this amount of energy. (kWh) * * Type: float * * Optional. */ #define CAPA_INVERTER_KWH_2D "Energy produced today (kWh)" #define CAPA_INVERTER_KWH_2D_TYPE float #define CAPA_INVERTER_KWH_YD "Energy produced yesterday (kWh)" #define CAPA_INVERTER_KWH_YD_TYPE float /** Feeded Energy Total * * Today the inverter has produced this amount of energy. (kWh) * * Type: float * * Optional. */ #define CAPA_INVERTER_KWH_TOTAL_NAME "Energy produced accumulated all time (kWh)" #define CAPA_INVERTER_KWH_TOTAL_TYPE float /** Installed Power * * How much power has beein installed? * * At least on the Sputnik, this can be configured and read by a query. * * Others can set this by e.g configuration options. * * Type: Float * * Optional. * * */ #define CAPA_INVERTER_INSTALLEDPOWER_NAME "Installed solar power (Wp)" #define CAPA_INVERTER_INSTALLEDPOWER_TYPE float /** Current AC Power Frequency * * * Type: Float * * Optional. * * */ #define CAPA_INVERTER_NET_FREQUENCY_NAME "Net frequency (Hz)" #define CAPA_INVERTER_NET_FREQUENCY_TYPE float /** relative power * * The Sputnik offers what is called "relative Power". * TODO: Check whats relative about that. * * (However, this reading is somwhat vague, so it is probably * better calculated by PAC and PIN or PDC*IDC and PIN) * * Type: Float * * Optional. * * */ #define CAPA_INVERTER_RELPOWER_NAME "relative Power (%)" #define CAPA_INVERTER_RELPOWER_TYPE float /** DC Input Voltage * * Input voltage from the generator. * * Type: Float * * Optional. * * */ #define CAPA_INVERTER_INPUT_DC_VOLTAGE_NAME "DC voltage in (V)" #define CAPA_INVERTER_INPUT_DC_VOLTAGE_TYPE float #define CAPA_INVERTER_INPUT_DC_VOLTAGE_T1_NAME "DC voltage Tracker 1 in (V)" #define CAPA_INVERTER_INPUT_DC_VOLTAGE_T1_TYPE float #define CAPA_INVERTER_INPUT_DC_VOLTAGE_T2_NAME "DC voltage Tracker 2 in (V)" #define CAPA_INVERTER_INPUT_DC_VOLTAGE_T2_TYPE float #define CAPA_INVERTER_INPUT_DC_VOLTAGE_T3_NAME "DC voltage Tracker 3 in (V)" #define CAPA_INVERTER_INPUT_DC_VOLTAGE_T3_TYPE float /** DC Input Current * * Input voltage from the generator. * * Type: Float * * Optional. * * */ #define CAPA_INVERTER_INPUT_DC_CURRENT_NAME "DC current in (A)" #define CAPA_INVERTER_INPUT_DC_CURRENT_TYPE float #define CAPA_INVERTER_INPUT_DC_CURRENT_T1_NAME "DC current Tracker 1 in (A)" #define CAPA_INVERTER_INPUT_DC_CURRENT_T1_TYPE float #define CAPA_INVERTER_INPUT_DC_CURRENT_T2_NAME "DC current Tracker 2 in (A)" #define CAPA_INVERTER_INPUT_DC_CURRENT_T2_TYPE float #define CAPA_INVERTER_INPUT_DC_CURRENT_T3_NAME "DC current Tracker 3 in (A)" #define CAPA_INVERTER_INPUT_DC_CURRENT_T3_TYPE float /** Power feeding DC per Tracker * * Type: float * * Optional. * * */ #define CAPA_INVERTER_DCPOWER_T1_NAME "DC Power Tracker 1" #define CAPA_INVERTER_DCPOWER_T1_TYPE float #define CAPA_INVERTER_DCPOWER_T2_NAME "DC Power Tracker 2" #define CAPA_INVERTER_DCPOWER_T2_TYPE float #define CAPA_INVERTER_DCPOWER_T3_NAME "DC Power Tracker 3" #define CAPA_INVERTER_DCPOWER_T3_TYPE float /** AC Grid Voltage * * For single-phase inverters: Grid voltage of the (single) phase. * * Type: Float * * Optional. * * */ #define CAPA_INVERTER_GRID_AC_VOLTAGE_NAME "AC grid voltage (V)" #define CAPA_INVERTER_GRID_AC_VOLTAGE_TYPE float #define CAPA_INVERTER_GRID_AC_VOLTAGE_PHASE2_NAME "AC grid L2 voltage (V)" #define CAPA_INVERTER_GRID_AC_VOLTAGE_PHASE2_TYPE float #define CAPA_INVERTER_GRID_AC_VOLTAGE_PHASE3_NAME "AC grid L3 voltage (V)" #define CAPA_INVERTER_GRID_AC_VOLTAGE_PHASE3_TYPE float /** AC Grid Current * * For single-phase inverters: Grid voltage of the (single) phase. * * Type: Float * * Optional. * * */ #define CAPA_INVERTER_GRID_AC_CURRENT_NAME "AC grid feeding current (A)" #define CAPA_INVERTER_GRID_AC_CURRENT_TYPE float #define CAPA_INVERTER_GRID_AC_CURRENT_PHASE2_NAME "AC grid L2 feeding current (A)" #define CAPA_INVERTER_GRID_AC_CURRENT_PHASE2_TYPE float #define CAPA_INVERTER_GRID_AC_CURRENT_PHASE3_NAME "AC grid L3 feeding current (A)" #define CAPA_INVERTER_GRID_AC_CURRENT_PHASE3_TYPE float /** Inverter internal temperature * * if supported, this shows the temp of the inverter. * (and one some models, if the fan is on.) * * (Note: If you prefer °F, you can program a filter to * transform it...) * * Type: Float * * Optional. * * */ #define CAPA_INVERTER_TEMPERATURE_NAME "Inverter Temperature (C)" #define CAPA_INVERTER_TEMPERATURE_TYPE float #define CAPA_INVERTER_TEMPERATURE_PHASE2_NAME "Inverter Temperature 2 (C)" #define CAPA_INVERTER_TEMPERATURE_PHASE2_TYPE float #define CAPA_INVERTER_TEMPERATURE_PHASE3_NAME "Inverter Temperature 3 (C)" #define CAPA_INVERTER_TEMPERATURE_PHASE3_TYPE float // IEE IEA IED /** Error Currents Ground Voltage * * Type: Float * * Optional. * * */ #define CAPA_INVERTER_ERROR_CURRENT_NAME "Error current in (mA)" #define CAPA_INVERTER_ERROR_CURRENT_TYPE float #define CAPA_INVERTER_DC_ERROR_CURRENT_NAME "DC Error current in (mA)" #define CAPA_INVERTER_DC_ERROR_CURRENT_TYPE float #define CAPA_INVERTER_AC_ERROR_CURRENT_NAME "AC Error current in (mA)" #define CAPA_INVERTER_AC_ERROR_CURRENT_TYPE float #define CAPA_INVERTER_GROUND_VOLTAGE_NAME "Voltage to Ground (V)" #define CAPA_INVERTER_GROUND_VOLTAGE_TYPE float /** Inverter Status Codes * */ enum InverterStatusCodes { /** offline -- the inverter is not responsing to queries (e.g night) That status will be set automatically by the Inverter-Class logic, if the capability has been registered and the connection is lost to the inverter. */ OFFLINE, /** status unavailable -- whatever reason. Could be that we just don't know that status code */ STATUS_UNAVAILABLE, /** warning -- a non-fatal situation, like "solar radiance too low" */ NOT_FEEDING_OK, /** not feeding -- because some external event prevents feeding (like grid power loss, frequency too low....) */ NOT_FEEDING_EXTEVENT, /**< error -- the inverter is inoperable, as it have deteced some error */ NOT_FEEDING_ERROR, /** warning -- user action required or limited operation (the inverter sensed a problem, but can operate, but maybe at a lower power settings) */ FEEDING_WARNING, /** operating -- basically green, but not in the optimun (yet) */ FEEDING, /** perfect -- everything fine. Operating in MPP*/ FEEDING_MPP, /** the inveter is feeding at its limit. * Note: If this is due a problem, use FEEDING_WARNING*/ FEEDING_MAXPOWER, }; /** Inverter Overall status * * for the values, see the enum InverterStatusCodes. */ #define CAPA_INVERTER_STATUS_NAME "Inverter Overall Status (int)" #define CAPA_INVERTER_STATUS_TYPE long /** Inverter Overall status -- human readable version * * This contains the status of the inverter, but parsed for humans. * This also should contain information, why the inverter is in that state, * if available. * * If available, one might give also the manufacturer's statuscode * * Examples: * NOT FEEDING -- Solar radiation insufficient * NOT FEEDING -- Inverter Starting up * WARNING -- FAN MALFUNCTION DETECTED. USER ATTENTION REQUIRED. LIMITED FEEDING! * FEEDING -- Searching MPP * FEEDING -- at MPP * FEEDING -- MAXIMUM POWER * */ #define CAPA_INVERTER_STATUS_READABLE_NAME "Inverter Overall Status" #define CAPA_INVERTER_STATUS_READABLE_TYPE std::string // Filter "CSVDumper" provides the current logging filename in this value. // Present only if CSV Dumper is in the chain. // Empty, if the file could not be opened. #define CAPA_CSVDUMPER_FILENAME "CSVDumper::Filename" #define CAPA_CSVDUMPER_FILENAME_TYPE std::string // Filer "CSVDumper" logges these capabilites. // Note: This lis might change over runtime, as only in "log everything" mode // the CSV is expanded dynamically. Be prepared that might be not enough data // in your CSV! // The Capabilites are "Comma seperated", with no blank in between. #define CAPA_CSVDUMPER_LOGGEDCAPABILITES "CSVDumper::LoggedCaps" #define CAPA_CSVDUMPER_LOGGEDCAPABILITES_TYPE std::string #endif /* CAPABILITES_H_ */ solarpowerlog-solarpowerlog-0.26/src/Inverters/DummyInverter/000077500000000000000000000000001444065341000246205ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Inverters/DummyInverter/CInverterDummy.cpp000066400000000000000000000051701444065341000302440ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2011-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /* * CInverterDummy.cpp * * Created on: 17.07.2011 * Author: coldtobi */ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif #ifdef HAVE_INV_DUMMY #include "Inverters/DummyInverter/CInverterDummy.h" #include "configuration/CConfigHelper.h" #include "Inverters/Capabilites.h" #include "patterns/IValue.h" #include "interfaces/CCapability.h" #include "patterns/CValue.h" #include "patterns/ICommand.h" CInverterDummy::CInverterDummy(const string &name, const string &configurationpath) : IInverterBase(name, configurationpath, "inverter") { CConfigHelper cfghlp(configurationpath); std::string s; // Complete your initializtion here. // For example, add capabilities: IValue *v; CCapability *c; s = CAPA_INVERTER_MANUFACTOR_NAME; v = CValueFactory::Factory(); ((CValue*) v)->Set("Dummy Inverter"); c = new CCapability(s, v, this); AddCapability(c); // add a bootstrap event to get called again ;-) ICommand *cmd = new ICommand(CMD_INIT, this); Registry::GetMainScheduler()->ScheduleWork(cmd); LOGDEBUG(logger,"Inverter configuration:"); LOGDEBUG(logger,"class CInverterDummy "); cfghlp.GetConfig("comms", s, (string) "unset"); LOGDEBUG(logger,"Communication: " << s); } CInverterDummy::~CInverterDummy() { // TODO Auto-generated destructor stub } void CInverterDummy::ExecuteCommand(const ICommand *Command) { // ICommandTarget hook -- will be called with any new command which is due.. LOGINFO(this->logger, "CInverterDummy " << this->name << " command received! GetCmd:" << Command->getCmd()); LOGINFO(this->logger, "Data:"); Command->DumpData(this->logger); // Probably you want to have a big switch-case here, covering all your CMD_xxx } #endif /* HAVE_INV_DUMMY */ solarpowerlog-solarpowerlog-0.26/src/Inverters/DummyInverter/CInverterDummy.h000066400000000000000000000031601444065341000277060ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2011-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /* * CInverterDummy.h * * Created on: 17.07.2011 * Author: tobi */ #ifndef CINVERTERDUMMY_H_ #define CINVERTERDUMMY_H_ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif #ifdef HAVE_INV_DUMMY #include "Inverters/interfaces/InverterBase.h" #include "Inverters/BasicCommands.h" class CInverterDummy: public IInverterBase { public: CInverterDummy(const string &name, const string & configurationpath); virtual ~CInverterDummy(); virtual bool CheckConfig() { return this->connection->CheckConfig(); } virtual void ExecuteCommand(const ICommand *Command); private: enum CMDs { CMD_INIT = BasicCommands::CMD_USER_MIN }; }; #endif /* HAVE_INV_DUMMY */ #endif /* CINVERTERDUMMY_H_ */ solarpowerlog-solarpowerlog-0.26/src/Inverters/DummyInverter/CInverterFactoryDummy.cpp000066400000000000000000000033341444065341000315740ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2011-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /* * CInverterFactoryDummy.cpp * * Created on: 17.07.2011 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif #ifdef HAVE_INV_DUMMY #include "Inverters/DummyInverter/CInverterFactoryDummy.h" #include "Inverters/DummyInverter/CInverterDummy.h" static const std::string supported_models = "Dummy-Inverter: accepts any model"; CInverterFactoryDummy::CInverterFactoryDummy() { } CInverterFactoryDummy::~CInverterFactoryDummy() { } IInverterBase *CInverterFactoryDummy::Factory(const string &, const string & name, const string & configurationpath) { // As this is a dummy, we are not picky and return a object on any model... return new CInverterDummy(name, configurationpath); } const string & CInverterFactoryDummy::GetSupportedModels() const { return supported_models; } #endif /* HAVE_INV_DUMMY */ solarpowerlog-solarpowerlog-0.26/src/Inverters/DummyInverter/CInverterFactoryDummy.h000066400000000000000000000031371444065341000312420ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2011-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /* * CInverterFactoryDummy.h * * This is the factory for the dummy Inverter. * * Created on: 17.07.2011 * Author: coldtobi */ #ifndef CINVERTERFACTORYDUMMY_H_ #define CINVERTERFACTORYDUMMY_H_ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif #ifdef HAVE_INV_DUMMY #include "Inverters/factories/IInverterFactory.h" class CInverterFactoryDummy: public IInverterFactory { public: CInverterFactoryDummy(); virtual ~CInverterFactoryDummy(); virtual IInverterBase * Factory(const string& type, const string& name, const string & configurationpath); virtual const string & GetSupportedModels() const; }; #endif /* HAVE_INV_DUMMY */ #endif /* CINVERTERFACTORYDUMMY_H_ */ solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/000077500000000000000000000000001444065341000256165ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/CInverterFactorySputnik.cpp000066400000000000000000000046361444065341000331420ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file CInverterFactorySputnik.cpp * * Created on: May 20, 2009 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif #include "Inverters/SputnikEngineering/CInverterFactorySputnik.h" #include "Inverters/SputnikEngineering/CInverterSputnikSSeries.h" #include "CInverterSputnikSSeriesSimulator.h" using namespace std; #if defined HAVE_INV_SPUTNIK || defined HAVE_INV_SPUTNIKSIMULATOR static const string supportedmodels = #if defined HAVE_INV_SPUTNIK "S-Series: \tModels 2000S, 3000S, 4200S, 6000S and similar\n " #endif #if defined HAVE_INV_SPUTNIKSIMULATOR "Simulator:\tModels a S-Series Inverter\n"; #else ; #endif CInverterFactorySputnik::CInverterFactorySputnik() { // TODO Auto-generated constructor stub } /** Instanciates the right inverter class */ IInverterBase *CInverterFactorySputnik::Factory(const string & type, const string& name, const string & configurationpath) { #if defined HAVE_INV_SPUTNIK if (type == "S-Series") { return new CInverterSputnikSSeries(name, configurationpath); } #endif #if defined HAVE_INV_SPUTNIKSIMULATOR if (type == "Simulator") { return new CInverterSputnikSSeriesSimulator(name,configurationpath); } #endif return NULL; } CInverterFactorySputnik::~CInverterFactorySputnik() { // TODO Auto-generated destructor stub } /** Return a string describing the available models for this factory. * This should be inforamative for the user. */ const string & CInverterFactorySputnik::GetSupportedModels() const { return supportedmodels; } #endif solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/CInverterFactorySputnik.h000066400000000000000000000034241444065341000326010ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file CInverterFactorySputnik.h * * Created on: May 20, 2009 * Author: tobi */ #ifndef CINVERTERFACTORYSPUTNIK_H_ #define CINVERTERFACTORYSPUTNIK_H_ /** \fixme Factory for the Sputnik inverters * * Creates the object handling the inverters for the manufacturer Sputnik * Engineering. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #if defined HAVE_INV_SPUTNIK || defined HAVE_INV_SPUTNIKSIMULATOR #include "Inverters/factories/IInverterFactory.h" using namespace std; /** Factory class for sputnik inverters. */ class CInverterFactorySputnik: public IInverterFactory { virtual IInverterBase * Factory(const string& type, const string& name, const string & configurationpath); virtual const string & GetSupportedModels() const; public: CInverterFactorySputnik(); virtual ~CInverterFactorySputnik(); }; #endif #endif /* CINVERTERFACTORYSPUTNIK_H_ */ solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/CInverterSputnikSSeries.cpp000066400000000000000000001053161444065341000331050ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2015 Tobias Frost 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 . ---------------------------------------------------------------------------- */ // HOW TO HANLDE MULTI-PHASE UNITS // not implemented, but some ideas: // -- for all parameters, which are dependend on the phase, // create extra parameters which the appendix _L[1-3] // -- create a filter, a "multi-phase-data-splitter", and connect it // to the inverter. // -- it will search all capabilities for the phase variants and, if found, // it will locally "rename" the capability for the following filters and displays. // --> The phases will be transformed to new data source, like a virtual inverter. /** \file CInverterSputnikSSeries.cpp * * Created on: May 21, 2009 * * Author: Tobias Frost * * Contributors: * E.A.Neonakis */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_INV_SPUTNIK #include "CInverterSputnikSSeries.h" #include "configuration/Registry.h" #include "configuration/CConfigHelper.h" #include "configuration/ConfigCentral/CConfigCentral.h" #include "patterns/ICommand.h" #include "interfaces/CWorkScheduler.h" #include "Inverters/Capabilites.h" #include "patterns/CValue.h" #include "configuration/ILogger.h" #include "Inverters/SputnikEngineering/SputnikCommand/CSputnikCommand.h" #include "Inverters/SputnikEngineering/SputnikCommand/CSputnikCommandSoftwareVersion.h" #include "Inverters/SputnikEngineering/SputnikCommand/CSputnikCommandSYS.h" #include "Inverters/SputnikEngineering/SputnikCommand/CSputnikCommandTYP.h" #include "Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/CSputnikCmdBOOnce.h" #include "Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/CSputnikCmdBOTimed.h" #include "Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/CSputnikCmdBOIfSupported.h" #include std::string i_need_a_stdstring; #define DESCRIPTION_SPUTNIK_INTRO \ "The description of the following settings are valid when solparppowerlog configures for the S-Series inverters from Sputnik Engineering, " \ "so when \n" \ "manufacturer=\"SPUTNIK_ENGINEERING\";\n" \ "model=\"S-Series\";\n" \ "\n Hint: Many Sputnik Engineering inverters speak the same protocol, " \ " so it make sense to just give it a try and see if it works." #define DESCRIPTION_QUERYINTERVAL \ "This setting defines how often the inverter should be queried for new data, " \ "how long it should wait before issuing the next round of commands.\n" \ "The unit is seconds. \n" #define DESCRIPITON_COMMADR \ "Communication address of the inverter (as set in the communication menu of the inverter)" #define DESCRIPITON_OWNADR \ "Address to use as originating address for the communication. " \ "You should not change this value: The default value is designated for loggers." #define DESCRIPITON_RESPONSE_TIMEOUT \ "Time the inverter has to answer the query before the request times out.\n" \ "The unit is seconds." #define DESCRIPITON_CONNECTION_TIMEOUT \ "Time until a connection has to be established before timing out.\n" \ "The unit is seconds." #define DESCRIPITON_SEND_TIMEOUT \ "Time until a send request has to be finished before timing out.\n" \ "The unit is seconds." #define DESCRIPITON_RECONNECT_DELAY \ "Time waited, until a reconnection is attempted.\n The unit is seconds." #define DESCRIPTION_DISABLE_3PHASE_COMMANDS \ "Should queries dedicated for 3-phase-inverters be disabled. " \ "\"true\" disables them, \"false\" enables them. " #undef DEBUG_TOKENIZER // Debug: Print all received tokens #if defined DEBUG_TOKENIZER void DEBUG_tokenprinter(ILogger &logger, std::vector tokens) { int i; if (logger.IsEnabled(ILogger::LL_TRACE)) { std::stringstream ss; vector::iterator it; for (i = 0, it = tokens.begin(); it != tokens.end() - 1; it++) { ss << i++ << ": " << (*it) << "\tlen: " << (*it).length() << endl; } LOGTRACE(logger, ss); } } #endif CInverterSputnikSSeries::CInverterSputnikSSeries(const string &name, const string & configurationpath) : IInverterBase::IInverterBase(name, configurationpath, "inverter") { _cfg_ownadr = 0xfb; //< not needed, just to make compiler happy. (initialized by cnfig check) _cfg_commadr = 0x01; //< not needed, just to make compiler happy. (initialized by cnfig check) _shutdown_requested = false; // Add the capabilites that this inverter has // Note: The "must-have" ones CAPA_CAPAS_REMOVEALL and CAPA_CAPAS_UPDATED are already instanciated by the base class constructor. // Note2: You also can add capabilites as soon you know them (runtime detection) string s; IValue *v; CCapability *c; #warning remove this depreciated cruft (and spell manufacturer correctly) s = CAPA_INVERTER_MANUFACTOR_NAME; v = CValueFactory::Factory(); ((CValue*) v)->Set("Sputnik Engineering"); c = new CCapability(s, v, this); AddCapability(c); s = CAPA_INVERTER_MANUFACTURER_NAME; v = CValueFactory::Factory(); ((CValue*) v)->Set("Sputnik Engineering"); c = new CCapability(s, v, this); AddCapability(c); // Add the request to initialize as soon as the system is up. ICommand *cmd = new ICommand(CMD_INIT, this); Registry::GetMainScheduler()->ScheduleWork(cmd); #warning move most stuff here outside of constructor #warning for example post CheckConfig CConfigHelper cfghlp(configurationpath); float interval; cfghlp.GetConfig("queryinterval", interval, 5.0f); cfghlp.GetConfig("disable_3phase_commands",_cfg_disable_3phase,(bool) false); s = CAPA_INVERTER_QUERYINTERVAL; v = CValueFactory::Factory(); ((CValue*) v)->Set(interval); c = new CCapability(s, v, this); AddCapability(c); s = CAPA_INVERTER_CONFIGNAME; v = CValueFactory::Factory(); ((CValue*) v)->Set(name); c = new CCapability(s, v, this); AddCapability(c); // Initialize vector of supported commands. // Handles the "TYP" command, which will identifiy the model // and handles CAPA_INVERTER_MODEL. commands.push_back(new CSputnikCommandTYP(logger, this, new CSputnikCmdBOOnce)); // Gets SW version commands.push_back( new CSputnikCommandSoftwareVersion(logger, this, CAPA_INVERTER_FIRMWARE, new CSputnikCmdBOOnce)); // needs special handling this->commands.push_back(CSputnikCommand("EC*",27,0)); // AC Power commands.push_back( new CSputnikCommand(logger, "PAC", 9, 0.5, this, CAPA_INVERTER_ACPOWER_TOTAL)); // DC Power commands.push_back( new CSputnikCommand(logger, "PDC", 9, 0.5, this, CAPA_INVERTER_DCPOWER_TOTAL)); // On Time inverter in hours. boost::posix_time::time_duration time_between(boost::posix_time::hours(0)+boost::posix_time::minutes(1));; commands.push_back( new CSputnikCommand(logger, "KHR", 9, 1.0, this, CAPA_INVERTER_PON_HOURS, new CSputnikCmdBOTimed(time_between))); // Number of startups -- only once per connection. commands.push_back( new CSputnikCommand(logger, "CAC", 9, 1.0, this, CAPA_INVERTER_STARTUPS, new CSputnikCmdBOOnce)); // not implemented this->commands.push_back(CSputnikCommand("DYR",7,0)); // not implemented this->commands.push_back(CSputnikCommand("DDY",7,0)); // not implemented this->commands.push_back(CSputnikCommand("DMT",7,0)); // Number of kwH this year. // as the unit is one kWh, we can reduce the query time to e.g two times a minute // (which would need 120kW feeding power ;-) time_between = boost::posix_time::seconds(30); commands.push_back( new CSputnikCommand(logger, "KYR", 9, 1.0, this, CAPA_INVERTER_KWH_Y2D, new CSputnikCmdBOTimed(time_between))); // Number of kwH today. Same logic as for KYR, limiting to 2 times a minute. commands.push_back( new CSputnikCommand(logger, "KMT", 7, 1.0, this, CAPA_INVERTER_KWH_M2D, new CSputnikCmdBOTimed(time_between))); commands.push_back( new CSputnikCommand(logger, "KDY", 10, 0.1, this, CAPA_INVERTER_KWH_2D)); // kwH produced yesterday. // Only once a session. commands.push_back( new CSputnikCommand(logger, "KLD", 10, 0.1, this, CAPA_INVERTER_KWH_YD, new CSputnikCmdBOOnce)); // All-time kwH // also only 2 times a minute. commands.push_back( new CSputnikCommand(logger, "KT0", 10, 1.0, this, CAPA_INVERTER_KWH_TOTAL_NAME, new CSputnikCmdBOTimed(time_between))); // Installed power .. will not change over a session. commands.push_back( new CSputnikCommand(logger, "PIN", 9, 0.5, this, CAPA_INVERTER_INSTALLEDPOWER_NAME, new CSputnikCmdBOOnce)); commands.push_back( new CSputnikCommand(logger, "TNF", 10, 0.01, this, CAPA_INVERTER_NET_FREQUENCY_NAME)); commands.push_back( new CSputnikCommand(logger, "PRL", 10, 1.0, this, CAPA_INVERTER_RELPOWER_NAME)); commands.push_back( new CSputnikCommand(logger, "UDC", 10, 0.1, this, CAPA_INVERTER_INPUT_DC_VOLTAGE_NAME)); commands.push_back( new CSputnikCommand(logger, "UL1", 10, 0.1, this, CAPA_INVERTER_GRID_AC_VOLTAGE_NAME)); if (_cfg_disable_3phase) { // First, implement the "this command is not supported" scheme. commands.push_back( new CSputnikCommand(logger, "UL2", 10, 0.1, this, CAPA_INVERTER_GRID_AC_VOLTAGE_PHASE2_NAME, new CSputnikCmdBOIfSupported)); commands.push_back( new CSputnikCommand(logger, "UL3", 10, 0.1, this, CAPA_INVERTER_GRID_AC_VOLTAGE_PHASE3_NAME, new CSputnikCmdBOIfSupported)); } commands.push_back( new CSputnikCommand(logger, "IDC", 10, 0.01, this, CAPA_INVERTER_INPUT_DC_CURRENT_NAME)); commands.push_back( new CSputnikCommand(logger, "IL1", 10, 0.01, this, CAPA_INVERTER_GRID_AC_CURRENT_NAME)); if (_cfg_disable_3phase) { commands.push_back( new CSputnikCommand(logger, "IL2", 10, 0.01, this, CAPA_INVERTER_GRID_AC_CURRENT_PHASE2_NAME, new CSputnikCmdBOIfSupported)); commands.push_back( new CSputnikCommand(logger, "IL3", 10, 0.01, this, CAPA_INVERTER_GRID_AC_CURRENT_PHASE3_NAME, new CSputnikCmdBOIfSupported)); } commands.push_back( new CSputnikCommand(logger, "TKK", 10, 1.0, this, CAPA_INVERTER_TEMPERATURE_NAME)); if (_cfg_disable_3phase) { commands.push_back( new CSputnikCommand(logger, "TK2", 10, 1.0, this, CAPA_INVERTER_TEMPERATURE_PHASE2_NAME, new CSputnikCmdBOIfSupported)); commands.push_back( new CSputnikCommand(logger, "TK3", 10, 1.0, this, CAPA_INVERTER_TEMPERATURE_PHASE3_NAME, new CSputnikCmdBOIfSupported)); } // DC Tracker 1-3 voltage, current, power // For multi tracker inverters (MT Series) commands.push_back( new CSputnikCommand(logger, "UD01", 10, 0.1, this, CAPA_INVERTER_INPUT_DC_VOLTAGE_T1_NAME, new CSputnikCmdBOIfSupported)); commands.push_back( new CSputnikCommand(logger, "UD02", 10, 0.1, this, CAPA_INVERTER_INPUT_DC_VOLTAGE_T2_NAME, new CSputnikCmdBOIfSupported)); commands.push_back( new CSputnikCommand(logger, "UD03", 10, 0.1, this, CAPA_INVERTER_INPUT_DC_VOLTAGE_T3_NAME, new CSputnikCmdBOIfSupported)); commands.push_back( new CSputnikCommand(logger, "ID01", 10, 0.01, this, CAPA_INVERTER_INPUT_DC_CURRENT_T1_NAME)); commands.push_back( new CSputnikCommand(logger, "ID02", 10, 0.01, this, CAPA_INVERTER_INPUT_DC_CURRENT_T2_NAME, new CSputnikCmdBOIfSupported)); commands.push_back( new CSputnikCommand(logger, "ID03", 10, 0.01, this, CAPA_INVERTER_INPUT_DC_CURRENT_T3_NAME, new CSputnikCmdBOIfSupported)); commands.push_back( new CSputnikCommand(logger, "PD01", 9, 0.5, this, CAPA_INVERTER_DCPOWER_T1_NAME, new CSputnikCmdBOIfSupported)); commands.push_back( new CSputnikCommand(logger, "PD02", 9, 0.5, this, CAPA_INVERTER_DCPOWER_T2_NAME, new CSputnikCmdBOIfSupported)); commands.push_back( new CSputnikCommand(logger, "PD03", 9, 0.5, this, CAPA_INVERTER_DCPOWER_T3_NAME, new CSputnikCmdBOIfSupported)); // Get Inverter Timestamp / (Hour:Minute) // Should be s special implementation! // But the clock is rather inaccurate, so the computer's timestamp is // far more precice. // Note: It is possible to set the time/hour via the interface, but // not implemented // this->commands.push_back(CSputnikCommand("TMI",10,0)); // not implemented // this->commands.push_back(CSputnikCommand("THR",10,0)); // Handles the SYS Command, which handles the CAPA_INVERTER_STATUS_NAME // and CAPA_INVERTER_STATUS_READABLE_NAME capabilities. commands.push_back(new CSputnikCommandSYS(logger, this)); commands.push_back( new CSputnikCommand(logger, "IEE", 10, 0.1, this, CAPA_INVERTER_ERROR_CURRENT_NAME)); commands.push_back( new CSputnikCommand(logger, "IED", 10, 0.1, this, CAPA_INVERTER_DC_ERROR_CURRENT_NAME)); commands.push_back( new CSputnikCommand(logger, "IEA", 10, 0.1, this, CAPA_INVERTER_AC_ERROR_CURRENT_NAME)); commands.push_back( new CSputnikCommand(logger, "UGD", 10, 0.1, this, CAPA_INVERTER_GROUND_VOLTAGE_NAME)); // Register for broadcast events Registry::GetMainScheduler()->RegisterBroadcasts(this); } CInverterSputnikSSeries::~CInverterSputnikSSeries() { /* delete all commands allocated in the constructor. */ vector::iterator it; for (it=commands.begin(); it!=commands.end(); it++) { delete *it; *it= NULL; } commands.clear(); } bool CInverterSputnikSSeries::CheckConfig() { // new configcode... std::auto_ptr cfg(getConfigCentralObject(NULL)); bool cfgok = cfg->CheckConfig(logger, configurationpath); assert(connection); if (!connection->CheckConfig()) cfgok=false; LOGTRACE(logger, "Big Config Check for the new CConfigCentral"); LOGTRACE(logger, "result so far: " << cfgok); LOGTRACE(logger, "_cfg_queryinterval_s " << _cfg_queryinterval_s); LOGTRACE(logger, "_cfg_response_timeout_s " << _cfg_response_timeout_s); LOGTRACE(logger, "_cfg_connection_timeout_s " << _cfg_connection_timeout_s ); LOGTRACE(logger, "_cfg_send_timeout_s " << _cfg_send_timeout_s ); LOGTRACE(logger, "_cfg_reconnectdelay_s " << _cfg_reconnectdelay_s); LOGTRACE(logger, "_cfg_disable_3phase" << _cfg_disable_3phase); LOGTRACE(logger, "_cfg_commadr " << _cfg_commadr); LOGTRACE(logger, "_cfg_ownadr " << _cfg_ownadr); return cfgok; } /** Calculate the telegram checksum and return it. * * The sputnik protects it telegrams with a checksum. * The checksum is a sum of all bytes of a telegram, * starting after the { and ending at the | just before * the checksum. * * This routine assumes, that you give it the complete * telegram, and only the checksum and the } is missing. */ unsigned int CInverterSputnikSSeries::CalcChecksum(const char *str, int len) { unsigned int chksum = 0; str++; do { chksum += *str++; } while (--len); return chksum; } void CInverterSputnikSSeries::ExecuteCommand(const ICommand *Command) { string commstring = ""; string reccomm = ""; ICommand *cmd; timespec ts; switch ((Commands) Command->getCmd()) { case CMD_DISCONNECTED: { // DISCONNECTED: Error detected, the link to the com partner is down. // Action: Schedule connection retry in xxx seconds // Next-State: INIT (Try to connect) LOGDEBUG(logger, "new state: CMD_DISCONNECTED"); // Tell everyone that all data is now invalid. CCapability *c = GetConcreteCapability(CAPA_INVERTER_DATASTATE); CValue *v = (CValue *) c->getValue(); v->Set(false); c->Notify(); // reset the backoff algorithms for the commands. this->pendingcommands.clear(); this->notansweredcommands.clear(); vector::iterator it; for (it=this->commands.begin(); it!=commands.end(); it++) { (*it)->InverterDisconnected(); } cmd = new ICommand(CMD_DISCONNECTED_WAIT, this); if (connection->IsConnected()) { connection->Disconnect(cmd); } else { Registry::GetMainScheduler()->ScheduleWork(cmd); } break; } case CMD_DISCONNECTED_WAIT: { LOGDEBUG(logger, "new state: CMD_DISCONNECTED_WAIT"); cmd = new ICommand(CMD_INIT, this); timespec ts; float fraction, intpart; fraction = modf(_cfg_reconnectdelay_s, &intpart); ts.tv_sec = (long) intpart; ts.tv_nsec = (long) (fraction*1E9); Registry::GetMainScheduler()->ScheduleWork(cmd, ts); break; } case CMD_INIT: { LOGDEBUG(logger, "new state: CMD_INIT"); // initiate new connection only if no shutdown was requested. if (_shutdown_requested) break; // INIT: Try to connect to the comm partner // Action Connection Attempt // Next-State: Wait4Connection cmd = new ICommand(CMD_WAIT4CONNECTION, this); cmd->addData(ICONN_TOKEN_TIMEOUT, ((long)(_cfg_connection_timeout_s * 1000.0))); connection->Connect(cmd); break; // storage of objects in boost::any //cmd->addData("TEST", cmd); //cmd = boost::any_cast(cmd->findData("TEST")); } case CMD_WAIT4CONNECTION: { LOGDEBUG(logger, "new state: CMD_WAIT4CONNECTION"); int err = -1; // WAIT4CONNECTION: Wait until connection is up of failed to set up // by the communication object. // Action: Check success/error flag // Next-State: Depending on success: // success IDENTIFY_COMM // error DISCONNECTED try { err = boost::any_cast(Command->findData(ICMD_ERRNO)); } catch (...) { LOGDEBUG(logger,"CMD_WAIT4CONNECTION: unexpected exception"); err = -1; } if (err < 0) { try { LOGERROR(logger, "Error while connecting: (" << -err << ") " << boost::any_cast(Command->findData(ICMD_ERRNO_STR))); } catch (...) { LOGERROR(logger, "Unknown error while connecting."); } cmd = new ICommand(CMD_DISCONNECTED, this); Registry::GetMainScheduler()->ScheduleWork(cmd); } else { cmd = new ICommand(CMD_QUERY_POLL, this); Registry::GetMainScheduler()->ScheduleWork(cmd); } } break; case CMD_QUERY_POLL: { LOGDEBUG(logger, "new state: CMD_QUERY_POLL"); // Collect all queries to be issued. std::vector::iterator it; for (it=commands.begin(); it!= commands.end(); it++) { if ((*it)->ConsiderCommand()) { long hash = (long)(*it); ///use the pointer as hash LOGDEBUG_SA(logger, hash, "Considering Command " << (*it)->GetCommand() ); pendingcommands.push_back(*it); } else { long hash = (long)(*it); ///use the pointer as hash LOGDEBUG_SA(logger,hash," Command " << (*it)->GetCommand() << " not to be considered."); } } } // fall through intended. case CMD_SEND_QUERIES: { LOGDEBUG(logger, "new state: CMD_SEND_QUERIES"); commstring = assemblequerystring(); LOGTRACE(logger, "Sending: " << commstring << " Len: "<< commstring.size()); cmd = new ICommand(CMD_WAIT_SENT, this); // Start an atomic communication block (to hint any shared comms) cmd->addData(ICONN_ATOMIC_COMMS, ICONN_ATOMIC_COMMS_REQUEST); cmd->addData(ICONN_TOKEN_SEND_STRING, commstring); cmd->addData(ICONN_TOKEN_TIMEOUT,((long)(_cfg_send_timeout_s*1000.0))); connection->Send(cmd); } break; case CMD_WAIT_SENT: { LOGDEBUG(logger, "new state: CMD_WAIT_SENT"); int err; try { err = boost::any_cast(Command->findData(ICMD_ERRNO)); } catch (...) { LOGDEBUG(logger, "BUG: Unexpected exception."); err = -EINVAL; } if (err < 0) { try { LOGERROR( logger, "Error while sending: (" << -err << ") " << boost::any_cast(Command->findData(ICMD_ERRNO_STR))); } catch (...) { LOGERROR(logger, "Error while sending. (" << -err << ")"); } // Hint the shard comms to stop the atomic session // and then disconnect. cmd = new ICommand(CMD_DISCONNECTED, this); cmd->addData(ICONN_ATOMIC_COMMS, ICONN_ATOMIC_COMMS_CEASE); connection->Noop(cmd); break; } cmd = new ICommand(CMD_EVALUATE_RECEIVE, this); cmd->addData(ICONN_TOKEN_TIMEOUT, (long)(_cfg_response_timeout_s*1000.0)); // finish this atomic block (shared comms hinting) cmd->addData(ICONN_ATOMIC_COMMS, ICONN_ATOMIC_COMMS_CEASE); connection->Receive(cmd); } break; case CMD_EVALUATE_RECEIVE: { LOGDEBUG(logger, "new state: CMD_EVALUATE_RECEIVE"); int err; std::string s; try { err = boost::any_cast(Command->findData(ICMD_ERRNO)); } catch (...) { LOGDEBUG(logger, "BUG: Unexpected exception."); err = -EINVAL; } if (err < 0) { // we do not differentiate the error here, an error is an error.... // try to log the error message, if any. try { s = boost::any_cast(Command->findData( ICMD_ERRNO_STR)); LOGERROR(logger, "Receive Error: (" <<-err <<") "<< s); } catch (...) { LOGERROR(logger, "Receive Error: " << strerror(-err)); } } try { s = boost::any_cast(Command->findData( ICONN_TOKEN_RECEIVE_STRING)); } catch (...) { LOGERROR(logger, "Retrieving string: Unexpected Exception"); err = -EINVAL; } if (err < 0) { // Schedule new connection later. cmd = new ICommand(CMD_DISCONNECTED,this); Registry::GetMainScheduler()->ScheduleWork(cmd); break; } LOGTRACE(logger, "Received :" << s << " len: " << s.size()); if (logger.IsEnabled(ILogger::LL_TRACE)) { string st; char buf[32]; for (unsigned int i = 0; i < s.size(); i++) { sprintf(buf, "%02x", (unsigned char) s[i]); st = st + buf; if (i && i % 16 == 0) st = st + "\n"; else st = st + ' '; } LOGTRACE(logger, "Received in hex: "<< st ); } int parseresult = parsereceivedstring(s); // parseresult => // -1 on error, // 0 if the string indicated that it is not for us // 1 on success. // get the result from the last parse // yes, in the unlikely event that we parsed more than one telegram in one // session, we discard the result of the first one, and do not detect // an error here (but the last telegram for us needed to be successful)... if (1 != parseresult) { // Reconnect on parse errors. LOGERROR(logger, "Parse error on received string."); cmd = new ICommand(CMD_DISCONNECTED, this); Registry::GetMainScheduler()->ScheduleWork(cmd); break; } // all issued commands should have been answered, // those in the not-answered set, were un-answered and we notify // the commands to pass that information to their backoff algorithms. std::set::iterator it; for (it=notansweredcommands.begin();it!=notansweredcommands.end();it++) { (*it)->CommandNotAnswered(); } notansweredcommands.clear(); // if there are still pending commands, issue them first before // filling the queue again. if (!pendingcommands.empty()) { LOGTRACE(logger, "Querying remaining commands"); cmd = new ICommand(CMD_SEND_QUERIES, this); Registry::GetMainScheduler()->ScheduleWork(cmd); break; } // TODO differentiate between identify query and "normal" runtime queries CCapability *c = GetConcreteCapability(CAPA_INVERTER_DATASTATE); CValue *vb = (CValue *) c->getValue(); vb->Set(true); c->Notify(); c = GetConcreteCapability(CAPA_INVERTER_QUERYINTERVAL); CValue *v = (CValue *) c->getValue(); ts.tv_sec = v->Get(); ts.tv_nsec = ((v->Get() - ts.tv_sec) * 1e9); cmd = new ICommand(CMD_QUERY_POLL, this); Registry::GetMainScheduler()->ScheduleWork(cmd, ts); } break; // Broadcast events case CMD_BRC_SHUTDOWN: LOGDEBUG(logger, "new state: CMD_BRC_SHUTDOWN"); // stop all pending I/Os, as we will exit soon. connection->AbortAll(); _shutdown_requested = true; break; default: if (Command->getCmd() <= BasicCommands::CMD_BROADCAST_MAX) { // broadcast event LOGDEBUG(logger, "Unhandled broadcast event received " << Command->getCmd()); break; } LOGERROR(logger, "Unknown CMD received: "<< Command->getCmd()); break; } } string CInverterSputnikSSeries::assemblequerystring() { // ensure two things: // - telegram len does not exceed 255 bytes in total, // while there are 16 header bytes and 6 trailing bytes to be considered. // - answer is not exceeding 255 bytes // (here, we reserve a safety of 10 bytes additionally). int telegramlen = 254-22; int expectedanswerlen = 255-31; int currentport = QUERY; // At the moment only QUERY's are supported. std::string telegram; if (pendingcommands.empty()) return ""; // assemble string to send out of pending commands. // get the max amount of commands up to the max size // (we also ensure max answer len, as on observations fragmentation of // the telgramm does break it -- at least on my inverters' firmware. std::vector::iterator it = pendingcommands.begin(); while (it != pendingcommands.end()) { int alen = (*it)->GetMaxAnswerLen(); int clen = (*it)->GetCommandLen(); if ( alen < expectedanswerlen && clen < telegramlen ) { if (!telegram.empty()) { // Add seperator if this is not the first command in the string. telegram += ";"; telegramlen--; } telegram += (*it)->GetCommand(); telegramlen -= clen; expectedanswerlen -=alen; notansweredcommands.insert(*it); it = pendingcommands.erase(it); } else { it++; } } int len = 0; char buf[32]; snprintf(buf, 32,"%X:", currentport); len = strlen(buf) + telegram.length() + 10 + 6; snprintf(buf, 32, "{%02X;%02X;%02X|%X:", _cfg_ownadr, _cfg_commadr, len, currentport); // Insert header at beginning, add trailing "|" telegram.insert(0,buf); telegram.append("|"); snprintf(buf,32,"%04X}", CalcChecksum(telegram.c_str(), telegram.length())); telegram.append(buf); return telegram; } int CInverterSputnikSSeries::parsereceivedstring(std::string &rcvd) { unsigned int i; size_t pos; // extract telegram to get "{...}" only // ensure that we get a "{" as first character. pos = rcvd.find_last_of('{'); if (pos != 0 && (std::string::npos != pos)) { rcvd = rcvd.substr(pos); } // check if we got an complete telegram pos = rcvd.find('}'); if (pos == std::string::npos) { // no "}" seen return -1; } // both { and } found -- extract the telegramm... // pos still contains result from pos = part_received.find('}'); rcvd = rcvd.substr(0,pos+1); // check for basic constraints... // tokenizer (taken from // http://oopweb.com/CPP/Documents/CPPHOWTO/Volume/C++Programming-HOWTO-7.html // but modified. // we take the received string and "split" it into smaller pieces. // the pieces ("tokens") then assemble one single information to be // for this, we split by: // ";" "|" ":" (and "{}") vector tokens; { char delimiters[] = "{;|:}"; tokenizer(delimiters, rcvd, tokens); } // Debug: Print all received tokens #if defined DEBUG_TOKENIZER DEBUG_tokenprinter(this->logger, tokens); #endif // Minimum tokens are {;;|: .... |} - 5 tokens if (tokens.size() <= 5 ) return -1; unsigned int tmp; if (1 != sscanf(tokens.back().c_str(), "%x", &tmp)) { LOGDEBUG(logger, "could not parse checksum. Token was:"); return -1; } if (tmp != CalcChecksum(rcvd.c_str(), rcvd.length() - 6)) { LOGDEBUG(logger, "Checksum error on received telegram"); return -1; } if (1 != sscanf(tokens[0].c_str(), "%x", &tmp)) { LOGDEBUG(logger, " could not parse from address"); return -1; } if (tmp != _cfg_commadr) { LOGDEBUG(logger, "Received string is not for us: Wrong Sender"); return 0; } if (1 != sscanf(tokens[1].c_str(), "%x", &tmp)) { LOGDEBUG(logger, "could not parse to-address"); return -1; } if (tmp != _cfg_ownadr) { LOGDEBUG(logger, "Received string is not for us: Wrong receiver"); return 0; } if (1 != sscanf(tokens[2].c_str(), "%x", &tmp)) { LOGDEBUG(logger, "could not parse telegram length"); return -1; } if (tmp != rcvd.length()) { LOGDEBUG(logger, "wrong telegram length "); return -1; } { int ret = 1; const char delimiters[] = "=,"; for (i = 4; i < tokens.size() - 1; i++) { vector subtokens; tokenizer(delimiters, tokens[i], subtokens); if (subtokens.empty()) continue; vector::iterator it; for (it = commands.begin(); it != commands.end(); it++) { if ((*it)->IsHandled(subtokens[0])) { LOGTRACE(logger,"Now handling: " << tokens[i]); bool result = (*it)->handle_token(subtokens); if (!result) { LOGTRACE(logger,"failed parsing " + tokens[i]); ret = -1; } else { notansweredcommands.erase(*it); } break; } } } // we return either -1 (error) or 1 (all ok) return ret; } } void CInverterSputnikSSeries::tokenizer(const char *delimiters, const string& s, vector &tokens) { unsigned int i; string::size_type lastPos = 0; string::size_type pos = 0; i = 0; // Skip tokens at the start of the string do { if (s[lastPos] == delimiters[i]) { lastPos++; i = 0; } } while (++i < strlen(delimiters)); pos = lastPos; // get the first substring by finding the "second" delimiter i = lastPos; do { unsigned int tmp; tmp = s.find_first_of(delimiters[i], lastPos); if (tmp < pos) pos = tmp; } while (++i < strlen(delimiters)); while (s.length() > pos && s.length() > lastPos) { unsigned int tmp, tmp2; if (pos - lastPos) { tokens.push_back(s.substr(lastPos, pos - lastPos)); } lastPos = pos; // Skip delimiters. i = 0; do { if (s[lastPos] == delimiters[i]) { lastPos++; i = 0; } } while (++i < strlen(delimiters)); // Find next "delimiter" i = 0; tmp2 = -1; do { tmp = s.find_first_of(delimiters[i], lastPos); if (tmp < tmp2) tmp2 = tmp; } while (++i < strlen(delimiters)); pos = tmp2; } // Check if we have an "end-token" (not seperated) if (lastPos != s.length()) { tokens.push_back(s.substr(lastPos, s.length() - lastPos)); } } CConfigCentral* CInverterSputnikSSeries::getConfigCentralObject(CConfigCentral *parent) { CConfigCentral *pcfg = IInverterBase::getConfigCentralObject(parent); if (!pcfg) { pcfg = new CConfigCentral; } assert(pcfg); CConfigCentral &cfg = *pcfg; /// needed fo CConfigCentral to "have an object". WE do not care about content. static std::string dummy; // those are for config-dumping only -- they are already interpretatd // before creating this object. cfg (NULL, IBASE_DESCRIPTION_INTRO) ("name", IBASE_DESCRIPTION_NAME, "\"Inverter_1\"") ("manufacturer", IBASE_DESCRIPTION_MANUFACTURER, "\"SPUTNIK_ENGINEERING\"") ("model", IBASE_DESCRIPTION_MODEL, "\"S-Series\"") ("comms", IBASE_DESCRIPTION_COMMS, dummy) ; cfg (NULL, DESCRIPTION_SPUTNIK_INTRO) ("queryinterval", DESCRIPTION_QUERYINTERVAL, _cfg_queryinterval_s, 5.0f, 0.0f, FLT_MAX) ("commadr", DESCRIPITON_COMMADR, _cfg_commadr, 0x01u, 0u , 255u) ("ownadr", DESCRIPITON_OWNADR, _cfg_ownadr, 0xfbu, 0u , 255u) ("response_timeout", DESCRIPITON_RESPONSE_TIMEOUT, _cfg_response_timeout_s, 3.0f, 0.0f, FLT_MAX) ("connection_timeout", DESCRIPITON_CONNECTION_TIMEOUT, _cfg_connection_timeout_s, 3.0f, 0.0f, FLT_MAX) ("send_timeout", DESCRIPITON_SEND_TIMEOUT, _cfg_send_timeout_s, 3.0f, 0.0f, FLT_MAX) ("reconnect_delay", DESCRIPITON_RECONNECT_DELAY, _cfg_reconnectdelay_s, 15.0f, 0.0f, FLT_MAX) ("disable_3phase_commands", DESCRIPTION_DISABLE_3PHASE_COMMANDS, _cfg_disable_3phase, false) ; return &cfg; } #endif solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/CInverterSputnikSSeries.h000066400000000000000000000103521444065341000325450ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2015 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file CInverterSputnikSSeries.h * * Created on: May 21, 2009 * Author: Tobias Frost * * Contributors: * E.A.Neonakis */ #ifndef CINVERTERSPUTNIKSSERIES_H_ #define CINVERTERSPUTNIKSSERIES_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #if defined HAVE_INV_SPUTNIK #include "Inverters/interfaces/InverterBase.h" #include "Inverters/BasicCommands.h" #include "Inverters/SputnikEngineering/SputnikCommand/ISputnikCommand.h" #include /** \fixme Implements the Inverter Interface for the Sputnik S Series * * The Sputnik S-Series are an inverter family by Sputnik Engineering * Please see the manufacturer's homepage for details. */ class CInverterSputnikSSeries: public IInverterBase { public: CInverterSputnikSSeries(const string & name, const string & configurationpath); virtual ~CInverterSputnikSSeries(); virtual bool CheckConfig(); /** implements the ICommandTarget interface */ virtual void ExecuteCommand(const ICommand *Command); virtual CConfigCentral* getConfigCentralObject(CConfigCentral *parent); protected: /** calculate the checksum for the telegram stored in str */ static unsigned int CalcChecksum(const char* str, int len); private: /// Commands for the Workscheduler enum Commands { // broadcast event. CMD_BRC_SHUTDOWN = BasicCommands::CMD_BRC_SHUTDOWN, CMD_INIT = BasicCommands::CMD_USER_MIN, CMD_WAIT4CONNECTION, CMD_IDENTFY_WAIT, CMD_POLL, CMD_DISCONNECTED, CMD_DISCONNECTED_WAIT, CMD_EVALUATE_RECEIVE, CMD_WAIT_SENT, CMD_SEND_QUERIES, CMD_QUERY_POLL }; /// Dataports of the sputnik inverters. enum Ports { QUERY = 100, COMMAND = 200, ALARM = 300, INTERFACE = 1000 }; /// Build up the communication string /// /// \returns the string created, or "" if nothing to do. string assemblequerystring(); /// parse the answer of the inverter. int parsereceivedstring(std::string &rcvd); /// helper for parsereceivedstring() bool parsetoken(string token); /// Adress to use as "our" adress for communication /// This can be set by the conffile and the parameter ownadr /// defaults to 0xFB /// unsigned int ownadr; void tokenizer(const char *delimiters, const string& s, vector &tokens); /// stores supported commands. vector commands; /// stores pending commmands. vector pendingcommands; /// stores not answered commands (by removing the ansewered ones) set notansweredcommands; /// set to true if the shutdown request has been received via broadcast /// event. bool _shutdown_requested; /// Configuration cache: queryinterval float _cfg_queryinterval_s; /// Configuration Cache: Timeout for telegramm, unit is ms float _cfg_response_timeout_s; /// Configuration Cache: Timeout to establish a connection, unit ms float _cfg_connection_timeout_s; /// Configuration Cache: Timeout to send a telegramm, unit ms float _cfg_send_timeout_s; /// Configuration Cache: Reconnect delay, unit s float _cfg_reconnectdelay_s; /** Configuration cache: Should we disable support for * 3-phase-specific commands? */ bool _cfg_disable_3phase; /// cache for inverters comm adr. unsigned int _cfg_commadr; /// cache for own adr unsigned int _cfg_ownadr; }; #endif #endif /* CINVERTERSPUTNIKSSERIES_H_ */ CInverterSputnikSSeriesSimulator.cpp000066400000000000000000001176511444065341000347330ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2012-2015 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file CInverterSputnikSSeriesSimulator.cpp * * Created on: June 07, 2012 * * Author: Tobias Frost * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_INV_SPUTNIKSIMULATOR #include "configuration/Registry.h" #include "configuration/CConfigHelper.h" #include "CInverterSputnikSSeriesSimulator.h" #include "patterns/ICommand.h" #include "interfaces/CWorkScheduler.h" #include "Inverters/Capabilites.h" #include "patterns/CValue.h" #include "configuration/ILogger.h" #include "Inverters/SputnikEngineering/SputnikCommand/CSputnikCommand.h" #include "Inverters/SputnikEngineering/SputnikCommand/CSputnikCommandSoftwareVersion.h" #include "Inverters/SputnikEngineering/SputnikCommand/CSputnikCommandSYS.h" #include "Inverters/SputnikEngineering/SputnikCommand/CSputnikCommandTYP.h" #include "Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/CSputnikCmdBOOnce.h" #include "Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/CSputnikCmdBOTimed.h" #include "Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/CSputnikCmdBOIfSupported.h" #include "Connections/factories/IConnectFactory.h" #include #include struct CInverterSputnikSSeriesSimulator::simulator_commands simcommands[] = { { "ADR", 1, new CValue(1), 0, NULL, false }, { "PAC", 0.5, new CValue(42), 0, NULL, false }, { "PDC", 0.5, new CValue(42), 0, NULL, false }, { "KHR", 1.0, new CValue(42), 0, NULL, false }, { "CAC", 1.0, new CValue(42), 0, NULL, false }, { "SYS", 1.0, new CValue(20004), 1, new CValue(0), false }, { "TYP", 1.0, new CValue(65534), 0, NULL, false }, { "BDN", 1.0, new CValue(24), 0, NULL, false }, { "SWV", 1.0, new CValue(1), 0, NULL, false }, { "KYR", 1.0, new CValue(42), 0, NULL, false }, { "KMT", 1.0, new CValue(42), 0, NULL, false }, { "KDY", 0.1, new CValue(42), 0, NULL, false }, { "KLD", 0.1, new CValue(42), 0, NULL, false }, { "KT0", 1.0, new CValue(42), 0, NULL, false }, { "PIN", 0.5, new CValue(42), 0, NULL, false }, { "TNF", 0.01, new CValue(50), 0, NULL, false }, { "PRL", 1.0, new CValue(42), 0, NULL, false }, { "PRL", 1.0, new CValue(42), 0, NULL, false }, { "UDC", 0.1, new CValue(200), 0, NULL, false }, { "UL1", 0.1, new CValue(230), 0, NULL, false }, { "UL2", 0.1, new CValue(200), 0, NULL, false }, { "UL3", 0.1, new CValue(230), 0, NULL, false }, { "IDC", 0.01, new CValue(2), 0, NULL, false }, { "IL1", 0.01, new CValue(2), 0, NULL, false }, { "IL2", 0.01, new CValue(2), 0, NULL, false }, { "IL3", 0.01, new CValue(2), 0, NULL, false }, { "TKK", 1.0, new CValue(42), 0, NULL, false }, { "TK2", 1.0, new CValue(42), 0, NULL, false }, { "TK3", 1.0, new CValue(42), 0, NULL, false }, { "IEE", 0.1, new CValue(1), 0, NULL, false }, { "IED", 0.1, new CValue(1), 0, NULL, false }, { "IEA", 0.1, new CValue(1), 0, NULL, false }, { "UGD", 0.1, new CValue(25), 0, NULL, false }, { "SAL", 1, new CValue(0), 0, NULL, false }, { "UD01", 0.1, new CValue(200), 0, NULL, false }, { "UD02", 0.1, new CValue(200), 0, NULL, false }, { "UD03", 0.1, new CValue(200), 0, NULL, false }, { "ID01", 0.01, new CValue(2), 0, NULL, false }, { "ID02", 0.01, new CValue(2), 0, NULL, false }, { "ID03", 0.01, new CValue(2), 0, NULL, false }, { "PD01", 0.5, new CValue(400), 0, NULL, false }, { "PD02", 0.5, new CValue(400), 0, NULL, false }, { "PD03", 0.5, new CValue(400), 0, NULL, false }, // Inverter Solarmax 20C uses those to signal stati... // (not implemented in the inverter class!) { "SE1", 1, new CValue(0), 0, NULL, false }, { "SE2", 1, new CValue(0), 0, NULL, false }, { "SPR", 1, new CValue(0), 0, NULL, false }, { "SCD", 1, new CValue(0), 0, NULL, false }, // some other commands to evaluate... Seems to be general settings // for the inverter / grid configuration. // take with care, values mostly guessed. // not implemented in the inverter. { "ULH", 0.1, new CValue(220), 0, NULL, false }, // Uac max { "ULL", 0.1, new CValue(240), 0, NULL, false }, // Uac min { "TNH", 0.01, new CValue(49.7), 0, NULL, false }, // freq max { "TNL", 0.01, new CValue(50.2), 0, NULL, false }, // freq min { "TND", 0.01, new CValue(0.1), 0, NULL, false }, // df/dt max { "ISL", 1, new CValue(0), 0, NULL, false }, // "ISL"anding detection { "IEM", .1, new CValue(.5), 0, NULL, false }, // max error current { "IAA", 0.01, new CValue(16), 0, NULL, false }, // max Iac mean { "ILM", 0.01, new CValue(17), 0, NULL, false }, // max Iac { "UMX", 0.1, new CValue(240), 0, NULL, false }, // 10 min Uax max. { "RSD", 1, new CValue(30), 0, NULL, false }, // Restart delay { "CYC", 1, new CValue(0), 0, NULL, false }, // ??? { "PWF", 1, new CValue(0), 0, NULL, false }, // ??? { "SYM", 1, new CValue(0), 0, NULL, false }, // Symmetry Settings? { "PAM", 1, new CValue(4000), 0, NULL, false }, // Maximum power to feed. { "PWM", 1, new CValue(25), 0, NULL, false }, // ??? some modulation { "PWT", 1, new CValue(26), 0, NULL, false }, // ??? some modulation { NULL , 0 , NULL, 0, NULL, false} }; /** helper to convert a string to a CValue * \param ivalue will be updated. * \param value stringvalue * \param scale applied to the decoded value, if sputnikmode=true * \param sputnikmode if true, expect hexvalue (raw value in telegramm). * \return false if string could not be converted. */ static bool converttovalue(IValue *ivalue, const std::string &value, float scale = 1.0, bool sputnikmode = false) { float tmp; if (!sputnikmode) { if (1 != sscanf(value.c_str(), "%f", &tmp)) return false; } else { long ltmp; if (1 != sscanf(value.c_str(), "%ld", <mp)) return false; tmp = ltmp * scale; } if (CValue::IsType(ivalue)) { ((CValue*)ivalue)->Set(tmp); } else if (CValue::IsType(ivalue)) { ((CValue*)ivalue)->Set(tmp + 0.5); } else if (CValue::IsType(ivalue)) { ((CValue*)ivalue)->Set(tmp + 0.5); } else if (CValue::IsType(ivalue)) { ((CValue*)ivalue)->Set(tmp > 0.5 ? true : false); } else { LOGERROR(Registry::GetMainLogger(), "converttovalue -- not implemented CValue"); return false; } //LOGTRACE(Registry::GetMainLogger(),"value now " << std::string(*ivalue)); return true; } /** modify the value a little bit * \param ivalue will be updated. */ static void modifyvalue(IValue *ivalue) { static boost::random::mt19937 rng; // allow changes from -20 to +20%, in 0.1% steps boost::random::uniform_int_distribution<> change(-200,200); float tmp = (float) change(rng) * 0.001; // only modify with a certain hardcoded probability. int shouldwe = change(rng) + 200; if (shouldwe < 350) return; cout << endl << "modifying with tmp=" << tmp << endl; if (CValue::IsType(ivalue)) { CValue &v = *((CValue*)ivalue); float tmp2 = v.Get(); tmp2 = tmp2 + tmp*tmp2; v.Set(tmp2); } else if (CValue::IsType(ivalue)) { CValue &v = *((CValue*)ivalue); float tmp2 = v.Get(); tmp2 = tmp2 + tmp*tmp2 + 0.5; v.Set(tmp2); } else if (CValue::IsType(ivalue)) { CValue &v = *((CValue*)ivalue); float tmp2 = v.Get(); tmp2 = tmp2 + tmp*tmp2 + 0.5; v.Set(tmp2); } else if (CValue::IsType(ivalue)) { ((CValue*)ivalue)->Set(tmp >= 0.0 ? true : false); } else { LOGERROR(Registry::GetMainLogger(), "converttovalue -- not implemented CValue"); } } /// helper to convert the values to suitable strings /// Implemented for float, long and int static std::string convert2sputnikhex(IValue *value, float scale) { char buf[32]; unsigned long tmp; if (CValue::IsType(value)) { float ftmp = ((CValue*)value)->Get() / scale + 0.5; tmp = (long)ftmp; } else if (CValue::IsType(value)) { float ftmp = ((CValue*)value)->Get() / scale + 0.5; tmp = (long)ftmp; } else if (CValue::IsType(value)) { float ftmp = ((CValue*)value)->Get() / scale + 0.5; tmp = (long)ftmp; } else if (CValue::IsType(value)) { bool btmp = ((CValue*)value)->Get(); tmp = (long)btmp; } else { LOGERROR(Registry::GetMainLogger(), "convert2sputnikhex -- not implemented CValue"); return ""; } snprintf(buf, sizeof(buf), "%lX", tmp); return buf; } CInverterSputnikSSeriesSimulator::CInverterSputnikSSeriesSimulator( const string &name, const string & configurationpath) : IInverterBase::IInverterBase(name, configurationpath, "inverter") { commadr = 0x01; _disconnect = false; _offline = false; _shutdown_requested = false; _isconnected = false; // will be initialized later. ctrlserver = NULL; _inject_chksum_err = false; _modify_values = false; // Add the capabilites that this inverter has // Note: The "must-have" ones CAPA_CAPAS_REMOVEALL and CAPA_CAPAS_UPDATED are already instanciated by the base class constructor. // Note2: You also can add capabilites as soon you know them (runtime detection) string s; IValue *v; CCapability *c; #warning remove this depreciated cruft (and spell correctly manufacturer) s = CAPA_INVERTER_MANUFACTOR_NAME; v = CValueFactory::Factory(); ((CValue*)v)->Set("Solarpowerlog"); c = new CCapability(s, v, this); AddCapability(c); s = CAPA_INVERTER_MANUFACTURER_NAME; v = CValueFactory::Factory(); ((CValue*) v)->Set("Sputnik Engineering"); c = new CCapability(s, v, this); AddCapability(c); // Add the request to initialize as soon as the system is up. ICommand *cmd = new ICommand(CMD_INIT, this); Registry::GetMainScheduler()->ScheduleWork(cmd); CConfigHelper cfghlp(configurationpath); // Query settings needed and default all optional settings. cfghlp.GetConfig("commadr", commadr, 0x01u); s = CAPA_INVERTER_CONFIGNAME; v = CValueFactory::Factory(); ((CValue*)v)->Set(name); c = new CCapability(s, v, this); AddCapability(c); LOGDEBUG(logger, "Inverter configuration:"); LOGDEBUG(logger, "class CInverterSputnikSSeriesSimulator "); LOGDEBUG(logger, "Commadr: " << commadr); cfghlp.GetConfig("comms", s, (string)"unset"); LOGDEBUG(logger, "Communication: " << s); this->scommands = new struct simulator_commands[sizeof(simcommands) / sizeof(struct simulator_commands)]; // copy the lookup table to have an working copy available (to be able to // modify the values with the control server) int i = 0; do { scommands[i].token = simcommands[i].token; scommands[i].scale1 = simcommands[i].scale1; scommands[i].scale2 = simcommands[i].scale2; scommands[i].value = NULL; scommands[i].value2 = NULL; if (simcommands[i].value) scommands[i].value = simcommands[i].value ->clone(); if (simcommands[i].value2) scommands[i].value2 = simcommands[i].value2 ->clone(); scommands[i].killbit = false; } while (simcommands[i++].token); // Register for broadcast events Registry::GetMainScheduler()->RegisterBroadcasts(this); } CInverterSputnikSSeriesSimulator::~CInverterSputnikSSeriesSimulator() { int i = 0; do { if (scommands[i].value) delete scommands[i].value; if (scommands[i].value2) delete scommands[i].value2; } while (scommands[++i].token); delete[] scommands; if (ctrlserver) delete ctrlserver; } bool CInverterSputnikSSeriesSimulator::CheckConfig() { string setting; string str; bool fail = false; CConfigHelper hlp(configurationpath); fail |= (true != hlp.CheckConfig("comms", libconfig::Setting::TypeString)); // Note: Queryinterval is optional. But CConfigHelper handle also opt. // parameters and checks for type. fail |= (true != hlp.CheckConfig("queryinterval", libconfig::Setting::TypeFloat, true)); fail |= (true != hlp.CheckConfig("commadr", libconfig::Setting::TypeInt)); // Check config of the connection component fail |= (true != connection->CheckConfig()); if (!connection->CanAccept()) { LOGFATAL(logger, "Configuration Error: Communication method supports not " "server-mode or is not configured accordingly."); fail = true; } std::string ctrl_cfg = configurationpath + ".ctrl_comms"; CConfigHelper ch(ctrl_cfg); if (ch.GetConfig("comms", setting)) { ctrlserver = IConnectFactory::Factory(ctrl_cfg); ctrlserver->SetupLogger(configurationpath, "ctrl-server"); if (!ctrlserver->CheckConfig()) { LOGFATAL(logger, "Ctrl-Server communication method configuration error"); return false; } if (!ctrlserver->CanAccept()) { LOGFATAL(logger, "Ctrl-Server communication method cannot act as a server"); return false; } } else { LOGINFO(logger, "Simulator: control server disabled as config not found."); } int type; int cadr; hlp.GetConfig("TYP", type, -1); hlp.GetConfig("commadr", cadr); if (-1 != type) { int i; for (i = 0; scommands[i].token; i++) { if (0 == strcmp(scommands[i].token, "TYP")) { CValue* v = (CValue*)scommands[i].value; v->Set(type); } else if (0 == strcmp(scommands[i].token, "ADR")) { CValue* v = (CValue*)scommands[i].value; v->Set(cadr); } } } LOGTRACE(logger, "Check Configuration result: " << !fail); return !fail; } /** Calculate the telegram checksum and return it. * * The sputnik protects it telegrams with a checksum. * The checksum is a sum of all bytes of a telegram, * starting after the { and ending at the | just before * the checksum. * * This routine assumes, that you give it the complete * telegram, and only the checksum and the } is missing. */ unsigned int CInverterSputnikSSeriesSimulator::CalcChecksum(const char *str, int len) { unsigned int chksum = 0; str++; do { chksum += *str++; } while (--len); return chksum; } void CInverterSputnikSSeriesSimulator::ExecuteCommand(const ICommand *Command) { string commstring = ""; string reccomm = ""; ICommand *cmd; switch ((Commands)Command->getCmd()) { case CMD_INIT: { LOGDEBUG(logger, "new state: CMD_INIT"); cmd = new ICommand(CMD_SIM_INIT, this); Registry::GetMainScheduler()->ScheduleWork(cmd); if (ctrlserver) { cmd = new ICommand(CMD_CTRL_INIT, this); Registry::GetMainScheduler()->ScheduleWork(cmd); } break; } case CMD_SIM_INIT: // Wait for incoming connections. { // only if no shutdown have been requested. if (_shutdown_requested) break; LOGDEBUG(logger, "new state: CMD_SIM_INIT"); if (connection->IsConnected()) { cmd = new ICommand(CMD_SIM_WAITDISCONNECT, this); connection->Disconnect(cmd); _isconnected = false; break; } } // fall-through ok case CMD_SIM_DISCONNECTED: LOGDEBUG(logger, "new state: CMD_SIM_DISCONNECTED"); LOGINFO(logger, "Disconnecting because we are in disconnected mode"); // we will wait here until we were allowed to connect again. // in this case the ctrl server will issue a work with // CMD_SIM_DISCONNECTED as target and let fall through here. // avoid race with ctrl server. if (_disconnect) break; // if we are still connected, do not accept -- otherwise we are // "double" accepting. if (_isconnected) break; case CMD_SIM_WAITDISCONNECT: { LOGDEBUG(logger, "new state: CMD_SIM_WAITDISCONNECT"); if (!_disconnect) { cmd = new ICommand(CMD_SIM_CONNECTED, this); connection->Accept(cmd); break; } // else wait here until reactivation. LOGDEBUG(logger, "waiting for _disconnect getting false"); break; } case CMD_SIM_CONNECTED: // Wait for { LOGDEBUG(logger, "new state: CMD_SIM_CONNECTED"); int err = -1; // CMD_CONNECTED: Accept succeeded of failure. try { err = boost::any_cast(Command->findData(ICMD_ERRNO)); } catch (...) { LOGDEBUG(logger, "CMD_SIM_CONNECTED: unexpected exception while " "trying to get the errorcode."); err = -1; } if (err < 0) { try { LOGERROR(logger, "Error while connecting: " << boost::any_cast(Command->findData(ICMD_ERRNO_STR))); } catch (...) { LOGERROR(logger, "Unknown error " << err << " while connecting."); } cmd = new ICommand(CMD_SIM_INIT, this); Registry::GetMainScheduler()->ScheduleWork(cmd); break; } if (!_disconnect) { _isconnected = true; cmd = new ICommand(CMD_SIM_EVALUATE_RECEIVE, this); cmd->addData(ICONN_TOKEN_TIMEOUT, (long)3600 * 1000); connection->Receive(cmd); LOGINFO(logger, "Simulator connected."); } else { // tear down connection again after we've got instructed to // be in disconnected mode. connection->Disconnect( new ICommand(CMD_SIM_DISCONNECTED, this)); LOGINFO(logger, "Simulator connected, but we shall disconnect."); } } break; case CMD_SIM_EVALUATE_RECEIVE: { LOGDEBUG(logger, "new state: CMD_SIM_EVALUATE_RECEIVE"); int err; std::string s; try { err = boost::any_cast(Command->findData(ICMD_ERRNO)); } catch (...) { LOGDEBUG(logger, "BUG: Unexpected exception."); err = -1; } if (err < 0) { // we do not differentiate between errors. cmd = new ICommand(CMD_SIM_INIT, this); Registry::GetMainScheduler()->ScheduleWork(cmd); try { s = boost::any_cast( Command->findData(ICMD_ERRNO_STR)); LOGERROR(logger, "Receive Error: " << s); } catch (...) { LOGERROR(logger, "Receive Error: " << strerror(-err)); } } try { s = boost::any_cast( Command->findData(ICONN_TOKEN_RECEIVE_STRING)); } catch (...) { LOGDEBUG(logger, "Unexpected Exception"); err = -EINVAL; break; } if (err < 0) { cmd = new ICommand(CMD_SIM_INIT, this); Registry::GetMainScheduler()->ScheduleWork(cmd); break; } LOGTRACE(logger, "Received: " << s << " len: " << s.size()); if (logger.IsEnabled(ILogger::LL_TRACE)) { string st; char buf[32]; for (unsigned int i = 0; i < s.size(); i++) { sprintf(buf, "%02x", (unsigned char)s[i]); st = st + buf; if (i && i % 16 == 0) st = st + "\n"; else st = st + ' '; } LOGTRACE(logger, "Received in hex: "<< st); } if (!_offline) { // make answer and send it. s = parsereceivedstring(s); } else { s.clear(); LOGINFO(logger, "Not answering because we are in offline mode"); } if (_disconnect) { // ctrl server tells not to connect... so we disconnect now and do not // send the answer cmd = new ICommand(CMD_SIM_DISCONNECTED, this); connection->Disconnect(cmd); _isconnected = false; } // only response if parsing was successful. if (s.size()) { LOGTRACE(logger, "Response :" << s << " len: " << s.size()); cmd = new ICommand(CMD_SIM_WAIT_SENT, this); cmd->addData(ICONN_TOKEN_TIMEOUT, (long)3000); cmd->addData(ICONN_TOKEN_SEND_STRING, s); connection->Send(cmd); } else { cmd = new ICommand(CMD_SIM_EVALUATE_RECEIVE, this); cmd->addData(ICONN_TOKEN_TIMEOUT, (long)3600 * 1000); connection->Receive(cmd); } } break; case CMD_SIM_WAIT_SENT: { LOGDEBUG(logger, "new state: CMD_SIM_WAIT_SENT"); int err; try { err = boost::any_cast(Command->findData(ICMD_ERRNO)); } catch (...) { LOGDEBUG(logger, "BUG: Unexpected exception."); err = -1; } if (err < 0) { LOGERROR(logger, "Error while sending"); cmd = new ICommand(CMD_SIM_INIT, this); Registry::GetMainScheduler()->ScheduleWork(cmd); break; } cmd = new ICommand(CMD_SIM_EVALUATE_RECEIVE, this); cmd->addData(ICONN_TOKEN_TIMEOUT, (long)3600 * 1000); connection->Receive(cmd); break; } // ##### CONTROL SEVER ##### case CMD_CTRL_INIT: { // only if no shutdown have been requested. if (_shutdown_requested) break; // the commandserver is known to be non-NULL, otherwise CMD_INIT // would not have scheduled this work. LOGDEBUG(logger, "new state: CMD_CTRL_INIT"); LOGINFO(logger, "Sputnik-Simulator control server ready."); if (ctrlserver->IsConnected()) { cmd = new ICommand(CMD_CTRL_WAITDISCONNECT, this); ctrlserver->Disconnect(cmd); break; } } // fall through ok case CMD_CTRL_WAITDISCONNECT: { cmd = new ICommand(CMD_CTRL_CONNECTED, this); ctrlserver->Accept(cmd); break; } case CMD_CTRL_CONNECTED: { LOGDEBUG(logger, "new state: CMD_CTRL_CONNECTED"); LOGINFO(logger, "Ctrl-Server connected"); int err = -1; try { err = boost::any_cast(Command->findData(ICMD_ERRNO)); } catch (...) { LOGDEBUG(logger, "CMD_CTRL_CONNECTED: unexpected exception while " "trying to get the errorcode."); err = -1; } if (err < 0) { try { LOGERROR(logger, "Error while connecting (ctrl-server): " << boost::any_cast(Command->findData(ICMD_ERRNO_STR))); } catch (...) { LOGERROR(logger, "Unknown error " << err << " while connecting."); } cmd = new ICommand(CMD_CTRL_INIT, this); timespec ts; ts.tv_sec = 15; ts.tv_nsec = 0; Registry::GetMainScheduler()->ScheduleWork(cmd, ts); break; } else { cmd = new ICommand(CMD_CTRL_PARSERECEIVE, this); cmd->addData(ICONN_TOKEN_TIMEOUT, (long)3600 * 1000); ctrlserver->Receive(cmd); break; } break; } case CMD_CTRL_PARSERECEIVE: { LOGDEBUG(logger, "new state: CMD_CTRL_PARSERECEIVE"); int err; std::string s; try { err = boost::any_cast(Command->findData(ICMD_ERRNO)); } catch (...) { LOGDEBUG(logger, "BUG: Unexpected exception."); err = -1; } if (err < 0) { // we do not differentiate the error here, an error is an error.... cmd = new ICommand(CMD_CTRL_INIT, this); Registry::GetMainScheduler()->ScheduleWork(cmd); try { s = boost::any_cast( Command->findData(ICMD_ERRNO_STR)); LOGERROR(logger, "Receive Error (ctrl-server): " << s); } catch (...) { LOGERROR(logger, "Receive Error (ctrl-server): " << strerror(-err)); } break; } try { s = boost::any_cast( Command->findData(ICONN_TOKEN_RECEIVE_STRING)); } catch (...) { LOGDEBUG(logger, "Unexpected Exception"); break; } LOGTRACE(logger, "Received (ctrl-server): " << s << " len: " << s.size()); if (logger.IsEnabled(ILogger::LL_TRACE)) { string st; char buf[32]; for (unsigned int i = 0; i < s.size(); i++) { sprintf(buf, "%02x", (unsigned char)s[i]); st = st + buf; if (i && i % 16 == 0) st = st + "\n"; else st = st + ' '; } LOGTRACE(logger, "Received in hex: " << st); } bool disconnect_old = _disconnect; // make answer and send it. s = parsereceivedstring_ctrlserver(s); LOGTRACE(logger, "Response (ctrl-server):" << s << "len: " << s.size()); cmd = new ICommand(CMD_CTRL_WAIT_SENT, this); cmd->addData(ICONN_TOKEN_TIMEOUT, ((long)3000)); cmd->addData(ICONN_TOKEN_SEND_STRING, s); if (s == "BYE!\n") { cmd->setCmd(CMD_CTRL_INIT); } ctrlserver->Send(cmd); // detect if we re-enable the connection of the simulator if (disconnect_old && !_disconnect) { LOGDEBUG(logger, "Re-enabling connection"); Registry::GetMainScheduler()->ScheduleWork( new ICommand(CMD_SIM_DISCONNECTED, this)); } break; } case CMD_CTRL_WAIT_SENT: { LOGDEBUG(logger, "new state: CMD_CTRL_WAIT_SENT"); int err; try { err = boost::any_cast(Command->findData(ICMD_ERRNO)); } catch (...) { LOGDEBUG(logger, "BUG: Unexpected exception."); err = -1; } if (err < 0) { LOGERROR(logger, "Error while sending (ctrl-server)"); cmd = new ICommand(CMD_CTRL_INIT, this); Registry::GetMainScheduler()->ScheduleWork(cmd); break; } cmd = new ICommand(CMD_CTRL_PARSERECEIVE, this); cmd->addData(ICONN_TOKEN_TIMEOUT, (long)3600 * 1000); ctrlserver->Receive(cmd); break; } // Broadcast events case CMD_BRC_SHUTDOWN: // stop all pending I/Os, as we will exit soon. connection->AbortAll(); ctrlserver->AbortAll(); _shutdown_requested = true; break; default: if (Command->getCmd() <= BasicCommands::CMD_BROADCAST_MAX) { // broadcast event LOGDEBUG(logger, "Unhandled broadcast event received " << Command->getCmd()); break; } LOGERROR(logger, "Unknown CMD received: "<< Command->getCmd()); break; } } // parsing of the reception -- simulator. std::string CInverterSputnikSSeriesSimulator::parsereceivedstring( const string & s) { unsigned int i; unsigned int sender_adr; // check for basic constraints... if (s[0] != '{' || s[s.length() - 1] != '}') return ""; // tokenizer (taken from // http://oopweb.com/CPP/Documents/CPPHOWTO/Volume/C++Programming-HOWTO-7.html // but modified. // we take the received string and "split" it into smaller pieces. // the pieces ("tokens") then assemble one single information to be // for this, we split by: // ";" "|" ":" (and "{}") vector tokens; { char delimiters[] = "{;|:}"; tokenizer(delimiters, s, tokens); } // Minimum is {;;|: .... |} - 5 tokens if (tokens.size() <= 5) return ""; // Debug: Print all received tokens #if defined DEBUG_TOKENIZER DEBUG_tokenprinter(this->logger, tokens); #endif unsigned int tmp; if (1 != sscanf(tokens.back().c_str(), "%x", &tmp)) { LOGDEBUG(logger, "could not parse checksum. Token was:"); return ""; } if (tmp != CalcChecksum(s.c_str(), s.length() - 6)) { LOGDEBUG(logger, "Checksum error on received telegram"); return ""; } if (1 != sscanf(tokens[0].c_str(), "%x", &sender_adr)) { LOGDEBUG(logger, " could not parse from address"); return ""; } if (1 != sscanf(tokens[1].c_str(), "%x", &tmp)) { LOGDEBUG(logger, "could not parse to-address"); return ""; } if (tmp != commadr) { LOGDEBUG(logger, "Received string is not for us: Wrong receiver"); return ""; } if (1 != sscanf(tokens[2].c_str(), "%x", &tmp)) { LOGDEBUG(logger, "could not parse telegram length"); return ""; } if (tmp != s.length()) { LOGDEBUG(logger, "wrong telegram length "); return ""; } if (tokens[3] != "64") { LOGDEBUG(logger, "Simulator only handling port 100"); return ""; } std::string ret; std::string tmps; bool found; int j = 0; for (i = 4; i < tokens.size() - 1; i++) { // tokens[i] contains the commands to be answered. //LOGDEBUG(logger,"token=" << tokens[i]); found = false; for (j = 0; scommands[j].token; j++) { if (tokens[i] == scommands[j].token) { // check if command was disabled via the ctrl server if (scommands[j].killbit) { found = true; LOGINFO(logger, "Token " << tokens[i] << " disabled and ignored."); break; } if (scommands[j].value) { found = true; // LOGTRACE(logger, tokens[i] << " found"); tmps = convert2sputnikhex(scommands[j].value, scommands[j].scale1); if (_modify_values) { modifyvalue(scommands[j].value); } if (!tmps.empty()) { if (!ret.empty()) { ret += ";"; } ret += tokens[i] + "=" + tmps; } else { continue; } } if (scommands[j].value2) { ret += "," + convert2sputnikhex(scommands[j].value2, scommands[j].scale2); } break; // token handled. continue with the next } } // token not in the list if (!found) LOGINFO(logger, "Token " << tokens[i] << " unknown and not answered"); } // ret contains token answer, but without framing. // size of answer = header {12;12;12|64:|1234} // 1234567890123 456789 char buf[32]; unsigned int telsize = ret.length() + 19; snprintf(buf, sizeof(buf), "{%02X;%02X;%02X|64:", commadr, sender_adr, telsize); ret = buf + ret + "|"; unsigned int checksum = CalcChecksum(ret.c_str(), ret.length()); if (_inject_chksum_err) { LOGINFO(logger, "Injecting checksum error"); checksum = 0xdead; _inject_chksum_err = false; } snprintf(buf, sizeof(buf), "%04X}", checksum); ret += buf; return ret; } std::string CInverterSputnikSSeriesSimulator::parsereceivedstring_ctrlserver( std::string s) { bool sputnikmode = false; // this parser accepts two versions of input // - complete telegrams in sputnik format, // where all parts except the data is ignored // - = or =, // // as the complete telegramm is only a special case of the seconds one, // parsing is still straight forward. // strip off the sputnik protocol path, if existant. // from the front: {xx;xx;xx|64: // from the end: |xxxx} // remove whitespaces. boost::algorithm::trim(s); // special commmands .. those commands must be the only command supplied on // the the line. if (s == "offline") { _offline = true; LOGDEBUG(logger, "offline"); return ("DONE\n"); } else if (s == "online") { _offline = false; LOGDEBUG(logger, "online"); return ("DONE\n"); } if (s == "disconnect") { _disconnect = true; return ("DONE\n"); } else if (s == "connect") { _disconnect = false; return ("DONE\n"); } else if (s == "quit") { return ("BYE!\n"); } else if (s == "version") { return PACKAGE_STRING + std::string("\n"); } else if (s == "inject_chksum") { _inject_chksum_err = true; return ("DONE\n"); } else if (s == "modify_on") { _modify_values = true; return ("DONE\n"); } else if (s == "modify_off") { _modify_values = false; return ("DONE\n"); } size_t first = s.find_first_of(':'); if (first != std::string::npos && s.length() > first) { first++; } else { first = std::string::npos; } size_t last = s.find_last_of('|'); if (first != std::string::npos && last != std::string::npos) { s = s.substr(first, last - first); sputnikmode = true; } // now should have just the data portion. // token1=value1;=value2;token3=value3,value4 // so seperated by ";" vector tokens; tokenizer(";", s, tokens); s.clear(); // now go through the tokens. vector::iterator it; vector subtokens; for (it = tokens.begin(); it != tokens.end(); it++) { subtokens.clear(); boost::algorithm::trim(*it); tokenizer("=,", *it, subtokens); if (subtokens.size() < 2 || subtokens.size() > 3) { LOGERROR(logger, "ctrl-server parse: Parse error."); s += "ERR: Parse error on " + *it + ". "; continue; } int i = 0; for (i = 0; scommands[i].token; i++) { if (scommands[i].token == subtokens[0]) { bool ret1 = true, ret2 = true; LOGTRACE(logger, "parsing " << *it); if (boost::algorithm::to_lower_copy(subtokens[1]) == "off") { LOGTRACE(logger, "Disabing " << subtokens[0]); scommands[i].killbit = true; break; } if (boost::algorithm::to_lower_copy(subtokens[1]) == "on") { LOGTRACE(logger, "Enabling " << subtokens[0]); scommands[i].killbit = false; break; } if (scommands[i].killbit) { break; } if (scommands[i].value) { ret1 = converttovalue(scommands[i].value, subtokens[1], scommands[i].scale1, sputnikmode); } if (scommands[i].value2 && subtokens.size() == 3) { ret2 = converttovalue(scommands[i].value2, subtokens[2], scommands[i].scale2, sputnikmode); } if (!ret1 || !ret2) { LOGDEBUG(logger, "Parse error on token "<<*it); s += "ERR: Parse error on " + *it + ". "; } break; } } if (!scommands[i].token) { s += "ERR: " + *it + " not found.\n"; } } s = s + "DONE\n"; return s; } // tokenizer. void CInverterSputnikSSeriesSimulator::tokenizer(const char *delimiters, const string& s, vector &tokens) { unsigned int i; string::size_type lastPos = 0; string::size_type pos = 0; i = 0; // Skip tokens at the start of the string do { if (s[lastPos] == delimiters[i]) { lastPos++; i = 0; } } while (++i < strlen(delimiters)); pos = lastPos; // get the first substring by finding the "second" delimiter i = lastPos; do { unsigned int tmp; tmp = s.find_first_of(delimiters[i], lastPos); if (tmp < pos) pos = tmp; } while (++i < strlen(delimiters)); while (s.length() > pos && s.length() > lastPos) { unsigned int tmp, tmp2; if (pos - lastPos) { tokens.push_back(s.substr(lastPos, pos - lastPos)); } lastPos = pos; // Skip delimiters. i = 0; do { if (s[lastPos] == delimiters[i]) { lastPos++; i = 0; } } while (++i < strlen(delimiters)); // Find next "delimiter" i = 0; tmp2 = -1; do { tmp = s.find_first_of(delimiters[i], lastPos); if (tmp < tmp2) tmp2 = tmp; } while (++i < strlen(delimiters)); pos = tmp2; } // Check if we have an "end-token" (not seperated) if (lastPos != s.length()) { tokens.push_back(s.substr(lastPos, s.length() - lastPos)); } } #endif solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/CInverterSputnikSSeriesSimulator.h000066400000000000000000000115731444065341000344530ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2012-2015 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file CInverterSputnikSSeriesSimulator.h * * Created on: June 07, 2012 * Author: Tobias Frost * */ #ifndef CINVERTERSPUTNIKSSERIESSIMULATOR_H_ #define CINVERTERSPUTNIKSSERIESSIMULATOR_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #if defined HAVE_INV_SPUTNIKSIMULATOR #include "Inverters/interfaces/InverterBase.h" #include "Inverters/BasicCommands.h" #include "Inverters/SputnikEngineering/SputnikCommand/ISputnikCommand.h" /** Implements a (simple) simulator for the Sputnik S Series * * The Sputnik S-Series are an inverter family by Sputnik Engineering * Please see the manufacturer's homepage for details. */ class CInverterSputnikSSeriesSimulator : public IInverterBase { public: struct simulator_commands { const char *token; float scale1; IValue *value; float scale2; IValue *value2; bool killbit; }; CInverterSputnikSSeriesSimulator(const string & name, const string & configurationpath); virtual ~CInverterSputnikSSeriesSimulator(); virtual bool CheckConfig(); /** implements the ICommandTarget interface */ virtual void ExecuteCommand(const ICommand *Command); #warning implement me! virtual CConfigCentral* getConfigCentralObject(void) { return NULL; } protected: /** calculate the checksum for the telegram stored in str */ static unsigned int CalcChecksum(const char* str, int len); private: /// Commands for the Workscheduler enum Commands { // broadcast event. CMD_BRC_SHUTDOWN = BasicCommands::CMD_BRC_SHUTDOWN, CMD_INIT = BasicCommands::CMD_USER_MIN, // Simulator commands CMD_SIM_INIT, ///< Wait for incoming connections. CMD_SIM_DISCONNECTED, ///< ctrl server forced us "disconnected" CMD_SIM_WAITDISCONNECT, ///< if connected, wait for disconnection. CMD_SIM_CONNECTED, ///< Wait for incoming data CMD_SIM_EVALUATE_RECEIVE, ///< Parse incoming data and send response CMD_SIM_WAIT_SENT, ///< Wait till data sent // Control-Server commmands. CMD_CTRL_INIT, ///< Wait for incoming connections (cmd-server). CMD_CTRL_WAITDISCONNECT, ///< if connected, wait for disconnection. CMD_CTRL_CONNECTED, ///< Wait for incoming data (cmd-server). CMD_CTRL_PARSERECEIVE, ///< Parse incoming data (cmd-server) and send response. CMD_CTRL_WAIT_SENT ///< Wait till response sent. }; /// Dataports of the sputnik inverters. enum Ports { QUERY = 100, COMMAND = 200, ALARM = 300, INTERFACE = 1000 }; /// parse the answer of the inverter. std::string parsereceivedstring(const string& s); /// parser for the control server. std::string parsereceivedstring_ctrlserver(std::string s); /// Adress to use as "our" adress for communication /// This can be set by the conffile and the parameter ownadr /// defaults to 0xFB /// unsigned int ownadr; void tokenizer(const char *delimiters, const string& s, vector &tokens); /// cache for inverters comm adr. unsigned int commadr; struct simulator_commands *scommands; /// Command Server. IConnect *ctrlserver; /// set to true if the shutdown request has been received via broadcast /// event. bool _shutdown_requested; /// if queries should be answered at all or if the inverter should be simulated as offline /// note: in offline mode it will still accept connections, just not answer them. bool _offline; /// the inverter should not accept() connections anymore. bool _disconnect; /// tracking if we are already connected and thus if we need to /// accept again when ctrl server allows us again to connect. /// \sa _disconnect bool _isconnected; /// inject the next time an checksum error (will force an reconnect from the /// client) bool _inject_chksum_err; /// modify values automatically to simulate changes bool _modify_values; }; #endif #endif /*CINVERTERSPUTNIKSSERIESSIMULATOR_H_*/ solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/SputnikCommand/000077500000000000000000000000001444065341000305525ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/000077500000000000000000000000001444065341000341405ustar00rootroot00000000000000CSputnikCmdBOAlways.cpp000066400000000000000000000017651444065341000403640ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /* * CSputnikCmdBOAlways.cpp * * Created on: 29.05.2012 * Author: tobi */ //#include "CSputnikCmdBOAlways.h" // currently empty. CSputnikCmdBOAlways.h000066400000000000000000000030041444065341000400150ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /* * CSputnikCmdBOAlways.h * * Created on: 29.05.2012 * Author: tobi */ #ifndef CSPUTNIKCMDBOALWAYS_H_ #define CSPUTNIKCMDBOALWAYS_H_ #include "ISputnikCommandBackoffStrategy.h" /// Backoff Algorithm which "always" will issue a command. /// (note: implementation example -- a bare ISputnikCommandBackoffStrategy /// will behave the same. class CSputnikCmdBOAlways : public ISputnikCommandBackoffStrategy { public: virtual ~CSputnikCmdBOAlways() {} CSputnikCmdBOAlways( ISputnikCommandBackoffStrategy *next = NULL ) : ISputnikCommandBackoffStrategy("BOAlways",next) { } }; #endif /* CSPUTNIKCMDBOALWAYS_H_ */ CSputnikCmdBOIfSupported.cpp000066400000000000000000000042671444065341000413700ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /* * CSputnikCmdBOOnce.cpp * * Created on: 30.05.2012 * Author: tobi */ #include "CSputnikCmdBOIfSupported.h" bool CSputnikCmdBOIfSupported::ConsiderCommand() { bool ret = ISputnikCommandBackoffStrategy::ConsiderCommand(); if (!ret) return ret; if (triesleft || supported){ if (supported) { LOGINFO_SA(_logger,LOG_SA_HASH("BOIF_consider"),"Command is supported."); } else { LOGTRACE_SA(_logger, __COUNTER__, "triesleft=" << triesleft); } return true; } LOGINFO_SA(_logger,LOG_SA_HASH("BOIF_consider"), "Command is not supported "); return false; } void CSputnikCmdBOIfSupported::CommandAnswered() { ISputnikCommandBackoffStrategy::CommandAnswered(); LOGDEBUG_SA(_logger, LOG_SA_HASH("BOIF_answered"), "Command answered"); supported = true; } void CSputnikCmdBOIfSupported::CommandNotAnswered() { ISputnikCommandBackoffStrategy::CommandNotAnswered(); LOGDEBUG_SA(_logger, LOG_SA_HASH("BOIF_answered"), "Command not answered. Tries left: " << triesleft); if (triesleft) { triesleft--; } } void CSputnikCmdBOIfSupported::Reset() { ISputnikCommandBackoffStrategy::Reset(); LOGDEBUG_SA(_logger, LOG_SA_HASH("BOIF_answered"), "Reset"); triesleft = triesleft_orig; supported = false; } CSputnikCmdBOIfSupported.h000066400000000000000000000051441444065341000410300ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /* * CSputnikCmdBOIfSupported.h * * Created on: 30.05.2012 * Author: tobi */ #ifndef CSPUTNIKCMDBOISSUPPORTED_H_ #define CSPUTNIKCMDBOISSUPPORTED_H_ #include "ISputnikCommandBackoffStrategy.h" /// Backoff strategy that try to detect if a certain command is supported. /// It will use an (simple) heuristic and try a specific command several times /// (default 3 times) and if it is not answered the command is not issued anymore. /// until the inverter reconnects. class CSputnikCmdBOIfSupported: public ISputnikCommandBackoffStrategy { public: /// Constructor: /// retries -> how often to issue command before giving up CSputnikCmdBOIfSupported( int retries = 3, ISputnikCommandBackoffStrategy *next = NULL) : ISputnikCommandBackoffStrategy("BOIfSupported", next), triesleft(retries), triesleft_orig(retries), supported(false) { // do not repeat ourself more than once a day (roughly, 23,5h to avoid // timing shifts due to the seasons) // and disable the "repeation count" _logger.setSaMaxSuppressTime(23*3600 + 30*60); _logger.setSaMaxSuppressRepetitions(0); } virtual ~CSputnikCmdBOIfSupported() {}; /// Should the command be considered? /// return false if not, true if yes. /// Call Interface first. If Interface returns "false", also return false. virtual bool ConsiderCommand(); /// The command has been answered. virtual void CommandAnswered(); /// The command has not been answered. virtual void CommandNotAnswered(); /// Inverter disconnected, reset state. virtual void Reset(); private: int triesleft; int triesleft_orig; bool supported; }; #endif /* CSPUTNIKCMDBOISSUPPORTED_H_ */ CSputnikCmdBOOnce.cpp000066400000000000000000000033431444065341000400020ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /* * CSputnikCmdBOOnce.cpp * * Created on: 30.05.2012 * Author: tobi */ #include "CSputnikCmdBOOnce.h" bool CSputnikCmdBOOnce::ConsiderCommand() { bool ret = ISputnikCommandBackoffStrategy::ConsiderCommand(); if (ret) { if (issued) { LOGDEBUG_SA(_logger, LOG_SA_HASH("BO-Once_Consider"), "BO-Once: Already issued."); return false; } } LOGDEBUG_SA(_logger, LOG_SA_HASH("BO-Once_Consider"), "BO-Once: Not issued yet."); return ret; } void CSputnikCmdBOOnce::CommandAnswered() { ISputnikCommandBackoffStrategy::CommandAnswered(); LOGDEBUG_SA(_logger, LOG_SA_HASH("BO-Once"), "BO-Once: Answered."); issued = true; } void CSputnikCmdBOOnce::Reset() { ISputnikCommandBackoffStrategy::Reset(); LOGDEBUG_SA(_logger, LOG_SA_HASH("BO-Once"), "BO-Once: Reset."); issued = false; } CSputnikCmdBOOnce.h000066400000000000000000000034021444065341000374430ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /* * CSputnikCmdBOOnce.h * * Created on: 30.05.2012 * Author: tobi */ #ifndef CSPUTNIKCMDBOONCE_H_ #define CSPUTNIKCMDBOONCE_H_ #include "ISputnikCommandBackoffStrategy.h" /// Backoff strategy that will issue a command only once until the inverter /// is reconnected. /// Note: It will only cease issuing if the command was answered. class CSputnikCmdBOOnce: public ISputnikCommandBackoffStrategy { public: CSputnikCmdBOOnce( ISputnikCommandBackoffStrategy *next = NULL ) : ISputnikCommandBackoffStrategy("BOOnce", next), issued(false) { } virtual ~CSputnikCmdBOOnce() {}; /// Should the command be considered? virtual bool ConsiderCommand(); /// The command has been answered. virtual void CommandAnswered(); /// Inverter disconnected, reset state. virtual void Reset(); private: bool issued; }; #endif /* CSPUTNIKCMDBOONCE_H_ */ CSputnikCmdBOTimed.cpp000066400000000000000000000037471444065341000401700ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /* * CSputnikCmdBOTimed.cpp * * Created on: 03.06.2012 * Author: tobi */ #include "CSputnikCmdBOTimed.h" bool CSputnikCmdBOTimed::ConsiderCommand() { bool ret = ISputnikCommandBackoffStrategy::ConsiderCommand(); if (!ret) return false; if (last == boost::posix_time::not_a_date_time) { LOGDEBUG_SA(_logger, LOG_SA_HASH("BO-Timed_Consider"), "BO-Timed: Considering -- first call"); return true; } if (last + interval >= boost::posix_time::second_clock::local_time()) { LOGDEBUG_SA(_logger, LOG_SA_HASH("BO-Timed_Consider"), "BO-Timed: Considering -- due"); return true; } LOGDEBUG_SA(_logger, LOG_SA_HASH("BO-Timed_Consider"), "BO-Timed: not yet due. Due at " << (last+interval)); return false; } void CSputnikCmdBOTimed::CommandAnswered() { LOGDEBUG_SA(_logger, LOG_SA_HASH("BO-Timed-Logic"),"BO-Timed: Answered"); last = boost::posix_time::second_clock::local_time(); } void CSputnikCmdBOTimed::Reset() { LOGDEBUG_SA(_logger, LOG_SA_HASH("BO-Timed-Logic"),"BO-Timed: Reset"); last = boost::posix_time::not_a_date_time; } CSputnikCmdBOTimed.h000066400000000000000000000036651444065341000376340ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /* * CSputnikCmdBOTimed.h * * Created on: 03.06.2012 * Author: tobi */ #ifndef CSPUTNIKCMDBOTIMED_H_ #define CSPUTNIKCMDBOTIMED_H_ #include "ISputnikCommandBackoffStrategy.h" #include /// Backoff strategy that will issue a command only once until the inverter /// is reconnected. /// Note: It will only cease issuing if the command was answered. class CSputnikCmdBOTimed: public ISputnikCommandBackoffStrategy { public: CSputnikCmdBOTimed( const boost::posix_time::time_duration &interval, ISputnikCommandBackoffStrategy *next = NULL) : ISputnikCommandBackoffStrategy("BOTimed", next), interval(interval) { } virtual ~CSputnikCmdBOTimed() {}; /// Should the command be considered? virtual bool ConsiderCommand(); /// The command has been answered. virtual void CommandAnswered(); /// Inverter disconnected, reset state. virtual void Reset(); private: boost::posix_time::time_duration interval; boost::posix_time::ptime last; }; #endif /* CSPUTNIKCMDBOONCE_H_ */ ISputnikCommandBackoffStrategy.cpp000066400000000000000000000036201444065341000426320ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /* * \file ISputnikCommandBackoffStrategy.cpp * * Created on: 28.05.2012 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "ISputnikCommandBackoffStrategy.h" #include "configuration/Registry.h" ISputnikCommandBackoffStrategy::ISputnikCommandBackoffStrategy( const std::string &botype, ISputnikCommandBackoffStrategy *next) : next(next), _botype(botype) { } ISputnikCommandBackoffStrategy::~ISputnikCommandBackoffStrategy() { if (next) delete next; }; bool ISputnikCommandBackoffStrategy::ConsiderCommand() { bool ret = true; if (next) ret = next->ConsiderCommand(); return ret; } void ISputnikCommandBackoffStrategy::CommandIssued() { if (next) next->CommandIssued(); } void ISputnikCommandBackoffStrategy::CommandAnswered() { if (next) next->CommandAnswered(); } void ISputnikCommandBackoffStrategy::CommandNotAnswered() { if (next) next->CommandNotAnswered(); } void ISputnikCommandBackoffStrategy::Reset() { if (next) next->Reset(); } ISputnikCommandBackoffStrategy.h000066400000000000000000000071261444065341000423040ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** * \file ISputnikCommandBackoffStrategy.h * * The backoff strategies decided how often a command will be issued * due to some compile-time selected algorithms. * * Also (they will) determine if a command is at all supported and if * necessary will cease this command completely. ( * * This class defines the interface for the algorithms. * * Note that Backoff-Stragetgies can be staggered (similar to the * decorator Pattern), therefore derived classes needs always to call * the interface method. * * Created on: 28.05.2012 * Author: tobi */ #ifndef ISPUTNIKCOMMANDBACKOFFSTRATEGY_H_ #define ISPUTNIKCOMMANDBACKOFFSTRATEGY_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "configuration/ILogger.h" /** * Interface class for the decorator-pattern-influenced Backoff strategies. */ class ISputnikCommandBackoffStrategy { public: protected: /** * protected constructor for BackoffStrategies * * As BO's needs to be specialized, this is protected. * * \param [in] botype pretty-print typename of the BO Algorithm (used for logging) * \param [in] next next object for the decorator-pattern. */ ISputnikCommandBackoffStrategy(const std::string &botype, ISputnikCommandBackoffStrategy *next = NULL); public: /** * Setting up the logger for the BO Algorithm and emit a DEBUG-level * (which helps to log the configured BO-decorator-chain) * * @param parent parent logger to hook up to * @param specialisation specialization to extend the logger with (usually the com */ virtual void SetLogger(const std::string &parent) { _logger.Setup(parent, _botype); if (next) next->SetLogger(parent); LOGINFO(_logger,"back off strategy " << _botype); } virtual ~ISputnikCommandBackoffStrategy(); /// Should the command be considered? /// return false if not, true if yes. /// Call Interface first. If Interface returns "false", also return false. virtual bool ConsiderCommand(); /// The command has been issued /// Note: If "Command Issued" is followed by "Command Answered" or /// a "CommandNotAnswered". virtual void CommandIssued() ; /// The command has been answered. virtual void CommandAnswered(); /// The command has not been answered. virtual void CommandNotAnswered(); /// Inverter disconnected, reset state. virtual void Reset(); protected: /// Next backoff strategy in the chain (decorator pattern) ISputnikCommandBackoffStrategy *next; /// Logger for this strategy object ILogger _logger; /// Caching the type -- needed for pretty print logging std::string _botype; }; #endif /* ISPUTNIKCOMMANDBACKOFFSTRATEGY_H_ */ solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/SputnikCommand/CSputnikCommand.cpp000066400000000000000000000031611444065341000343160ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** * \file CSputnikCommand.cpp * * * Plans are that the CSC will take over the logic in the Inverter code step by step. * 1- (done) CSC will know about the command token, expected telegram length, do the parsing and return the raw value * 2- (done) CSC will be able to "scale" values with an factor. * 2- CSC will keep track if a command is supported by the Inverter and cease issuing command if not. * 3- (Specialisations of CSCs might handle for some commands more often than others.) * 4- (done) CSC will take the Capabilities handling: creating the Capabilities, updating the values, notfiing subscribers * * Created on: 14.05.2012 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #endif // currently empty. solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/SputnikCommand/CSputnikCommand.h000066400000000000000000000105311444065341000337620ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /* * CSputnikCommand.h * * Created on: 14.05.2012 * Author: tobi */ #ifndef CSPUTNIKCOMMAND_H_ #define CSPUTNIKCOMMAND_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "Inverters/interfaces/InverterBase.h" #include "Inverters/SputnikEngineering/SputnikCommand/ISputnikCommand.h" #include "patterns/CValue.h" #include "Inverters/Capabilites.h" #include "configuration/ILogger.h" /* Defines and handles a single commmand the Sputnik Inverter. * * CSputnikCommand (CSC) will in the long term replace the handling code in * CSputnikInverter for an object orientated approach. * * The object will store all knowledge about an individual command, allows the * parsing and eventually will also take over the Capabilities handling. * * It will also allow an strategy to wisely let the command itself decide when it * should be issued and how often, and to probe if an actual command is indeed supported * by the inverter. * * Milestones to implement functionality: * 1- CSC will store information about * - (implemented) command token, scaling information, expected telegram length, * - (implemented for basic types) do the parsing and return the parsed result. * * 2- CSC will keep track if a command is supported by the Inverter and cease issuing command if not. * 3- (Specialisations of CSCs might handle for some commands more often than others.) * 4- CSC will take the Capabilities handling: creating the Capabilities, updating the values, notfiing subscribers * * Strategy: * - CSCs are kept in a list attached to the Inverters Object * - The CSCS are instanciated on construction time of the Inverter's object with all the information they need to operate. * 5- CSC will be registered by the Inverter at Instanciation time by key parameters- * * */ /** Command Class template for all basic types with the support to scale.*/ template class CSputnikCommand : public ISputnikCommand { public: CSputnikCommand(ILogger &logger, const std::string &cmd, int max_answer_len, T scale, IInverterBase *inv, const std::string & capname, ISputnikCommandBackoffStrategy *backoff = NULL) : ISputnikCommand(logger, cmd, max_answer_len, inv, capname, backoff), scale( scale) { } virtual ~CSputnikCommand() {}; private: /// convert the hex answer to a long variable. unsigned long converthextolong(const std::vector & tokens) { return strtoul(tokens[1].c_str(), NULL, 16); } /// convert the answer of the inverter (given in tokens) to the template type /// and scale it T convert(const std::vector & tokens) { unsigned long integer = this->converthextolong(tokens); T result = integer; result *= scale; return result; } public: //// Parsing of the response and updating capability. /// Caller gives us a already tokenized response, where tokens[0] is the /// command (echoed by the inverter) and tokens[1] is the value is hex /// format. (without 0x, always integer.) virtual bool handle_token(const std::vector & tokens) { if (tokens.size() != 2) return false; try { T temp = this->convert(tokens); CapabilityHandling(temp); } catch (...) { return false; } this->strat->CommandAnswered(); return true; } private: T scale; }; #endif /* CSPUTNIKCOMMAND_H_ */ CSputnikCommandSYS.cpp000066400000000000000000000201471444065341000346410ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/SputnikCommand/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /* * CSputnikCommandSYS.cpp * * Created on: 23.05.2012 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "CSputnikCommandSYS.h" #include "Inverters/Capabilites.h" static const struct { unsigned int code; enum InverterStatusCodes status; const char *description; } statuscodes[] = { { 20001, FEEDING, "Operating" }, { 20002, NOT_FEEDING_OK, "Solar radiation too low" }, { 20003, NOT_FEEDING_OK, "Inverter starting up" }, { 20004, FEEDING_MPP, "Feeding on MPP" }, { 20005, FEEDING, "Feeding. Fan on" }, { 20006, FEEDING_MAXPOWER, "Feeding. Inverter at power limit" }, { 20008, FEEDING, "Feeding" }, { 20009, FEEDING_MAXPOWER, "Feeding. Current limitation (DC)" }, { 20010, FEEDING_MAXPOWER, "Feeding. Current limitation (AC)" }, { 20011, FEEDING_WARNING, "Test mode" }, { 20012, FEEDING_WARNING, "Remotely controlled" }, { 20013, NOT_FEEDING_OK, "Restart delay" }, { 20050, STATUS_UNAVAILABLE, "Firmware update" }, { 20051, FEEDING, "Operating (200051)" }, { 20101, NOT_FEEDING_ERROR, "Inverter error (20101)" }, { 20102, NOT_FEEDING_ERROR, "Inverter error (20102)" }, { 20103, NOT_FEEDING_ERROR, "Inverter error (20103)" }, { 20104, NOT_FEEDING_ERROR, "Inverter error (20104)" }, { 20105, NOT_FEEDING_ERROR, "Isolation error (20105) " }, { 20106, NOT_FEEDING_ERROR, "Isolation error (20106)" }, { 20107, NOT_FEEDING_ERROR, "Inverter error (20107)" }, { 20108, NOT_FEEDING_ERROR, "Inverter error (20108)" }, { 20109, NOT_FEEDING_ERROR, "DC Voltage too high (20109)" }, { 20110, NOT_FEEDING_ERROR, "Internal voltage too high." }, { 20111, NOT_FEEDING_ERROR, "Surge" }, { 20112, NOT_FEEDING_ERROR, "Overload." }, { 20113, NOT_FEEDING_ERROR, "Inverter error (20113)" }, { 20114, NOT_FEEDING_ERROR, "Error current too high." }, { 20115, NOT_FEEDING_EXTEVENT, "Off-grid" }, { 20116, NOT_FEEDING_EXTEVENT, "Grid frequency too high" }, { 20117, NOT_FEEDING_EXTEVENT, "Grid frequency too low" }, { 20118, FEEDING_WARNING, "Islanding -- Operating in island mode." }, { 20119, NOT_FEEDING_EXTEVENT, "Grid quality too low" }, { 20120, NOT_FEEDING_ERROR, "Inverter error (20120)" }, { 20121, NOT_FEEDING_ERROR, "Inverter error (20120)" }, { 20122, NOT_FEEDING_EXTEVENT, "Grid voltage too high" }, { 20123, NOT_FEEDING_EXTEVENT, "Grid voltage too low" }, { 20124, NOT_FEEDING_EXTEVENT, "Over temperature" }, { 20125, STATUS_UNAVAILABLE, "Asymmetric grid currents" }, { 20126, STATUS_UNAVAILABLE, "Error external input 1" }, { 20127, STATUS_UNAVAILABLE, "Error external input 2" }, { 20128, NOT_FEEDING_ERROR, "Inverter error (20128)" }, { 20129, STATUS_UNAVAILABLE, "Phase sequence wrong" }, { 20130, STATUS_UNAVAILABLE, "Wrong Device" }, { 20131, STATUS_UNAVAILABLE, "Main switch off" }, { 20132, STATUS_UNAVAILABLE, "Diode over temperature" }, { 20133, NOT_FEEDING_ERROR, "Inverter error (20133)" }, { 20134, STATUS_UNAVAILABLE, "Fan broken" }, { 20135, NOT_FEEDING_ERROR, "Inverter error (20135)" }, { 20136, NOT_FEEDING_ERROR, "Inverter error (20136)" }, { 20137, NOT_FEEDING_ERROR, "Inverter error (20137)" }, { 20139, NOT_FEEDING_ERROR, "Inverter error (20139)" }, { 20140, NOT_FEEDING_ERROR, "Inverter error (20140)" }, { 20141, NOT_FEEDING_ERROR, "Inverter error (20141)" }, { 20142, NOT_FEEDING_ERROR, "Inverter error (20142)" }, { 20143, NOT_FEEDING_ERROR, "Inverter error (20143)" }, { 20144, NOT_FEEDING_ERROR, "Inverter error (20144)" }, { 20145, STATUS_UNAVAILABLE, "Frequency change rate too high" }, { 20146, NOT_FEEDING_ERROR, "Inverter error (20146)" }, { 20163, NOT_FEEDING_ERROR, "Inverter error (20163)" }, { 20164, NOT_FEEDING_ERROR, "Error current to high." }, { 20165, NOT_FEEDING_EXTEVENT, "Off-grid (20165)" }, { 20166, NOT_FEEDING_EXTEVENT, "Grid frequency too high (20166)" }, { 20167, NOT_FEEDING_EXTEVENT, "Grid frequency too low (20167)" }, { 20168, FEEDING_WARNING, "Islanding -- Operating in island mode (20168)" }, { 20169, NOT_FEEDING_EXTEVENT, "Grid quality too low (20169)" }, { 20170, NOT_FEEDING_ERROR, "Inverter error (20170)" }, { 20172, NOT_FEEDING_EXTEVENT, "Grid voltage too high (20172)" }, { 20173, NOT_FEEDING_EXTEVENT, "Grid voltage too low (20173)" }, { 20175, STATUS_UNAVAILABLE, "Asymmetric Currents (20175)" }, { 20186, STATUS_UNAVAILABLE, "Inverter error (20186)" }, { 20187, STATUS_UNAVAILABLE, "Invalid Firmware" }, { 20188, STATUS_UNAVAILABLE, "Inverter error (20188)" }, { 20190, STATUS_UNAVAILABLE, "Too low results" }, { 20198, STATUS_UNAVAILABLE, "Inverter error (20198)" }, { 20199, STATUS_UNAVAILABLE, "Unknown error (20199)" }, { 0xFFFF, STATUS_UNAVAILABLE, "UNKNOWN -- Please file a bug " "with as much information as you have, and please read the display of your inverter." } }; CSputnikCommandSYS::CSputnikCommandSYS(ILogger &logger, IInverterBase *inv, ISputnikCommandBackoffStrategy *backoff) : ISputnikCommand(logger, "SYS", 10, inv, CAPA_INVERTER_STATUS_NAME " and " CAPA_INVERTER_STATUS_READABLE_NAME, backoff), laststatuscode(0), secondparm_sys(0) {} bool CSputnikCommandSYS::handle_token(const std::vector& tokens) { if (tokens.size() != 3) return false; char *ptmp = NULL; unsigned long status = strtol(tokens[1].c_str(), &ptmp, 16); if (!status || status == ULONG_MAX || ptmp == tokens[1].c_str()) return false; unsigned long status2 = strtol(tokens[2].c_str(), &ptmp, 16); if (ptmp == tokens[1].c_str()) return false; if (status2 && status2 != secondparm_sys) { secondparm_sys = status2; LOGINFO(inverter->logger, "Received an unknown SYS response. Please file a bug" << " along with the following: " << tokens[0] << "," << tokens[1] << "," << tokens[2] << " and check the Inverter's display for more information."); } int i = 0; do { if (statuscodes[i].code == status) break; } while (statuscodes[++i].code != 0xffff ); if (laststatuscode != 0xffff && statuscodes[i].code == 0xffff ) { LOGINFO(inverter->logger, "SYS reported an (too us) unknown status code of " << tokens[0] << "=" << tokens[1] << "," << tokens[2] ); LOGINFO (inverter->logger, " PLEASE file along with all information you have, for example," << " reading the display of the inverter and of the info given above." ); } laststatuscode = statuscodes[i].code; CapabilityHandling(status, CAPA_INVERTER_STATUS_NAME); CapabilityHandling( statuscodes[i].description, CAPA_INVERTER_STATUS_READABLE_NAME); this->strat->CommandAnswered(); return true; } void CSputnikCommandSYS::InverterDisconnected() { CCapability *cap; cap = inverter->GetConcreteCapability(CAPA_INVERTER_STATUS_NAME); if (cap) cap->getValue()->Invalidate(); cap = inverter->GetConcreteCapability(CAPA_INVERTER_STATUS_READABLE_NAME); if (cap) cap->getValue()->Invalidate(); ISputnikCommand::InverterDisconnected(); } CSputnikCommandSYS.h000066400000000000000000000032261444065341000343050ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/SputnikCommand/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /* * CSputnikCommandSYS.h * * Created on: 23.05.2012 * Author: tobi */ #ifndef CSPUTNIKCOMMANDSYS_H_ #define CSPUTNIKCOMMANDSYS_H_ #include "ISputnikCommand.h" #include "Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/ISputnikCommandBackoffStrategy.h" #include "configuration/ILogger.h" class CSputnikCommandSYS : public ISputnikCommand { public: CSputnikCommandSYS( ILogger &logger, IInverterBase *inv, ISputnikCommandBackoffStrategy *backoff = NULL ); virtual ~CSputnikCommandSYS() {} virtual bool handle_token(const std::vector & tokens); virtual void InverterDisconnected(); private: unsigned int laststatuscode; unsigned int secondparm_sys; }; #endif /* CSPUTNIKCOMMANDSYS_H_ */ CSputnikCommandSoftwareVersion.cpp000066400000000000000000000073511444065341000373250ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/SputnikCommand/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /* * CSputnikCommandSoftwareVersion.cpp * * Created on: 19.05.2012 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "CSputnikCommandSoftwareVersion.h" #include "configuration/ILogger.h" /// command string for Software Version static const std::string SWV("SWV"); /// command string for BuildVersion static const std::string BDN("BDN"); /// command string for both static const std::string BDNSWV("BDN;SWV"); CSputnikCommandSoftwareVersion::CSputnikCommandSoftwareVersion(ILogger &logger, IInverterBase *inv, const std::string & capname, ISputnikCommandBackoffStrategy *backoff ) : ISputnikCommand(logger, BDNSWV, 0, inv, capname, backoff), got_buildversion(false), got_swversion(false), sw(0), build(0) { } int CSputnikCommandSoftwareVersion::GetMaxAnswerLen(void) { if ( !got_swversion && ! got_buildversion ) return 9+7; if ( !got_swversion) return 9; return 7; } const std::string& CSputnikCommandSoftwareVersion::GetCommand(void) { if ( !got_swversion && ! got_buildversion ) return BDNSWV; if ( !got_swversion) return (SWV); return BDN; } unsigned int CSputnikCommandSoftwareVersion::GetCommandLen(void) { return GetCommand().length(); } bool CSputnikCommandSoftwareVersion::IsHandled(const std::string& token) { if ( token == SWV) return true; if ( token == BDN) return true; return false; } bool CSputnikCommandSoftwareVersion::ConsiderCommand() { return strat->ConsiderCommand(); } bool CSputnikCommandSoftwareVersion::handle_token( const std::vector& tokens) { if ( tokens.size() != 2) return false; if ( tokens[0] == SWV ) { sw = strtoul(tokens[1].c_str(), NULL, 16); if (sw) got_swversion = true; } else if ( tokens[0] == BDN ) { build = strtoul(tokens[1].c_str(), NULL, 16); if (build) got_buildversion = true; } else { return false; } // do not assemble the capability without the sw version. if (!got_swversion) return true; std::string strsw; strsw = sw/10 + "." + sw%10; if ( got_buildversion ) { strsw += "Build " + build; } CapabilityHandling(strsw); // only tell the backoff strategy that we have been answered if we got // both information. // The Inverter will tell it later, if one sub-command is not available // (as this will be reissued and then detected as not answered. // This will catch also if we make "progress", but never getting the full // information (However, all Inverters I know support this command...) if (got_swversion && got_buildversion) this->strat->CommandAnswered(); return true; } void CSputnikCommandSoftwareVersion::InverterDisconnected() { got_buildversion = false; got_swversion = false; ISputnikCommand::InverterDisconnected(); } CSputnikCommandSoftwareVersion.h000066400000000000000000000044011444065341000367630ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/SputnikCommand/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /* * CSputnikCommandSoftwareVersion.h * * Created on: 19.05.2012 * Author: tobi */ #ifndef CSPUTNIKCOMMANDSOFTWAREVERSION_H_ #define CSPUTNIKCOMMANDSOFTWAREVERSION_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "ISputnikCommand.h" #include "Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/ISputnikCommandBackoffStrategy.h" /** Special implementation for the Inverter's Firmware Version property. * * To get that information, two commands are required. (SWV and BDN) * * \sa ISputnikCommand */ class CSputnikCommandSoftwareVersion : public ISputnikCommand { public: CSputnikCommandSoftwareVersion(ILogger &logger, IInverterBase *inv, const std::string & capname, ISputnikCommandBackoffStrategy *backoff = NULL); virtual ~CSputnikCommandSoftwareVersion() {} virtual bool ConsiderCommand(); virtual int GetMaxAnswerLen(void); virtual const std::string& GetCommand(void); virtual unsigned int GetCommandLen(void); virtual bool IsHandled(const std::string &token); virtual bool handle_token(const std::vector & tokens); virtual void InverterDisconnected(); private: /// to track if we've got the build version already bool got_buildversion; /// to track if we've got the software version already bool got_swversion; /// var to save our result. long sw,build; }; #endif CSputnikCommandTYP.cpp000066400000000000000000000056771444065341000346520ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/SputnikCommand/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /* * CSputnikCommandTYP.cpp * * Created on: 26.05.2012 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "CSputnikCommandTYP.h" static const struct { const int typ; const char *description; } model_lookup[] = { { 65534, "Solarpowerlog Sputnik Simulator"}, { 2001, "SolarMax 2000 E" }, { 3001, "SolarMax 3000 E" }, { 4001, "SolarMax 4000 E" }, { 6000, "SolarMax 6000 E" }, { 2010, "SolarMax 2000 C" }, { 3010, "SolarMax 3000 C" }, { 4010, "SolarMax 4000 C" }, { 4200, "SolarMax 4200 C" }, { 6010, "SolarMax 6000 C" }, { 20010, "SolarMax 2000 S" }, { 20020, "SolarMax 3000 S" }, { 20030, "SolarMax 4200 S" }, { 20040, "SolarMax 6000 S" }, { 20202, "SolarMax 10MT" }, { 20210, "SolarMax 10MTV2" }, { 20, "SolarMax 20 C" }, { 25, "SolarMax 25 C" }, { 30, "SolarMax 30 C" }, { 35, "SolarMax 35 C" }, { 50, "SolarMax 50 C" }, { 80, "SolarMax 80 C" }, { 100, "SolarMax 100 C" }, { 300, "SolarMax 300 C" }, { -1, "UKNOWN MODEL. PLEASE FILE A BUG WITH THE REPORTED ID," "ALONG WITH ALL INFOS YOU HAVE" } }; CSputnikCommandTYP::CSputnikCommandTYP(ILogger &logger, IInverterBase *inv, ISputnikCommandBackoffStrategy *backoff) : ISputnikCommand(logger, "TYP", 9, inv, CAPA_INVERTER_MODEL, backoff) { } bool CSputnikCommandTYP::handle_token(const std::vector& tokens) { string strmodel; unsigned int i = 0; // Check syntax if (tokens.size() != 2) return false; int model = strtoul(tokens[1].c_str(), NULL, 16); do { if (model_lookup[i].typ == model) break; } while (model_lookup[++i].typ != -1); if (model_lookup[i].typ == -1) { LOGINFO(inverter->logger, "Identified a " << model_lookup[i].description); LOGINFO(inverter->logger, "Received TYP was " << tokens[0] << "=" << tokens[1]); } CapabilityHandling(model_lookup[i].description); this->strat->CommandAnswered(); return true; } CSputnikCommandTYP.h000066400000000000000000000030751444065341000343050ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/SputnikCommand/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /* * CSputnikCommandTYP.h * * Created on: 26.05.2012 * Author: tobi */ #ifndef CSPUTNIKCOMMANDTYP_H_ #define CSPUTNIKCOMMANDTYP_H_ #include "ISputnikCommand.h" #include "Inverters/Capabilites.h" #include "Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/ISputnikCommandBackoffStrategy.h" #include "configuration/ILogger.h" class CSputnikCommandTYP : public ISputnikCommand { public: CSputnikCommandTYP(ILogger &logger, IInverterBase *inv, ISputnikCommandBackoffStrategy *backoff = NULL); virtual ~CSputnikCommandTYP() {} virtual bool handle_token(const std::vector & tokens); }; #endif /* CSPUTNIKCOMMANDTYP_H_ */ solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/SputnikCommand/ISputnikCommand.cpp000066400000000000000000000035431444065341000343300ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2014 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /* * ISputnikCommand.cpp * * Created on: 19.05.2012 * Author: tobi */ #include "ISputnikCommand.h" #include "Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/CSputnikCmdBOAlways.h" ISputnikCommand::ISputnikCommand(const ILogger &parentlogger, const std::string &cmd, int maxanswerlen, IInverterBase *inv, const std::string &capname, ISputnikCommandBackoffStrategy *backoffstrategy) : command(cmd), max_answer_len(maxanswerlen), inverter(inv), capaname(capname), strat(backoffstrategy) { logger.Setup(parentlogger.getLoggername(), command); LOGINFO(logger, "Command \"" << command << "\" used for \"" << capaname << "\""); if (!strat) { strat = new CSputnikCmdBOAlways(NULL); } else { // Hides also boring logging message for BOAlways. strat->SetLogger(logger.getLoggername()); } } ISputnikCommand::~ISputnikCommand() { delete strat; } bool ISputnikCommand::ConsiderCommand() { return strat->ConsiderCommand(); } solarpowerlog-solarpowerlog-0.26/src/Inverters/SputnikEngineering/SputnikCommand/ISputnikCommand.h000066400000000000000000000222461444065341000337760ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2014 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ #ifndef ISPUTNIKCOMMAND_H_ #define ISPUTNIKCOMMAND_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "patterns/CValue.h" #include "Inverters/Capabilites.h" #include "Inverters/interfaces/InverterBase.h" #include "Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/ISputnikCommandBackoffStrategy.h" #include "configuration/ILogger.h" // TODO Refactor interface to IInverterCommand and abstract as far a possible // to have a general inverter command abstraction class, not specific to sputnik // (maybe after Danfoss branch has been merged) /** * \file ISputnikCommand.h * * Abstracts the definition and handling of a single command for the Sputnik Inverter. * * The past implementation had a function for every command the inverter * understands, consisting some thousand lines of basically identical code. * To add one command the file had to be edited on several page. * * To fight this and increase maintainability along reducing code complexity this * "strategy" pattern has been implemented. * Using this pattern the inverter has (except for the creation of the * CSputnikCommand) no special knowledge about the commands it will issue. * * The objects encapsulates all information needed to handle the command * - information about the "command token" (token itself, length, max answer * length) * - information about the data we receive (type, capability name) * - handles the registration of this data with the inverter. * * This interface is the base class for the commands. There are two basic * variants of derived classes: * - Most of the commands use simple datatype (integer, float, boolean ...) * - Some commands needs special implementation (for example the Software- * Version needs to assemble the information from two inverter queries) * * For the first category, the templated "CSputnikCommand" can be used, * for the others derived classes like the "CSputnikCommandSoftwareVersion" * come into the game. */ /* * * Created on: 19.05.2012 * Author: tobi */ /** Interface Class for the Commands. * * Implements also the common storage and methods */ class ISputnikCommand { public: /** Constructs the ISputnikCommand. * * @param parentlogger to connect to * @param cmd to be issued to get this data * @param maxanswerlen estimated maximum answer length * @param inv inverter belonwing to this command * @param capname Capabilityname associated with this command * @param backoffstrategy backoffs for this command. If núll "BOAlways" will * be assumed. Object ownership is transferred to this class. */ ISputnikCommand(const ILogger &parentlogger, const std::string &cmd, int maxanswerlen, IInverterBase *inv, const std::string & capname, ISputnikCommandBackoffStrategy *backoffstrategy); virtual ~ISputnikCommand(); /** Returnes the maximium expected length (usually set at compile time) * * \returns max answer length for this command * */ virtual int GetMaxAnswerLen(void) { return max_answer_len; } /** Setter for the Max Answer Len, if needed * * @param max new maximum length */ virtual void SetMaxAnswerLen(int max) { max_answer_len = max; } /** Should we consider this command to be issued? * * This function also handles the calls to the backoff strategies. * * \note If this function is overriden in your class, call * strat->ConsiderCommand -- if it returns false, you also return false. * * \returns true if so, else do not issue command at this time. */ virtual bool ConsiderCommand(); /** Returns a const reference to the token string to be used for the communication * when requesting the data from the inverter. * (note: can be overriden for complex datas, for example if more than one command is * required to get thw whole set. * * \returns reference to string containing the command. */ virtual const std::string& GetCommand(void) { return command; } /** Helper: Return the length of the command to be issued. * * @return length of the command to be issued. */ virtual unsigned int GetCommandLen(void) { return command.length(); } /** Check if the token is handled by this instance. * \returns true if it is, else false * * For complex data, which is assembled from more than one command, this * needs be overridden. */ virtual bool IsHandled(const std::string &token) { return (token == command); } /** handles the parsing and then the capability. * must be implemented in the derived class. * * \note You must call strat->CommandAnswered() in your derived class before * you return true. * * @param vector containing tokenized components. [0] is the command echoed * by the Inverter and [1] is the value * * @return true when sucessuflly handled, false if e.g parse error occoured. */ virtual bool handle_token(const std::vector &) = 0; /** command was sent, but no answer received * (will only be called when there was no other error, like communication * etc) */ /** Notify the class that the command has not been answered by the inverter * * If a command has been issued but not answered, e.g ignored by the inveter * due to being not supported, this function must be called. * * This is for the backoff strategy handling. * * \sa ISputnikCommandBackoffStrategy::CommandAnswered * \sa ISputnikCommandBackoffStrategy::CommandNotAnswered */ virtual void CommandNotAnswered() { this->strat->CommandNotAnswered(); } /** Inform the class that the inverter has been disconnected * * Used to reset the backoff strategies to have a fresh start on reconnects. * */ virtual void InverterDisconnected() { CCapability *cap = inverter->GetConcreteCapability(this->capaname); if (cap) { cap->getValue()->Invalidate(); cap->Notify(); } this->strat->Reset(); } protected: /** Makes the complete capability handling: * * Please see the overridden version (with capaname as parameter) * for details. */ template void CapabilityHandling(T value) const throw() { this->CapabilityHandling(value, this->capaname); }; /** Makes the complete capability handling: * - Registers capability if not existing (incl. notifyng of the observers) * - Updates capability with new value (incl. notifying of observers.) * - Notifcation is only done when the value actually changes. * - Type-check of capabilities (throws if not) (only happens if capability * handling is not done consequently, that is using types-defines in * Capabilites.h) * * \param value Value to be stored * \param capname Capability-name. * * \throw exception if types are mismatching. */ template void CapabilityHandling(T value, const std::string &capname) const throw() { assert(inverter); CCapability *cap = inverter->GetConcreteCapability(capname); if (!cap) { IValue *v = new CValue; ((CValue*)v)->Set(value); cap = new CCapability(capname,v,inverter); inverter->AddCapability(cap); inverter->GetConcreteCapability(CAPA_CAPAS_UPDATED)->Notify(); cap->Notify(); return; } // Check for the type and throw an exception if not equal. // (as capabilities are created by this class, this should be not happen) if ( CValue::IsType(cap->getValue())) { CValue *v = (CValue *)cap->getValue(); if (!v->IsValid() || value != v->Get()) { v->Set(value); cap->Notify(); } return; } else { std::bad_cast e; LOGERROR(inverter->logger,"Bad cast for command " + command); throw e; } } protected: std::string command; int max_answer_len; IInverterBase *inverter; std::string capaname; ISputnikCommandBackoffStrategy *strat; ILogger logger; }; #endif /* ISPUTNIKCOMMAND_H_ */ solarpowerlog-solarpowerlog-0.26/src/Inverters/factories/000077500000000000000000000000001444065341000237655ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Inverters/factories/IInverterFactory.cpp000066400000000000000000000023171444065341000277330ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 209-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file IInverterFactory.cpp * * Created on: May 20, 2009 * Author: tobi */ #include "IInverterFactory.h" #ifdef HAVE_CONFIG_H #include "config.h" #endif IInverterFactory::IInverterFactory() { // TODO Auto-generated constructor stub } IInverterFactory::~IInverterFactory() { // TODO Auto-generated destructor stub } solarpowerlog-solarpowerlog-0.26/src/Inverters/factories/IInverterFactory.h000066400000000000000000000027621444065341000274040ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file IInverterFactory.h * * Created on: May 20, 2009 * Author: tobi */ #ifndef IINVERTERFACTORY_H_ #define IINVERTERFACTORY_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif class IInverterBase; #include using namespace std; /** \fixme COMMENT ME * * * TODO DOCUMENT ME! */ class IInverterFactory { public: virtual IInverterBase * Factory(const string& type, const string& name, const string & configurationpath) = 0; virtual const string & GetSupportedModels() const = 0; IInverterFactory(); virtual ~IInverterFactory(); }; #endif /* IINVERTERFACTORY_H_ */ solarpowerlog-solarpowerlog-0.26/src/Inverters/factories/InverterFactoryFactory.cpp000066400000000000000000000035701444065341000311540ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2015 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file InverterFactoryFactory.cpp * * Created on: May 21, 2009 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "InverterFactoryFactory.h" #include "IInverterFactory.h" #if defined HAVE_INV_SPUTNIK || defined HAVE_INV_SPUTNIKSIMULATOR #include "Inverters/SputnikEngineering/CInverterFactorySputnik.h" #endif #if defined HAVE_INV_DUMMY #include "Inverters/DummyInverter/CInverterFactoryDummy.h" #endif InverterFactoryFactory::InverterFactoryFactory() { // TODO Auto-generated constructor stub } IInverterFactory *InverterFactoryFactory::createInverterFactory(const string& manufacturer) { #if defined HAVE_INV_SPUTNIK || defined HAVE_INV_SPUTNIKSIMULATOR if (manufacturer == INV_MANU_SPUTNIK) { return new CInverterFactorySputnik; } #endif #if defined HAVE_INV_DUMMY if ( manufacturer == INV_MANU_DUMMY) { return new CInverterFactoryDummy; } #endif return NULL; } InverterFactoryFactory::~InverterFactoryFactory() { // TODO Auto-generated destructor stub } solarpowerlog-solarpowerlog-0.26/src/Inverters/factories/InverterFactoryFactory.h000066400000000000000000000035301444065341000306150ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2015 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file InverterFactoryFactory.h * * Created on: May 21, 2009 * Author: tobi */ #ifndef INVERTERFACTORYFACTORY_H_ #define INVERTERFACTORYFACTORY_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #if defined HAVE_INV_SPUTNIK || defined HAVE_INV_SPUTNIKSIMULATOR #define INV_MANU_SPUTNIK "SPUTNIK_ENGINEERING" #else #define INV_MANU_SPUTNIK #endif #ifdef HAVE_INV_DUMMY #define INV_MANU_DUMMY "DUMMY_INVERTER" #else #define INV_MANU_DUMMY #endif class IInverterFactory; using namespace std; /** IInverterFactoryFactory * * This class implements a factory-factory pattern. * The generated object is -- depending on the manufacturer of the inverter -- * a factory able to generate the final object. * */ class InverterFactoryFactory { public: static IInverterFactory * createInverterFactory(const string& manufacturer); private: InverterFactoryFactory(); public: virtual ~InverterFactoryFactory(); }; #endif /* INVERTERFACTORYFACTORY_H_ */ solarpowerlog-solarpowerlog-0.26/src/Inverters/interfaces/000077500000000000000000000000001444065341000241315ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/Inverters/interfaces/CNestedCapaIterator.cpp000066400000000000000000000053031444065341000304620ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file CNestedCapaIterator.cpp * * Created on: Jun 2, 2009 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "Inverters/interfaces/CNestedCapaIterator.h" CNestedCapaIterator::CNestedCapaIterator( IInverterBase *b, IInverterBase* parent ) : ICapaIterator(b, parent) { topmost = b; // TODO Auto-generated constructor stub } CNestedCapaIterator::~CNestedCapaIterator() { // TODO Auto-generated destructor stub } /// Check if we have a next capa, if not, delegate to /// the parent, if exists. /// Set also the grandparent, if necessary to recurse all the way down. /// /// \warning do not use GetNext() without a HasNext()! bool CNestedCapaIterator::HasNext() { CCapability *c; pair p; if (ICapaIterator::HasNext()) { p = GetElement(); // Check if we had to filter that one out. if (p.second == (c = topmost->GetConcreteCapability(p.first))) { // yes, not filtered. return true; } else { // that one is to be filtered. // Filter and then recurse. ICapaIterator::GetNext(); return HasNext(); } } // appearantly, the curent base is out of capabilities. // descent one level deeper. if (parent) { // Set the parent as new base, and ask the parent for its parent // via getting the information in its iterator. this->SetBase(parent); auto_ptr p(parent->GetCapaNewIterator()); this->setParent(p->getParent()); } else { return false; } return ICapaIterator::HasNext(); } /// One could just delegate to the base class, but /// we want to make sure that we won't pass /// capas we have overriden /// \warning do not use GetNext() without a prior HasNext()! pair CNestedCapaIterator::GetNext() { // HasNext() already set up everything we need to know... return ICapaIterator::GetNext(); } solarpowerlog-solarpowerlog-0.26/src/Inverters/interfaces/CNestedCapaIterator.h000066400000000000000000000030001444065341000301170ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file CNestedCapaIterator.h * * Created on: Jun 2, 2009 * Author: tobi */ #ifndef CNESTEDCAPAITERATOR_H_ #define CNESTEDCAPAITERATOR_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif /** \fixme COMMENT ME * * * TODO DOCUMENT ME! */ #include "Inverters/interfaces/ICapaIterator.h" class CNestedCapaIterator : public ICapaIterator { public: CNestedCapaIterator(IInverterBase *b, IInverterBase *parent = NULL); virtual bool HasNext(); virtual pair GetNext(); virtual ~CNestedCapaIterator(); private: IInverterBase *topmost; }; #endif /* CNESTEDCAPAITERATOR_H_ */ solarpowerlog-solarpowerlog-0.26/src/Inverters/interfaces/ICapaIterator.cpp000066400000000000000000000042311444065341000273240ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file ICapaIterator.cpp * * Created on: Jun 2, 2009 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "ICapaIterator.h" #include "configuration/Registry.h" ICapaIterator::ICapaIterator( IInverterBase *b, IInverterBase *p ) { // TODO Cleanup Debug code cerr << " DEBUG: new ICapaInverter b=" << b->GetName(); //if (p) // cerr << " p=" << p->GetName(); // cerr << endl; SetBase(b); parent = p; } void ICapaIterator::SetBase( IInverterBase *b ) { base = b; it = base-> GetCapabilityIterator(); } bool ICapaIterator::HasNext() { if (it != base->GetCapabilityLastIterator()) { //cerr << "DEBUG: Has Next on " << base->GetName() << " " // << (*it).first << endl; return true; } else { return false; } } IInverterBase *ICapaIterator::GetBase() { return base; } pair ICapaIterator::GetNext() { if (it != base->GetCapabilityLastIterator()) { return *(it++); } LOGDEBUG(Registry::GetMainLogger(), "BUG: NOTHING TO RETURN: USE HasNext() PRIOR GETNEXT()"); assert(false); return *it; } ICapaIterator::~ICapaIterator() { // TODO Auto-generated destructor stub } pair ICapaIterator::GetElement() { // cerr << "DEBUG: Get Element: Returning Element " << (*it).first << endl; return *it; } solarpowerlog-solarpowerlog-0.26/src/Inverters/interfaces/ICapaIterator.h000066400000000000000000000037201444065341000267730ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file ICapaIterator.h * * Created on: Jun 2, 2009 * Author: tobi */ #ifndef ICAPAITERATOR_H_ #define ICAPAITERATOR_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include "Inverters/interfaces/InverterBase.h" class CCapability; /** \fixme COMMENT ME * * * TODO DOCUMENT ME! */ class ICapaIterator { public: private: IInverterBase *base; map::iterator it; protected: IInverterBase *parent; public: ICapaIterator( IInverterBase *b, IInverterBase *parent = 0 ); virtual ~ICapaIterator(); IInverterBase *GetBase(); virtual void SetBase( IInverterBase *b ); virtual bool HasNext(); virtual pair GetNext(); virtual pair GetElement(); // TODO Rename that meethod. It has nothing to do with "the parent element", // but the "parent hierachy element", the one-level-deeper.... IInverterBase *getParent() const { return parent; } void setParent( IInverterBase *parent ) { this->parent = parent; } }; #endif /* ICAPAITERATOR_H_ */ solarpowerlog-solarpowerlog-0.26/src/Inverters/interfaces/InverterBase.cpp000066400000000000000000000076631444065341000272420ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2015 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /* * InverterBase.cpp * * Created on: May 9, 2009 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include "Inverters/interfaces/InverterBase.h" #include "Connections/factories/IConnectFactory.h" #include "interfaces/CCapability.h" #include "Inverters/Capabilites.h" #include "patterns/IValue.h" #include "patterns/CValue.h" #include "Inverters/interfaces/ICapaIterator.h" #include "configuration/ConfigCentral/CConfigCentral.h" using namespace std; IInverterBase::IInverterBase( const string& name, const string & configurationpath, const string& role ) { // Setup the logger logger.Setup(name,configurationpath, role); this->name = name; this->configurationpath = configurationpath; connection = IConnectFactory::Factory(configurationpath); connection->SetupLogger(logger.getLoggername()); pair::iterator, bool> b; string s; CCapability *c; // Add the "must have" capabilites. c = new CCapability(CAPA_CAPAS_UPDATED, new CValue, this); b = CapabilityMap.insert(pair(CAPA_CAPAS_UPDATED, c)); assert( b.second); c = new CCapability(CAPA_CAPAS_REMOVEALL, new CValue, this); b = CapabilityMap.insert( pair(CAPA_CAPAS_REMOVEALL, c)); assert( b.second); c = new CCapability(CAPA_INVERTER_DATASTATE, new CValue, this); b = CapabilityMap.insert( pair(CAPA_INVERTER_DATASTATE, c)); assert( b.second); } IInverterBase::~IInverterBase() { if (connection) delete connection; connection = NULL; // TODO auto destruct all elements in the capability map. map::iterator it = GetCapabilityIterator(); for (; it != GetCapabilityLastIterator(); it++) delete (*it).second; CapabilityMap.clear(); } const std::string& IInverterBase::GetName( void ) const { return name; } ICapaIterator *IInverterBase::GetCapaNewIterator() { return new ICapaIterator(this); } /** returns a iterator of the Capabilties. The iterator is inizialized at the begin of the map.*/ map::iterator IInverterBase::GetCapabilityIterator() { map::iterator it = CapabilityMap.begin(); return it; } /** return a iterator of the Capabilites. The iterator is placed at the end of the map * This allows a end-of-list check */ map::iterator IInverterBase::GetCapabilityLastIterator() { map::iterator it = CapabilityMap.end(); return it; } CCapability *IInverterBase::GetConcreteCapability( const string &identifier ) { map::iterator it = CapabilityMap.find(identifier); if (it == CapabilityMap.end()) return 0; return it->second; } /// Add a Capability for the inverter. void IInverterBase::AddCapability(CCapability* capa) { CapabilityMap.insert(pair (capa->getDescription(), capa)); LOGDEBUG(logger, "Added new Capability to " << name << ": " << capa->getDescription()); } solarpowerlog-solarpowerlog-0.26/src/Inverters/interfaces/InverterBase.h000066400000000000000000000334501444065341000267000ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2015 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \defgroup Inverters Description and Configration of Inverter Models */ /** \file InverterBase.h * * \date May 9, 2009 * \author Tobias Frost * * This is the interface for inverters. * * \page IInverterBase IInverterBase: Interface for Inverters * \ingroup Concepts * * IIinverterBase is the interface which should be used for all inverters. * It is also the base class for all DataFilters, (IDataFilter), as they * also need most of the infracstructure for their duties. * * It is ought to be derived, and the concrete implemenation implements the * data aquisition. * * To help avoiding doublicated code, the base class have contains already some * code for the following funcions: * - \ref IIBConnection "Comms" * - Generate the concrete connection object (IConnect * derived) The type of connection is extracted automatically * from the configuration. On Datafilters, a default "Dummy" * Connection will be created, if the specific configuratoin * entries are missing. * - \ref IIBCapabilites "Capabilites" * - Generating the required CCapabilites (see * #Inverters/Capabilites.h) * - Infrastructure around Capabilites: Adding, Iterator, Finding * an concrete Capability... * - \ref IIBWorkScheduler "Target for a WorkScheduler" * - Inverters should use solarpowerlog's Work Scheduler for * execution planning. To be able to receive ICommands they are * derived from ICommandTarget. * - By using the default Scheduler (see #Registry), concurrency * is avoided, so there is no need for locking methods for common * accessed datas. * * \section IIBConnection Connection: Connecting to the word. * * As stated before, the base class creates the communication object. * To determine which connectiion type is to be used, it will use the factory * IConnectionFactory. This factory looks in the configuration-path -- supplied * by the bootstrap code -- for the entry "commtype" and create the associated * comms object. * * BTW: The connection classes will also retrieve their configuration from the * config file automatically. * * The comms object will not automatically connect. This should be done * later while running the initializtion commmand (see workqueue below) * * \image html inverter_comm.png "Connection Services" * * The image above shows the services currently exists for the comm classes. * * Currently, the connection classed are mixed synchron and asynchronous. But * this will change, the connection class should use asynchronous * methods to avoid blocking the main task while waiting for completion. * * This also implies, that the inverters have to implement some kind of time-out * handling by itself. (Well its on the TODO list: \todo implement some kind of * generic timeout handling, like schedule timeout, cancel timeout.) * * \sa #IInverterBase::connection * * \section IIBCapabilites How Capabilites transport datas * * As described \ref CapaConcept "here", Capabilies store abstraced measurement * values as well as some meta-datas controlling the Capabilites itself. * * Simplified, the values are generated at the inverter's object and passed * down to Datafilters (or loggers). * * \image html FilterSequence.png "Capabilitiy passed through two filters (simplified)" * * The data is passed along the Filters using the Observer Design pattern * inheritated by the capability: Upon updating it, the Inverter calls the * Notify function and the DataFilter gets notified. * * As illustrated, * Datafilters can also be chained. With this abitrary data * manipulations can be achieved: The datafilter can calculate additional data * sets (example: Efficiency out of Pin and Pout), or do some other arithmetics * on them (mean value...) * * Datafilters can also be parallel to e.g enable parallel serving of data to * different logging systems: One would log to disk, the other servin a web * page. * * By the way, the Inverter manages a list of capablities in the map * (IInverterBase::CapabilityMap). (The map is organized in pairs of * containing the description of the Capability and a * pointer to the Capability.) However, there is an API to access the Capability * in IInverteBase and an enhanced one in IDataFilter. * * ( \todo More details are planned to appear on the IDataFilter description.) * * The image below shows all the classes involved in the dataflow from the * Inverter to the DataFilter: * * \image html dataflow_classes.png "Classes needed to let the data flow" * * This image shows how a typical update of a Capbility, and how the Data-Filter * receives the information. * * \image html dataflow_sequence.png "Dataflow Sequence Diagram" * * \subsection IIBCSnip Code-Snippets around Capabilites. * * Unconditionally Register a new Capability (e.g in the constructor) * \code * c = new CCapability(CAPA_CAPAS_UPDATED, CAPA_CAPAS_UPDATED_TYPE, this); * AddCapability(CAPA_CAPAS_UPDATED, c); * * \endcode * * Update a Capability's Value, create if not existance. Check for type *\code * * CCapability *cap = GetConcreteCapability(CAPA_INVERTER_TEMPERATURE_NAME); * * if (!cap) { * string s; * IValue *v; * CCapability *c; * s = CAPA_INVERTER_TEMPERATURE_NAME; * v = IValue::Factory(CAPA_INVERTER_TEMPERATURE_TYPE); * ((CValue*) v)->Set(f); * c = new CCapability(s, v, this); * AddCapability(s, c); * * cap = GetConcreteCapability(CAPA_CAPAS_UPDATED); * cap->Notify(); * } * * // Capa already in the list. Check if we need to update it. * else if (cap->getValue()->GetType() == CAPA_INVERTER_TEMPERATURE_TYPE) { * CValue *val = (CValue*) cap->getValue(); * * if (val -> Get() != f) { * val->Set(f); * cap->Notify(); * } * } else { * cerr << "BUG: " << CAPA_INVERTER_TEMPERATURE_NAME* * << " not a float "; * } * * \endcode * * * \section IIBWorkScheduler "Work Scheduler" * * IInverterbase is derived from ICOmmandTarget. This class can reiceive * commands from CWorkSchedule objects. One CWorkSchedule object can be received * by the #Registry, which is open for all objects to use. (This Scheduler is * operated by solarpowerlogs internals, so no extra code is required to have * an operating schedule.) * * For Details, for example, how to use a CWorkSchedule and how to schedule * some Work in a specific amount of time, please see the \ref PageCWorkSchedule * "WorkScheduler overview" Page. * * \todo that Workscheduler-Page is not yet written, */ #ifndef INVERTERBASE_H_ #define INVERTERBASE_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include "Connections/interfaces/IConnect.h" #include "Connections/factories/IConnectFactory.h" #include "Inverters/factories/InverterFactoryFactory.h" #include "interfaces/CCapability.h" #include "patterns/ICommandTarget.h" #include "configuration/ILogger.h" class ICapaIterator; class CConfigCentral; using namespace std; /// connection_timeout: Wait this long for the connection attempt to time out /// note: currently FIXME (not implemented in ASIO TCP) /// note: This tweak is optional. The Connection method needs not to honor /// this parameter (especially if the success/failure is immediately known) #define CONFIG_TWEAK_CONNECTION_TIMEOUT "option_connectiontimeout" #define CONFIG_TWEAK_CONNECTION_TIMEOUT_DEFAULT (15000) #define IBASE_DESCRIPTION_INTRO \ "Inverter definition\n" \ "These basic parameters are necessary so that solarpowerlog has knowldege " \ "about the inverter to be configured:\n" \ "\"name\", \"manufacturer\" and \"model\"" #define IBASE_DESCRIPTION_NAME \ "This configuration names the inverter. The name are used internally to identify " \ "the inverter and thus needs to be unique." #define IBASE_DESCRIPTION_MANUFACTURER \ "Specifies the manufacturer of the inverter.\n" \ "Possible values are:\n" \ INV_MANU_SPUTNIK " " \ INV_MANU_DUMMY #define IBASE_DESCRIPTION_MODEL \ "Specifies the model of the inverter." #define IBASE_DESCRIPTION_COMMS \ "Specifies the communication method to be used.\n" \ "Possible values are:\n" \ COMMS_ASIOTCP_ID " " \ COMMS_ASIOSERIAL_ID " " \ COMMS_SHARED_ID " " \ "\nNote: Every communication method has its own configuration parameter, " \ "please consult its documentation and sample configuration files." /** Inverter Interface .... */ // TODO: This class renamed, as it also fits for the "Filters" (Data source, data computing/enhancing, ...) // Inverters will be only a special interface, derived from this base class class IInverterBase: public ICommandTarget { public: friend class ICapaIterator; /** Constructor for the Inverter Interface. * The baseclass does this already for their concrete Implementations: * - Create the connection (out of config, if not needed a dummy * will be created * - Create the "must have" Capabilites * - CAPA_CAPAS_UPDATED * - CAPA_CAPAS_REMOVEALL * - CAPA_INVERTER_DATASTATE * \note: DataFilters might want to delete one or more of them * (out of their instance!), if they are sure that they don't * need them. For example, a DataFilter has usually no influence * over data validity. If it deletes its Capability, its client * will automatically get the one of the inverter) * * \param name of the Inverter (name as in the config) * \param configurationpath is the path to access this inverters config * \param role specifies the role in which the inverter is used. Currently this * is only used to setup the correct logging path for the associated logger. * use "inverter" for inverters and "datafilter" for filters. * */ IInverterBase(const string &name, const string & configurationpath, const string & role); virtual ~IInverterBase(); /** Getter for the name property. (Inverter Name equals to the one given in the config) */ virtual const std::string& GetName(void) const; /** check for a specific capability and return the pointer to it * returns NULL if it is not registered. */ // TODO Move to c++ file virtual CCapability *GetConcreteCapability(const string &identifier); /// Get a Iterator over all Capability. /// Filters may overload it to provide a CCapaNestedIterator. /// \warning you are responsible deleting the iterator! Use auto_ptr ! virtual ICapaIterator* GetCapaNewIterator(); /// Check Configuration /// \returns false on error, true on success. virtual bool CheckConfig() = 0; virtual const std::string& GetConfigurationPath() const { return configurationpath; } virtual IConnect * getConnection(void) const { return connection; } /** Create & Get a CCOnfigCentral object for this instance. * * @return generated object. Ownership is waived to caller. might return NULL. * * \note in overriden version, call the base's implementation. * The base *might* already have added properties to check aloing with * their descriptions. It also can opt to just return a NULL pointer though. */ virtual CConfigCentral* getConfigCentralObject(CConfigCentral *parent) { return parent; } protected: /// Add a Capability for the inverter. friend class ISputnikCommand; virtual void AddCapability(CCapability* capa); /** returns a iterator of the Capabilties. The iterator is inizialized at the begin of the map.*/ virtual map::iterator GetCapabilityIterator(void); /** return a iterator of the Capabilites. The iterator is placed at the end of the map * This allows a end-of-list check */ virtual map::iterator GetCapabilityLastIterator(void); /** Configuration path as determined on start -- for easier fetching the config.*/ std::string configurationpath; /** Inverter Name -- as in config */ std::string name; // Class for handling the connectivity. // Is a strategy design pattern interface. So different ways of connection can be handled by the same interface // (In other words: Our inverter does need nor want to know, if it is RS485 or TCP/IP, or even, both.) // NOTE: The Connection is to be made by a factory! // NOTE2: Beware: Connections can die any time! Make sure to handle this. IConnect *connection; protected: // This maps contains all the Caps by the Inverter. // Caps implements the Subject in the Observer-Pattern. // This keeps all informed! // Class for handling capabilities. // (Inverters can add capabilities at runtime, also can enhancement filters. // (A Capability bundles reading of a inverter with a tag describing the information map CapabilityMap; // Allow sub-objects of the inverter accessing the logger. public: /// The Logger Class for Debugging, Error reporting etc... ILogger logger; }; #endif /* INVERTERBASE_H_ */ // debug code to keep... // Dumping the map. // map::iterator it1 = CapabilityMap.begin(); // cerr << "DUMP Capabilites: " << this->GetName() << endl; // for (int i=0; it1 != CapabilityMap.end(); it1++,i++) // { // cerr << i << " " << (*it1).first << endl; // } solarpowerlog-solarpowerlog-0.26/src/Makefile.am000066400000000000000000000147771444065341000221010ustar00rootroot00000000000000## Makefile.am -- Process this file with automake to produce Makefile.in ACLOCAL_AMFLAGS = -I m4 AM_CXXFLAGS = $(BOOST_CPPFLAGS) bin_PROGRAMS = solarpowerlog solarpowerlog_SOURCES = configuration/CConfigHelper.cpp \ configuration/CConfigHelper.h \ configuration/ConfigCentral/CConfigCentral.cpp \ configuration/ConfigCentral/CConfigCentralEntry.cpp \ configuration/ConfigCentral/CConfigCentralEntry.h \ configuration/ConfigCentral/CConfigCentral.h \ configuration/ConfigCentral/IConfigCentralEntry.cpp \ configuration/ConfigCentral/IConfigCentralEntry.h \ configuration/ConfigCentral/CConfigCentralEntryRangeCheck.cpp \ configuration/ConfigCentral/CConfigCentralEntryRangeCheck.h \ configuration/ConfigCentral/CConfigCentralEntryText.cpp \ configuration/ConfigCentral/CConfigCentralEntryText.h \ configuration/ConfigCentral/ConfigCentralHelpers.cpp \ configuration/ConfigCentral/ConfigCentralHelpers.h \ configuration/ILogger.cpp \ configuration/ILogger.h \ configuration/ILogger_hashmacro.h \ configuration/Registry.cpp \ configuration/Registry.h \ Connections/CAsyncCommand.cpp \ Connections/CAsyncCommand.h \ Connections/CConnectDummy.cpp \ Connections/CConnectDummy.h \ Connections/CConnectSerialAsio.cpp \ Connections/CConnectSerialAsio.h \ Connections/CConnectTCPAsio.cpp \ Connections/CConnectTCPAsio.h \ Connections/factories/IConnectFactory.cpp \ Connections/factories/IConnectFactory.h \ Connections/interfaces/IConnect.cpp \ Connections/interfaces/IConnect.h \ Connections/sharedconnection/CSharedConnection.cpp \ Connections/sharedconnection/CSharedConnection.h \ Connections/sharedconnection/CSharedConnectionMaster.cpp \ Connections/sharedconnection/CSharedConnectionMaster.h \ Connections/sharedconnection/CSharedConnectionSlave.cpp \ Connections/sharedconnection/CSharedConnectionSlave.h \ ctemplate/ctemplate.c \ ctemplate/ctemplate.h porting.h \ daemon.cpp \ daemon.h \ DataFilters/CCSVOutputFilter.cpp \ DataFilters/CCSVOutputFilter.h DataFilters/HTMLWriter/CHTMLWriter.h \ DataFilters/CDumpOutputFilter.cpp \ DataFilters/DBWriter/CdbInfo.cpp \ DataFilters/DBWriter/CdbInfo.h \ DataFilters/DBWriter/CDBWHSpecialTokens.cpp \ DataFilters/DBWriter/CDBWHSpecialTokens.h \ DataFilters/DBWriter/CDBWriterFilter.cpp \ DataFilters/DBWriter/CDBWriterFilter.h \ DataFilters/DBWriter/CDBWriterHelper.cpp \ DataFilters/DBWriter/CDBWriterHelper.h \ DataFilters/HTMLWriter/CHTMLWriter.cpp \ DataFilters/HTMLWriter/formatter/CFormaterWebRootStrip.cpp \ DataFilters/HTMLWriter/formatter/CFormaterWebRootStrip.h \ DataFilters/HTMLWriter/formatter/CFormatterSearchCSVEntry.cpp \ DataFilters/HTMLWriter/formatter/CFormatterSearchCSVEntry.h \ DataFilters/HTMLWriter/formatter/IFormater.cpp \ DataFilters/HTMLWriter/formatter/IFormater.h \ DataFilters/interfaces/factories/IDataFilterFactory.cpp \ DataFilters/interfaces/factories/IDataFilterFactory.h \ DataFilters/interfaces/IDataFilter.cpp \ DataFilters/interfaces/IDataFilter.h DataFilters/CDumpOutputFilter.h \ interfaces/CCapability.cpp \ interfaces/CCapability.h \ interfaces/CDebugHelper.cpp \ interfaces/CDebugHelper.h \ interfaces/CMutexHelper.cpp \ interfaces/CMutexHelper.h \ interfaces/CTimedWork.cpp \ interfaces/CTimedWork.h \ interfaces/CWorkScheduler.cpp \ Inverters/BasicCommands.h \ Inverters/Capabilites.h \ Inverters/DummyInverter/CInverterDummy.cpp \ Inverters/DummyInverter/CInverterDummy.h \ Inverters/DummyInverter/CInverterFactoryDummy.cpp \ Inverters/DummyInverter/CInverterFactoryDummy.h \ Inverters/factories/IInverterFactory.cpp \ Inverters/factories/IInverterFactory.h interfaces/CWorkScheduler.h \ Inverters/factories/InverterFactoryFactory.cpp \ Inverters/factories/InverterFactoryFactory.h \ Inverters/interfaces/CNestedCapaIterator.cpp \ Inverters/interfaces/CNestedCapaIterator.h \ Inverters/interfaces/ICapaIterator.cpp\ Inverters/interfaces/ICapaIterator.h \ Inverters/interfaces/InverterBase.cpp \ Inverters/interfaces/InverterBase.h \ Inverters/SputnikEngineering/CInverterFactorySputnik.cpp \ Inverters/SputnikEngineering/CInverterFactorySputnik.h \ Inverters/SputnikEngineering/CInverterSputnikSSeries.cpp \ Inverters/SputnikEngineering/CInverterSputnikSSeries.h \ Inverters/SputnikEngineering/CInverterSputnikSSeriesSimulator.cpp \ Inverters/SputnikEngineering/CInverterSputnikSSeriesSimulator.h \ Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/CSputnikCmdBOAlways.cpp \ Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/CSputnikCmdBOAlways.h \ Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/CSputnikCmdBOIfSupported.cpp \ Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/CSputnikCmdBOIfSupported.h \ Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/CSputnikCmdBOOnce.cpp \ Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/CSputnikCmdBOOnce.h \ Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/CSputnikCmdBOTimed.cpp \ Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/CSputnikCmdBOTimed.h \ Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/ISputnikCommandBackoffStrategy.cpp \ Inverters/SputnikEngineering/SputnikCommand/BackoffStrategies/ISputnikCommandBackoffStrategy.h \ Inverters/SputnikEngineering/SputnikCommand/CSputnikCommand.cpp \ Inverters/SputnikEngineering/SputnikCommand/CSputnikCommand.h \ Inverters/SputnikEngineering/SputnikCommand/CSputnikCommandSoftwareVersion.cpp \ Inverters/SputnikEngineering/SputnikCommand/CSputnikCommandSoftwareVersion.h \ Inverters/SputnikEngineering/SputnikCommand/CSputnikCommandSYS.cpp \ Inverters/SputnikEngineering/SputnikCommand/CSputnikCommandSYS.h \ Inverters/SputnikEngineering/SputnikCommand/CSputnikCommandTYP.cpp \ Inverters/SputnikEngineering/SputnikCommand/CSputnikCommandTYP.h \ Inverters/SputnikEngineering/SputnikCommand/ISputnikCommand.cpp \ Inverters/SputnikEngineering/SputnikCommand/ISputnikCommand.h \ patterns/CValue.h \ patterns/ICommand.cpp \ patterns/ICommand.h \ patterns/ICommandTarget.cpp \ patterns/ICommandTarget.h \ patterns/IObserverObserver.cpp \ patterns/IObserverObserver.h \ patterns/IObserverSubject.cpp \ patterns/IObserverSubject.h \ patterns/IValue.cpp \ patterns/IValue.h \ solarpowerlog.cpp solarpowerlog_CPPFLAGS = $(CONFIG_CFLAGS) $(LOG4CXX_CFLAGS) $(APR_CFLAGS) \ $(APRUTIL_CFLAGS) $(CTEMPLATE_CFLAGS) solarpowerlog_LDADD = $(CONFIG_LIBS) $(LOG4CXX_LIBS) $(APR_LIBS) \ $(APRUTIL_LIBS) $(BOOST_LDFLAGS) $(BOOST_THREAD_LIBS) \ $(BOOST_DATE_TIME_LIBS) $(BOOST_SYSTEM_LIBS) $(BOOST_ASIO_LIBS) \ $(BOOST_PROGRAM_OPTIONS_LIBS) $(WIN32_LIBS) $(CPPDB_LIBS) LIBS = $(DEPS_LIBS) # add the search path to the linker path #solarpowerlog_LDFLAGS = $(pwd) solarpowerlog-solarpowerlog-0.26/src/configuration/000077500000000000000000000000001444065341000226745ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/configuration/CConfigHelper.cpp000066400000000000000000000063271444065341000260600ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2014 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file CConfigHelper.cpp * * \date Jul 4, 2009 * \author Tobias Frost */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "configuration/CConfigHelper.h" #include "configuration/Registry.h" using namespace std; using namespace libconfig; #warning TODO Cache libconfig::settings in class and initialize in constructor CConfigHelper::CConfigHelper( const string& configurationpath, int index ) { cfgpath = configurationpath; if (index != -1 ) { char c[32]; snprintf( c, 31, ".[%d]", index ); cfgpath += c; } } CConfigHelper::CConfigHelper(const string& configurationpath, const string &element, int index) { // unfortunatly, delegating a constructor is only possible with C++11 and I // dont want to switch now... cfgpath = configurationpath + "." + element; if (index != -1) { char c[32]; snprintf(c, 31, ".[%d]", index); cfgpath += c; } } CConfigHelper::~CConfigHelper() { // TODO Auto-generated destructor stub } bool CConfigHelper::CheckConfig( const char *setting, libconfig::Setting::Type type, bool optional, bool printerr ) { libconfig::Config* cfg = Registry::Instance().Configuration(); string reason = ""; string tmp = cfgpath + "." + setting; if (!cfg->exists(tmp)) { if (optional) { return true; } if (printerr) reason = "was not found"; } else { try { libconfig::Setting & set = cfg->lookup(tmp); if (printerr) reason = "is of wrong type."; switch (type) { case Setting::TypeInt: case Setting::TypeFloat: case Setting::TypeInt64: { if (set.isNumber()) return true; break; } default: if (set.getType() == type) { return true; } break; } } catch (...) { if (printerr) reason = "cannot type-check: setting not found"; } } if (printerr) { string name = cfgpath; cfg->lookupValue(cfgpath + ".name", name); LOGERROR(Registry::GetMainLogger(), "Setting " << setting << " of " << name << " " << reason); } return false; } bool CConfigHelper::isExisting(void) const { libconfig::Config* cfg = Registry::Instance().Configuration(); return cfg->exists(cfgpath); } solarpowerlog-solarpowerlog-0.26/src/configuration/CConfigHelper.h000066400000000000000000000521701444065341000255220ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2014 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file CConfigHelper.h * * \date Jul 4, 2009 * \author Tobias Frost (coldtobi) */ /** \page Config_new Handling the Configuration * * \section ConfigNew_Overview Overview * * Solarpowerlog uses a configuration file to get the settings of the individual * components. It even decided out of the config file which components are * needed ans wich objects needs to work together. * * For easier handling of the configuration, solarpowerlog provides methods to * gather the settings. * * First, every object gets a so-called configuaration path. This is a string, * describing where the configuration of the object is located. (As currently * libconfig is used, this is the "path" of libconfig. * Every object should safe this path. * * Second, a helper class, CConfigHelper exists. This class has some methods * allowing to extract the configuration very conveniently: * - Check for existence of a key (including error reporting) * - Check for the type of a key (including error reporting) * - Retrieve the value * - Handle "optional" values by specifying the optional value to be used * if the configuration cannot be read. * * \section ConfigNew_Use Usage and Examples * * Without many words, here are the some examples. * * Example 1: Check for key type and existence. * \code * CConfigHelper hlp(configurationpath); * // Required Key: Will set fail=true if non-existent or wrong type * fail |= !hlp.CheckConfig("datasource", Setting::TypeString); * // Optional key: Will set fail=truel only if clearscreen is not boolean. * fail |= !hlp.CheckConfig("clearscreen", Setting::TypeBoolean, * true); * \endcode * * Example 2: Retrieve key * \code * CConfigHelper cfghlp(configurationpath); * float interval; * // This sets the value to 5.0 if not in the configuration: * cfghlp.GetConfig("queryinterval", interval, 5.0f); * // This would just set the value. if not configured, the value is left * // alone. * cfghlp.GetConfig("queryinterval", interval); * \endcode * * \note C++ has strong types. So make sure, that everything matches. * The above example would not compile, if you write "5.0" instead of "5.0f", * as gcc would make a "double" and interval would be "float". * This is also true for integers: int != unsigned int! * */ #ifndef CCONFIGHELPER_H_ #define CCONFIGHELPER_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include "configuration/Registry.h" using namespace std; /** Encapsulates helper-functions needed for easier handling of configuration * issues. * * The class allows: * - easier checking of types * - easier access of values, * - easier access of optional values (by setting default values) */ class CConfigHelper { public: /** Constructor * * \param configurationpath The path to the section of the * \param index (optional) to get enter a aggregate (by adding .[] to the array) * configuration file we want to evaluate. * (Usually you got this path by the factory generated your object) */ CConfigHelper( const string& configurationpath, int index= -1); CConfigHelper( const string& configurationpath, const string &element,int index= -1); virtual ~CConfigHelper(); /** Basic checks on configuration keys, with the support for optional parameters * * This function checks for the parameter associated with the * configuration-key (setting) for the existence (if not an optional parameter) * and for type-correctness. * * For mandatory options (specified by parameter optional=false) the key * must exists and be of the right datatype. * * Optional parameters needs not to be present, but when they are the * datatype must be suitable. * * If printerr is given as true, a found configuration error * (type, and existence of mandatory options) * will be logged using the mainlogger. * * The function checks if the configuration-key (parameter setting) * Mandatory options (optional=false, default) are checked for existence first, * optional options are only checked if the type if ok. * If printerr is given with true (default), the type of error is logged-. * * \returns true if the tests passed, else false * * \param setting configuration-key * \param type expected type * \param optional is it mandatory(false) or optional(true,default), * \param printerr if true, explain the error to the main logger * (default:false) */ bool CheckConfig( const char *setting, libconfig::Setting::Type type, bool optional = false, bool printerr = true ); /** Convenience function that checks if the setting fulfills the requirements * (type, optionality...) and if it does stores the value into the given * object. * * \returns false if the CheckConfig failed, true if CheckConfig succeeded. * * \note To have the default value option, just initialize your object with * the initial value before calling this member. It will not be changed if * an optional setting is not found. */ template bool CheckAndGetConfig(const char* setting, libconfig::Setting::Type type, T &store,bool optional = false, bool printerr = true ) { if (!CheckConfig(setting, type, optional, printerr)) { return false; } GetConfig(setting, store); return true; } #if 0 template bool CheckAndGetConfig(const string &setting, libconfig::Setting::Type type, T &store,bool optional = false, bool printerr = true ) { if (!CheckConfig(setting.c_str(), type, optional, printerr)) { return false; } GetConfig(setting, store); return true; } #endif #if 0 /** Specialization of the GetConfig routine for the type long. * This is necessary as libconfig-1.4.8 removes long support. * \returns false, if the default value has been used. */ bool GetConfig(string const &setting, long &store, long defvalue) { return GetConfig(setting.c_str(), store, defvalue); } #endif /** Specialization of the GetConfig routine for the type unsigned-long. * This is necessary as libconfig-1.4.8 removes long support. * * \returns false, if the default value has been used. */ bool GetConfig(char const *setting, unsigned long &store, unsigned long defvalue) { unsigned long long tmp; try { libconfig::Setting & set = Registry::Instance().GetSettingsForObject(cfgpath); if (!set.lookupValue(setting, tmp) || tmp > ULONG_MAX ) { store = defvalue; return false; } store = tmp; return true; } catch (...) { store = defvalue; return false; } } /** Specialization of the GetConfig routine for the type long. * This is necessary as libconfig-1.4.8 removes long support. * \returns false, if the default value has been used. */ bool GetConfig(char const *setting, long &store, long defvalue) { long long tmp; try { libconfig::Setting & set = Registry::Instance().GetSettingsForObject(cfgpath); if (!set.lookupValue(setting, tmp) || tmp > LONG_MAX) { store = defvalue; return false; } store = tmp; return true; } catch (...) { store = defvalue; return false; } } #if 0 /** Retrieves a configuration or set the config with a default value. * * \returns false, if the default value has been used. */ template bool GetConfig( string const &setting, T &store, T defvalue ) { return GetConfig(setting.c_str(), store, defvalue); } #endif /** GetConfig when using a char-array as setting -- see ##GetConfig doc * \returns false, if the default value has been used. */ template bool GetConfig(char const *setting, T &store, const T defvalue) { try { libconfig::Setting & set = Registry::Instance().GetSettingsForObject(cfgpath); if (!set.lookupValue(setting, store)) { store = defvalue; return false; } return true; } catch (...) { store = defvalue; return false; } } /** GetConfig for mandatory unsigned long settings, for libconfig 1.4.8 * \returns false, if the setting could not be retrieved. */ bool GetConfig(char const *setting, unsigned long &store) { unsigned long long tmp; try { libconfig::Setting & set = Registry::Instance().GetSettingsForObject(cfgpath); if (!set.lookupValue(setting, tmp) || tmp > ULONG_MAX) { return false; } store = tmp; return true; } catch (...) { return false; } } #if 0 /** GetConfig for mandatory settings. * \returns false, if the setting could not be retrieved. */ template bool GetConfig( const string &setting, T &store ) { return GetConfig(setting.c_str(),store); } #endif /** GetConfig for mandatory long settings, for libconfig 1.4.8 * \returns false, if the setting could not be retrieved. */ bool GetConfig(char const *setting, long &store) { try { long long tmp; libconfig::Setting & set = Registry::Instance().GetSettingsForObject(cfgpath); if (!set.lookupValue(setting, tmp) || tmp > LONG_MAX) { return false; } store = tmp; return true; } catch (...) { return false; } } /** GetConfig for mandatory settings, flavour "char*" * \returns false, if the setting could not be retrieved. */ template bool GetConfig(char const *setting, T &store) { try { libconfig::Setting & set = Registry::Instance().GetSettingsForObject(cfgpath); return set.lookupValue(setting, store); } catch (...) { return false; } } /** Get the configuration for an array entry, identified by a index. * * See Libconfig's docs for how the arrays working. * * With this helper, you can simply query the array by its index. * * \param [in] setting Setting-id * \param [in] index Index of the array * \param [out] store place where config is placed. * * \return true on success, false if not an array, wrong type or index * not existant. * */ template bool GetConfigArray(const char* setting, int index, T &store) { try { libconfig::Setting & set = Registry::Instance().GetSettingsForObject(cfgpath); store = set[setting][index]; return true; #if 0 } catch (libconfig::SettingNotFoundException &e) { return false; } catch (libconfig::SettingTypeException &e) { return false; } #else } catch (...) { return false; } #endif } /** Get the configuration for an array entry, identified by a index. * (std::string specialization) -> this is needed as for libconfig it would * be ambiguous for char* and std::string. * * See Libconfig's docs for how the arrays working. * * With this helper, you can simply query the array by its index. * * \param [in] setting Setting-id * \param [in] index Index of the array * \param [out] store place where config is placed. * * \return true on success, false if not an array, wrong type or index * not existant. * */ bool GetConfigArray(const char* setting, int index, string &store) { try { libconfig::Setting & set = Registry::Instance().GetSettingsForObject(cfgpath); store = (const char *)set[setting][index]; return true; #if 0 } catch (libconfig::SettingNotFoundException &e) { return false; } catch (libconfig::SettingTypeException &e) { return false; } #else } catch (...) { return false; } #endif } /** This is a ugly helper the CHTHML Writer -- we are having a list which * embeddes a array. The entries are anonymous (without a destinctive name). * I currently wonder how a better access function should look like ;-O) * TODO make this more elegant and reusable... * * so, well, this function looks up an entry in the embedded array, * with param i giving the row, index the column. * * \returns true on success (setting found, type ok), * false else (setting not found, type error) */ template bool GetConfigArray( const int i, int index, T &store ) { try { libconfig::Setting & set = Registry::Instance().GetSettingsForObject(cfgpath); store = (const char *) set[i][index]; return true; #if 0 } catch (libconfig::SettingNotFoundException &e) { return false; } catch (libconfig::SettingTypeException &e) { return false; } #else } catch (...) { return false; } #endif } /** this ugly helper is for the CHTHML Writer, for types that are strings. * See the other ##GetConfigArray for details. */ bool GetConfigArray( const int i, int index, string &store ) { try { libconfig::Setting & set = Registry::Instance().GetSettingsForObject(cfgpath); store = (const char *) set[i][index]; return true; #if 0 } catch (libconfig::SettingNotFoundException &e) { cerr << e.what(); return false; } catch (libconfig::SettingTypeException &e) { cerr << e.what(); return false; } #else } catch (...) { return false; } #endif } /** Return current configurationpath. */ const std::string & GetCfgPath(void) const { return cfgpath; } bool isExisting(void) const; /*** Check (type, existence) and assign value. This variant is for optional * parameters. This variant is necessary due to the ambiguousness of * char and std::string for libconfig. * * @param setting to look for * @param store where to store the result * @param defval what is the default value * @param logger where to log the error (if NULL, do not print the error) * @return true on success, false on "type error" */ bool CheckAndGetConfig(const char* setting, std::string &store, const std::string &defval, ILogger *logger = NULL) { libconfig::Config* cfg = Registry::Instance().Configuration(); string tmp = cfgpath + "." + setting; try { store = (const char *) cfg->lookup(cfgpath + "." + setting); } catch (libconfig::SettingNotFoundException &e) { if (logger) LOGINFO(*logger, "Setting " << setting << " was not found. Using default value: \"" << defval <<"\""); store = defval; } catch (libconfig::SettingTypeException &e) { if (logger) LOGERROR(*logger, "Setting " << setting << " is of wrong type"); return false; } return true; } /*** Check (type, existence) and assign value. This variant is for optional * parameters * * @param setting to look for * @param store where to store the result * @param defval what is the default value * @param logger where to log the error (if NULL, do not print the error) * @return true on success, false on "type error" */ template bool CheckAndGetConfig(const char* setting, T &store, const T &defval, ILogger *logger = NULL) { libconfig::Config* cfg = Registry::Instance().Configuration(); string tmp = cfgpath + "." + setting; try { store = cfg->lookup(cfgpath + "." + setting); } catch (libconfig::SettingNotFoundException &e) { if (logger) LOGINFO(*logger, "Setting " << setting << " was not found. Using default value: \"" << defval << "\""); store = defval; } catch (libconfig::SettingTypeException &e) { if (logger) LOGERROR(*logger, "Setting " << setting << " is of wrong type"); return false; } return true; } /** Convenience wrapper for CheckAndGetConfig() to take a std::string as * setting parameter * * @param setting to look for * * @param store where to store the result * @param defval what is the default value * @param logger where to log the error (if NULL, do not print the error) * @return true on success, false on "type error" */ template bool CheckAndGetConfig(const std::string &setting, T &store, const T &defval, ILogger *logger = NULL) { return CheckAndGetConfig(setting.c_str(), store, defval, logger); } /*** Check (type, existence) and assign value. This variant is for mandatory * parameters. This variant is necessary due to the ambiguousness of * char and std::string for libconfig. * * This function looks up a value and store it in the destination variable, * if the type of the setting is convertible. * * If libconfig cannot convert to the desired datatype, it will fail, * If the setting is not there, the default value will be used. * * @param setting to look for * @param store where to store the result * @param logger where to log the error (if NULL, do not print the error) * @return true on success, false on "type error" or "not found" */ bool CheckAndGetConfig(const char* setting, std::string &store, ILogger *logger = NULL) { libconfig::Config* cfg = Registry::Instance().Configuration(); string tmp = cfgpath + "." + setting; try { store = (const char*) cfg->lookup(tmp); } catch (libconfig::SettingNotFoundException &e) { if (logger) LOGERROR(*logger, "Required setting " << setting << " was not found."); return false; } catch (libconfig::SettingTypeException &e) { if (logger) LOGERROR(*logger, "Setting " << setting << " is of wrong type."); return false; } return true; } /*** Check (type, existence) and assign value. This variant is for mandatory * parameters. * * This function looks up a value and store it in the destination variable, * if the type of the setting is convertible. * * If libconfig cannot convert to the desired datatype, it will fail, * If the setting is not there, the default value will be used. * * @param setting to look for * @param store where to store the result * @param logger where to log the error (if NULL, do not print the error) * @return true on success, false on "type error" or "not found" */ template bool CheckAndGetConfig(const char* setting, T &store, ILogger *logger = NULL) { libconfig::Config* cfg = Registry::Instance().Configuration(); string tmp = cfgpath + "." + setting; try { store = cfg->lookup(tmp); } catch (libconfig::SettingNotFoundException &e) { if (logger) LOGERROR(*logger, "Required setting " << setting << " was not found."); return false; } catch (libconfig::SettingTypeException &e) { if (logger) LOGERROR(*logger, "Setting " << setting << " is of wrong type."); return false; } return true; } /** Convenience wrapper for CheckAndGetConfig() to take a std::string as * setting parameter * * @param setting to look for * @param store where to store the result * @param logger where to log the error (if NULL, do not print the error) * @return true on success, false on "type error" or "not found" * */ template bool CheckAndGetConfig(const std::string &setting, T &store, ILogger *logger = NULL) { return CheckAndGetConfig(setting.c_str(), store, logger); } private: string cfgpath; }; #endif /* CCONFIGHELPER_H_ */ solarpowerlog-solarpowerlog-0.26/src/configuration/ConfigCentral/000077500000000000000000000000001444065341000254125ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/configuration/ConfigCentral/CConfigCentral.cpp000066400000000000000000000032271444065341000307430ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2015 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file CConfigCentral.cpp * * Created on: 02.01.2015 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "CConfigCentral.h" bool CConfigCentral::CheckConfig(ILogger& logger, const std::string &configpath) { bool ret = true; CConfigHelper helper(configpath); std::list >::iterator it; for (it = l.begin(); it != l.end(); it++) { if (!(*it)->CheckAndUpdateConfig(logger,helper)) ret = false; } return ret; } std::string CConfigCentral::GetConfigSnippet() { std::string ret; std::list >::iterator it; for (it = l.begin(); it != l.end(); it++) { ret += (*it)->GetConfigSnippet(); ret += '\n'; } return ret; } solarpowerlog-solarpowerlog-0.26/src/configuration/ConfigCentral/CConfigCentral.h000066400000000000000000000157471444065341000304220ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2015 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /* \file CConfigCentral.h * * Created on: 02.01.2015 * Author: tobi * */ #ifndef SRC_CONFIGURATION_CCONFIGCENTRAL_H_ #define SRC_CONFIGURATION_CCONFIGCENTRAL_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "configuration/ILogger.h" #include "configuration/CConfigHelper.h" #include "CConfigCentralEntry.h" #include "CConfigCentralEntryRangeCheck.h" #include "CConfigCentralEntryText.h" /** Configuration Helper Class for better checking and documenting * configuration options for solarpowerlog * * It is cumbersome to keep documentation and code in sync. This class * should couple documentation with the code, and also make the checking of * the configuration and configuration error reporting more straight forward. * * The class is designed to be instantiated within the class to be configured, * so that the values can be directly placed into the class' variables (via * pointers) * */ class CConfigCentral { public: CConfigCentral() {}; virtual ~CConfigCentral() { } /** Add an textual entry (which can be used when dumping the help * to have an header, prequel text or like or parameters which are * (reported) aliases to others (like the typo "manufactor" instead of * "manufacturer") * * \param parameter parameter linked to this entry. Might be NULL. * \param description description, text... * \param example everyone loves examples... */ CConfigCentral& operator()(const char* parameter, const char *description, const char *example = NULL) { boost::shared_ptr p((IConfigCentralEntry*) new CConfigCentralEntryText(parameter, description, example)); l.push_back(p); return *this; } /** Add an setting describing entry (mandatory version) * * @param parameter setting's name * @param description setting's description * @param store where to store the value * @return object, so that the operator can be cascaded. */ template CConfigCentral& operator()(const char* parameter, const char* description, T &store) { boost::shared_ptr p((IConfigCentralEntry*) new CConfigCentralEntry(parameter, description, store)); l.push_back(p); return *this; } /** Add an setting describing entry (optional version with default value) * * @param parameter setting's name * @param description setting's description * @param store where to store the value * @param defaultvalue to use when the setting was not found. * @return object, so that the operator can be cascaded. */ template CConfigCentral& operator()(const char* parameter, const char* description, T &store, const T &defaultvalue) { boost::shared_ptr p((IConfigCentralEntry*) new CConfigCentralEntry(parameter, description, store, defaultvalue)); l.push_back(p); return *this; } /** Add an setting describing entry (mandatory version) with range-check * * @param parameter setting's name * @param description setting's description * @param store where to store the value * @param minimum range limit (including) * @param maximum range limit (including * @return object, so that the operator can be cascaded. */ template CConfigCentral& operator()(const char* parameter, const char* description, T &store, const T &minimum, const T &maximum) { boost::shared_ptr p( (IConfigCentralEntry*)new CConfigCentralEntryRangeCheck( parameter, description, store, minimum, maximum)); l.push_back(p); return *this; } /** Add an setting describing entry (optional version) with range-check * * @param parameter setting's name * @param description setting's description * @param store where to store the value * @param defaultvalue to use when setting has not been found * @param minimum range limit (including) * @param maximum range limit (including * @return object, so that the operator can be cascaded. */ template CConfigCentral& operator()(const char* parameter, const char* description, T &store, const T &defaultvalue, const T &minimum, const T &maximum) { boost::shared_ptr p( (IConfigCentralEntry*)new CConfigCentralEntryRangeCheck( parameter, description, store, defaultvalue, minimum, maximum)); l.push_back(p); return *this; } /** Parse the configuration and use the supplied logger to print errors. * * Store values into their targets (if given) * * @return true on success, false on config errors. */ bool CheckConfig(ILogger &logCConfigCentralEntryger, const std::string &configpath); /** Get a configuration snippet */ std::string GetConfigSnippet(void); /** Set or replace an example */ template void SetExample(const char *token, T newdefault, bool is_optional= true){ std::list >::iterator it; CConfigCentralEntry *ptr; IConfigCentralEntry *p; for (it = l.begin(); it != l.end(); it++) { p = (*it).get(); try { if ( 0 == strcmp(p->getSetting().c_str(),token)) { ptr = dynamic_cast *>(p); if (ptr) ptr->setDefvalue(newdefault, is_optional); return; } } catch(const std::bad_cast& ex) { // means a bug in the code! assert(false); } } // bug: one tries to add an example/default for an unknown setting. assert(false); } private: std::list > l; }; #endif /* SRC_CONFIGURATION_CCONFIGCENTRAL_H_ */ solarpowerlog-solarpowerlog-0.26/src/configuration/ConfigCentral/CConfigCentralEntry.cpp000066400000000000000000000056361444065341000317730ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2015 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /* * CConfigCentralEntry.cpp * * Created on: 04.01.2015 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include "CConfigCentralEntry.h" #include "CConfigCentral.h" template <> std::string CConfigCentralEntry::GetConfigSnippet() const { extern std::string _WrapForConfigSnippet(const std::string &description); std::string ret; // Wrap the desription ret = CConfigCentralHelpers::WrapForConfigSnippet(_description); std::stringstream ss; // print the optional / mandatory statement if (_optional) { ss << "This setting is optional." << std::endl; } else { ss << "This setting is mandatory." << std::endl; } ret += CConfigCentralHelpers::WrapForConfigSnippet(ss.str()); ss.str(""); // make a nice example if (_optional) ret += "# "; ret += _setting + " = "; if (_have_default_set) { //std::stringstream ss; ss << '"' << _defvalue << '"' << std::endl; ret += ss.str(); } else { ret += "\"\"\n"; } return ret; } template <> std::string CConfigCentralEntry::GetConfigSnippet() const { extern std::string _WrapForConfigSnippet( const std::string &description); std::string ret; // Wrap the desription ret = CConfigCentralHelpers::WrapForConfigSnippet(_description); // make a nice example std::stringstream ss; // print the optional / mandatory statement if (_optional) { assert(_have_default_set); // optional -- default must be set ss << "This setting is optional with a default value of " << (_defvalue ? "true" : "false"); } else { ss << "This setting is mandatory.\n"; } ret += CConfigCentralHelpers::WrapForConfigSnippet(ss.str()); if (_optional) ret += "# "; ret += _setting + " = "; if (_have_default_set) { if (_defvalue) ret += "true"; else ret += "false"; } else { ret += ""; } ret += ";\n"; return ret; } solarpowerlog-solarpowerlog-0.26/src/configuration/ConfigCentral/CConfigCentralEntry.h000066400000000000000000000104211444065341000314240ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2015 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /* * CConfigCentralEntry.h * * Created on: 04.01.2015 * Author: tobi */ #ifndef SRC_CONFIGURATION_CONFIGCENTRAL_CCONFIGCENTRALENTRY_H_ #define SRC_CONFIGURATION_CONFIGCENTRAL_CCONFIGCENTRALENTRY_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "IConfigCentralEntry.h" #include "configuration/CConfigHelper.h" #include "ConfigCentralHelpers.h" /*** Single setting/configuration entry. * */ template class CConfigCentralEntry : public IConfigCentralEntry { public: /** Constructor for mandatory parameters * * @param setting which setting * @param description description for the setting * @param store where to store the parsed value */ CConfigCentralEntry(const char* setting, const char* description, T &store) : IConfigCentralEntry(setting, description), _have_default_set(false), _optional(false), _store(store) {} /** Constructor for optional parameters * * @param setting which setting * @param description description for the setting * @param store where to store the parsed value * @param defaultvalue which value to use when the parameter was not found */ CConfigCentralEntry(const char* setting, const char* description, T &store, T defaultvalue) : IConfigCentralEntry(setting, description), _have_default_set(true), _optional(true), _store(store), _defvalue(defaultvalue) {} virtual ~CConfigCentralEntry() { } virtual bool CheckAndUpdateConfig(ILogger &logger, CConfigHelper &helper) const { if (_optional && _have_default_set) { return helper.CheckAndGetConfig(_setting.c_str(), _store, _defvalue, &logger); } else { return helper.CheckAndGetConfig(_setting, _store, &logger); } } std::string GetConfigSnippet() const { extern std::string _WrapForConfigSnippet(const std::string &description); std::string ret; // Wrap the desription ret = CConfigCentralHelpers::WrapForConfigSnippet(_description); std::stringstream ss; // print the optional / mandatory statement if (_optional) { assert(_have_default_set); // optional -- default must be set ss << "This setting is optional with a default value of " << _defvalue; } else { ss << "This setting is mandatory.\n"; } ret += CConfigCentralHelpers::WrapForConfigSnippet(ss.str()); // make a nice example if (_optional) ret += "# "; ret += this->_setting + " = "; if (_have_default_set) { //std::stringstream ss; ss << this->_defvalue; ret += ss.str(); } else { ret += ""; } ret += ";\n"; return ret; } T getDefvalue() const { return _defvalue; } void setDefvalue(T defvalue, bool is_optional=true) { _defvalue = defvalue; _optional = is_optional; _have_default_set = true; } protected: bool _have_default_set; bool _optional; T &_store; T _defvalue; }; // Forward declarations of the template specializations. template<> std::string CConfigCentralEntry::GetConfigSnippet() const; template <> std::string CConfigCentralEntry::GetConfigSnippet() const; #endif /* SRC_CONFIGURATION_CONFIGCENTRAL_CCONFIGCENTRALENTRY_H_ */ solarpowerlog-solarpowerlog-0.26/src/configuration/ConfigCentral/CConfigCentralEntryRangeCheck.cpp000066400000000000000000000020611444065341000336730ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2015 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /* * CConfigCentralEntryRangeCheck.cpp * * Created on: 04.01.2015 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "CConfigCentralEntryRangeCheck.h" // currently empty. solarpowerlog-solarpowerlog-0.26/src/configuration/ConfigCentral/CConfigCentralEntryRangeCheck.h000066400000000000000000000110141444065341000333360ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2015 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /* * CConfigCentralEntryRangeCheck.h * * Created on: 04.01.2015 * Author: tobi */ #ifndef SRC_CONFIGURATION_CONFIGCENTRAL_CCONFIGCENTRALENTRYRANGECHECK_H_ #define SRC_CONFIGURATION_CONFIGCENTRAL_CCONFIGCENTRALENTRYRANGECHECK_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "ConfigCentralHelpers.h" #include "CConfigCentralEntry.h" /*** Single setting/configuration entry. * */ template class CConfigCentralEntryRangeCheck : public CConfigCentralEntry { public: /** Constructor for mandatory parameters with range check * * @param setting which setting * @param description description for the setting * @param store where to store the parsed value * @param minimum allowed value (including) * @param maximum allowed value (including) */ CConfigCentralEntryRangeCheck(const char* setting, const char* description, T &store, const T &minimum, const T &maximum) : CConfigCentralEntry(setting, description, store), _min(minimum), _max(maximum) { } /** Constructor for optional parameters with range check * * @param setting which setting * @param description description for the setting * @param store where to store the parsed value * @param defaultvalue which value to use when the parameter was not found * @param minimum allowed value (including) * @param maximum allowed value (including) */ CConfigCentralEntryRangeCheck(const char* setting, const char* description, T &store, const T &defaultvalue, const T &minimum, const T &maximum) : CConfigCentralEntry(setting, description, store, defaultvalue), _min(minimum), _max(maximum) { } virtual ~CConfigCentralEntryRangeCheck() { } virtual bool CheckAndUpdateConfig(ILogger &logger, CConfigHelper &helper) const { bool ret; T old = this->_store; ret = CConfigCentralEntry::CheckAndUpdateConfig(logger, helper); if (!ret) { this->_store = old; return false; } if (this->_store > _max || this->_store < _min) { LOGERROR(logger, "Setting " << this->_setting << " is out of range (allowed range: " << _min << " to " << _max); this->_store = old; return false; } return true; } virtual std::string GetConfigSnippet() const { extern std::string _WrapForConfigSnippet( const std::string &description); std::string ret; // Wrap the desription ret = CConfigCentralHelpers::WrapForConfigSnippet(this->_description); std::stringstream ss; // print the range ss << "Valid values are from " << _min << " to " << _max << ".\n"; // print the optional / mandatory statement if (this->_optional) { assert(this->_have_default_set); // optional -- default must be set ss << "This setting is optional with a default value of " << this->_defvalue; } else { ss << "This setting is mandatory.\n"; } ret += CConfigCentralHelpers::WrapForConfigSnippet(ss.str()); // make a nice example if (this->_optional) ret += "# "; ret += this->_setting + " = "; if (this->_have_default_set) { std::stringstream ss; ss << this->_defvalue; ret += ss.str(); } else { ret += ""; } ret += ";\n"; return ret; } private: T _min; T _max; }; #endif /* SRC_CONFIGURATION_CONFIGCENTRAL_CCONFIGCENTRALENTRYRANGECHECK_H_ */ solarpowerlog-solarpowerlog-0.26/src/configuration/ConfigCentral/CConfigCentralEntryText.cpp000066400000000000000000000025071444065341000326320ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2015 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /* * CConfigCentralEntryText.cpp * * Created on: 04.01.2015 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "CConfigCentralEntryText.h" #include "ConfigCentralHelpers.h" std::string CConfigCentralEntryText::GetConfigSnippet() const { std::string ret = CConfigCentralHelpers::WrapForConfigSnippet(_description); if (!_setting.empty() && !_example.empty()) { ret += "# " + _setting + " = " + _example +";\n"; } return ret; } solarpowerlog-solarpowerlog-0.26/src/configuration/ConfigCentral/CConfigCentralEntryText.h000066400000000000000000000035431444065341000323000ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2015 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /* * CConfigCentralEntryText.h * * Created on: 04.01.2015 * Author: tobi */ #ifndef SRC_CONFIGURATION_CONFIGCENTRAL_CCONFIGCENTRALENTRYTEXT_H_ #define SRC_CONFIGURATION_CONFIGCENTRAL_CCONFIGCENTRALENTRYTEXT_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include "IConfigCentralEntry.h" /** Class for a description, without parameter. * * note: to get a */ class CConfigCentralEntryText : public IConfigCentralEntry { public: CConfigCentralEntryText(const char *setting, const char *description, const char *example) : IConfigCentralEntry(setting, description) { if (example) _example = example; } virtual bool CheckAndUpdateConfig(ILogger &, CConfigHelper &) const { return true; } virtual std::string GetConfigSnippet() const; virtual ~CConfigCentralEntryText() { } private: std::string _example; }; #endif /* SRC_CONFIGURATION_CONFIGCENTRAL_CCONFIGCENTRALENTRYTEXT_H_ */ solarpowerlog-solarpowerlog-0.26/src/configuration/ConfigCentral/ConfigCentralHelpers.cpp000066400000000000000000000105371444065341000321650ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2015 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /* * ConfigCentral_helpers.cpp * * Created on: 04.01.2015 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "IConfigCentralEntry.h" #include "ConfigCentralHelpers.h" #include std::string CConfigCentralHelpers::WrapForConfigSnippet(const std::string &description) { std::string ret; std::string tmp = description; // the parsed snipped should look like // # // remove trailing white spaces (this ensures that the last char is not // a whitespace, so we can safely look ahead later.) // first remove trailing spaces / newlines. std::string whitespaces(" \t\f\v\n\r"); std::size_t found = tmp.find_last_not_of(whitespaces); if (found != std::string::npos) tmp.erase(found + 1); else tmp.clear(); // is all whitespace? // iterate through string to remove double spaces size_t cursor = 0; std::string doublespace = " "; while (1) { cursor = tmp.find(doublespace, cursor); if (std::string::npos == cursor) break; tmp.erase(cursor, 1); } cursor = 0; size_t column = 0; size_t len = tmp.length() - 1; // iterate through remaining string. while (len > cursor) { size_t s; // Add trailing "# " if (0 == column && len > cursor) { ret += "# "; column = 2; } if (tmp[cursor] == ' ') { cursor++; continue; } // look for newlines that shold break actual line s = tmp.find('\n', cursor); if (std::string::npos != s && (s - cursor) < (WRAP_AT - column)) { ret += tmp.substr(cursor, s - cursor + 1); column = 0; cursor = s + 1; continue; } // is the remaining line shorter than we would need to break anyway? s = len - cursor; if (s < (WRAP_AT - column)) { ret += tmp.substr(cursor); ret += '\n'; break; } // find the spot to break // cursor = last character parsed // column = horizontal position // lets keep the followin debug aid for a moment: //std::cerr << "len=" << tmp.length() <<"column=" << column << " cursor=" << cursor << " " << std::endl; s = tmp.find_last_of(whitespaces, cursor + WRAP_AT - column); // s == npos means that we've got a monster string without spaces. // also if s is smaller than the current cursor position. if (std::string::npos == s || s < cursor) { s = tmp.find_first_not_of(whitespaces, cursor); if (s != std::string::npos) { if (s == cursor) { // The next whitespace is at the cursor. // we have to get the complete monster-word... s = tmp.find_first_of(whitespaces,cursor+1); if ( s == std::string::npos) s=tmp.length()-1; } ret += tmp.substr(cursor, s - cursor); cursor = s; continue; } else { ret += tmp.substr(cursor); ret += "\n"; break; } } else { // s is > cursor and our wrap position ret += tmp.substr(cursor, s - cursor); cursor = s; ret += "\n"; column = 0; } } return ret; } solarpowerlog-solarpowerlog-0.26/src/configuration/ConfigCentral/ConfigCentralHelpers.h000066400000000000000000000032551444065341000316310ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2015 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /* * ConfigCentralHelpers.h * * Created on: 04.01.2015 * Author: tobi */ #ifndef SRC_CONFIGURATION_CONFIGCENTRAL_CONFIGCENTRALHELPERS_H_ #define SRC_CONFIGURATION_CONFIGCENTRAL_CONFIGCENTRALHELPERS_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include /** some helper functions. * */ class CConfigCentralHelpers { private: // private constructor -- we do not want objects of this. CConfigCentralHelpers() {} virtual ~CConfigCentralHelpers() {} public: /// wrap the string at WRAP_AT characters. /// add "# " before each line to mimic configuration syntax static std::string WrapForConfigSnippet(const std::string &description); /// defines where to wrap. static const int WRAP_AT=79; }; #endif /* SRC_CONFIGURATION_CONFIGCENTRAL_CONFIGCENTRALHELPERS_H_ */ solarpowerlog-solarpowerlog-0.26/src/configuration/ConfigCentral/IConfigCentralEntry.cpp000066400000000000000000000017741444065341000320000ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2015 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /* * IConfigCentralEntry.cpp * * Created on: 04.01.2015 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #endif // currently empty. solarpowerlog-solarpowerlog-0.26/src/configuration/ConfigCentral/IConfigCentralEntry.h000066400000000000000000000045531444065341000314430ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2015 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /* * IConfigCentralEntry.h * * Created on: 04.01.2015 * Author: tobi */ #ifndef SRC_CONFIGURATION_CONFIGCENTRAL_ICONFIGCENTRALENTRY_H_ #define SRC_CONFIGURATION_CONFIGCENTRAL_ICONFIGCENTRALENTRY_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include class CConfigHelper; class ILogger; /** Interface class for config entries. * */ class IConfigCentralEntry { public: IConfigCentralEntry(const char* setting, const char* description) { if (setting) _setting = setting; if (description) _description = description; } virtual ~IConfigCentralEntry() { } /** Check the config entry and if ok update the target value. * * @param logger where to log errors * @param helper where to retrive the settings. * @return true if setting ok, false if something is wrong. The reason is * logged using LOGERROR(logger,...) */ virtual bool CheckAndUpdateConfig(ILogger &logger, CConfigHelper &helper) const = 0; /** Get the configuration options pretty-printed to automatically generate * config snippets. */ virtual std::string GetConfigSnippet() const = 0; const std::string& getSetting() const { return _setting; } /** Get the configuration options pretty-printed to suitable for help2man */ // virtual std::string& GetHelp() const = 0; protected: std::string _setting; std::string _description; }; #endif /* SRC_CONFIGURATION_CONFIGCENTRAL_ICONFIGCENTRALENTRY_H_ */ solarpowerlog-solarpowerlog-0.26/src/configuration/ILogger.cpp000066400000000000000000000175041444065341000247370ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2015 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file ILogger.cpp * * Created on: Jul 20, 2009 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "configuration/ILogger.h" #ifdef HAVE_LIBLOG4CXX #include "configuration/CConfigHelper.h" #include "interfaces/CMutexHelper.h" #include using namespace std; /// sbdm hash function -- public domain /// used to static uint32_t runtime_hash(const char *str) { uint32_t hash = 0; int c; while ((c = *str++)) hash = c + (hash << 6) + (hash << 16) - hash; return hash; } #define MYLINE(x) #x #define MYLINE2(x) MYLINE(x) #define MYLINE3 MYLINE2(__LINE__) /** Construct a logger object with default settings. * * When using this constructor, the logger will attach to the root * logger by default. This can be overridden by a subsequent call Setup() */ ILogger::ILogger() { // if not overridden by setup, always log to the root logger. loggerptr_ = log4cxx::Logger::getRootLogger(); currentlevel = currentloggerlevel_ = loggerptr_->getLevel()->toInt(); sa_max_suppress_repetitions_ = LOG_STATEWARE_REPEAT; sa_max_time_suppress_ = LOG_STATEWARE_TIME; } /** Setup logger in the logger, attaching to a parent. * * The parent logger and the specialization forms the path of the new logger * for easier identification of the source of the message. The new logger is * put one level deeper than its parent. * * For example, Inverter_1 adds a Comm which specalizsation of "Comms_TCP_ASIO", * the resulting logger is Inveter_1.Comms_TCP_ASIO * * The logging level will be deducted from the parent, (default * from log4cxx). * * \param parent logger to attach from. Must not be empty. * \param specialization for the logger. (the name of the new one) */ void ILogger::Setup(const std::string & parent, const std::string & specialization) { if (specialization.empty()) { loggername_ = parent; } else { loggername_ = parent + "." + specialization; } log4cxx::LoggerPtr logger(log4cxx::Logger::getLogger(loggername_)); log4cxx::LevelPtr ptr = logger->getEffectiveLevel(); currentloggerlevel_ = ptr->toInt(); loggerptr_ = logger; } /** Setup a logger. Do not associate to a parent * * (of course, the root logger is always parent) * * This variant creates a logger with the path
. *
is the name of the section in the configuration file, * the name of the object, * * The Settings for the logger are extracted from the configuration * file and with this precedence: * - Specification in XML file (liblog4cxx config) * -
. enry dbglevel in the solarpowerlog.conf * - application.dbglevel in the solarpowerlog.conf * - "ERROR" if nothing is given, * * \param name of the new logger * \param configuration path where to obtain te objects config. * */ void ILogger::Setup(const string & name, const string & configuration, const string& section) { config_ = configuration; loggername_ = section + "." + name; log4cxx::LoggerPtr logger(log4cxx::Logger::getLogger(loggername_)); loggerptr_ = logger; // check if the logger has magically already a level. // if so, it must be from XML. log4cxx::LevelPtr ptr = logger->getLevel(); if (!ptr) { // no, no level set. // first try to retrieve specific setting, then global setting and // if both fails, just keep using the root loggers setup (which is setup // in the constructor) string level; bool success; CConfigHelper hlp(configuration); success = hlp.GetConfig("dbglevel", level); if (!success) { CConfigHelper global("application"); success = global.GetConfig("dbglevel", level, (std::string)"ERROR"); } if (success) { logger->setLevel(log4cxx::Level::toLevel(level)); currentloggerlevel_ = logger->getLevel()->toInt(); } } } void ILogger::SetLoggerLevel(log4cxx::LevelPtr level) { loggerptr_->setLevel(level); currentloggerlevel_ = level->toInt(); } void ILogger::Log_sa(const int32_t hash, std::stringstream &ss) { bool needlog = false; uint reason = 0; time_t now = time(NULL); uint32_t strhash = runtime_hash(ss.str().c_str()); // LL_ALL disables this feature. if (this->IsEnabled(ILogger::LL_ALL)) { (*this) << ss; return; } CMutexAutoLock cma(*this); std::unordered_map::iterator it; it = sa_info.find(hash); if (it != sa_info.end()) { // check for suppression criteria. struct log_stateaware_info &info = (*it).second; do { if (strhash != info.hash) { needlog = true; reason = 1; break; } if (sa_max_suppress_repetitions_ && (info.supressed_cnt >= sa_max_suppress_repetitions_)) { needlog = true; reason = 2; break; } if (sa_max_time_suppress_ && ((info.last_seen + sa_max_time_suppress_) <= now)) { needlog = true; reason = 3; break; } } while (0); if (!needlog) { // supress message. info.supressed_cnt++; return; } // print message and update info std::stringstream ss2; if (reason == 1) { if (!info.supressed_cnt) { reason = 0; } else { ss2 << "note: " << info.supressed_cnt << " messages in this context have been suppressed."; } } if (reason == 2) { ss2 << info.supressed_cnt << " duplicate messages have been suppressed"; } if (reason == 3 && info.supressed_cnt) { ss2 << "repeating after " << info.supressed_cnt << " duplicate messages have already been suppressed for " << now - info.last_seen << " seconds"; } else { reason = 0; } info.hash = strhash; info.supressed_cnt = 0; info.last_seen = now; cma.unlock(); (*this) << ss; if (reason) (*this) << ss2; return; } // data not in map. // means we need to print it and create an entry. struct log_stateaware_info newinfo; newinfo.hash = strhash; newinfo.supressed_cnt = 0; newinfo.last_seen = now; sa_info[hash] = newinfo; cma.unlock(); (*this) << ss; return; } /// Forget the history for one stateaware log entry /// (the next one will be issued anyway) bool ILogger::sa_forgethistory(int32_t hash) { CMutexAutoLock cma(*this); return sa_info.erase(hash); } /// Forget the history for all stateaware log entries /// (that means complete reset) void ILogger::sa_forgethistory() { CMutexAutoLock cma(*this); sa_info.clear(); } ILogger::~ILogger() { // TODO Auto-generated destructor stub } #endif solarpowerlog-solarpowerlog-0.26/src/configuration/ILogger.h000066400000000000000000000461161444065341000244050ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2014 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file ILogger.h * * Created on: Jul 20, 2009 * Author: tobi * * \section LoggingLevels Loggging Levels * * Here some guideline to choose the right logging level to keep consistent * throughout the program. * * - TRACE: Very verbatim (debug purpose) infos, like protocol, telegramms, * low level data .... This level should be used to gather every information * that might be needed to debug problems... Milestones during execution * also counts to this. The goal is to find out whats happening if debugging * some rare problems. * - DEBUG: "Regular" Debug infos, like tracepoints, unusual program flow * detection, etc. Detected problems that are likely a programming problem.... * - INFO: Verbatim information targeted to the user, showing details of * the program flow, but not too much details. * (showing when talking to a inverter, ...) * - WARN: This level indicates a minor problems, caused by external events. * Usually some functions might be temporary not available. * - ERROR: The program cannot function under this circumstances. The feature * imposed will not be available until the reason is fixed and the program * restarted. * The program can usually continue to execute, but with the limitations. * - FATAL: The program detected a problem which makes it impossible to * continue. The program will usually call abort() after FATAL. * * \section LoggingStateAware State-aware logging * * This new feature allows to keep track of already issued log messages, and * suppress duplicate messages. This feature is not used by default, there * are dedicated macros: They have the suffix _SA for state aware. * * Those macros take an additional parameter, which is supposed to be some hash * value to distinguish the logging location. * The hash value can be generated by e.g. __COUNTER__ or also from strings * using the macro LOG_SA_HASH("somestring"). The nice thing about this macro * is that the hash is computed at compile time, so there is no runtime overhead * However, (to make that work, optimization needs to be turned on, for the gcc * -O1 seems to be sufficient. * Due to the compile-time-impact, __COUNTER__ should be preferred whenever * possible. * * NOTE: The compile-time hash must be constant. (They are compile-time anyway; * but the API doesn't enforce this -- if you bring a new constant every time * you call the macro, you'll have a nice memory leak as noone is cleaning * up after you. * * When a code location using the same hash emits identical log messages, the * log messages will be not printed but suppressed, when: * - the log message is identical * - the log message has not been repeated xx times (where xx is by default * LOG_STATEWARE_REPEAT) This can be set per-Logger via * setSaMaxSuppressRepeattions() * - the log message has not been xx seconds (xx per default * LOG_STATEAWARE_TIME) -- set-able via setSaMaxSupressTime() * * The state-awareness is bound to the ILogger object and is also thread-safe. * * Implementation details: As already indicated, a hash is used to correlate * logging location with the suppression feature. To keep track of logging content, * a 32-bit hash is calculated from the string and stored, using sdbm algorithm, * The information about seen logging info is stored in a stdlib container: * Currently map, later maybe unordered_map, as this is faster in looking up * the hashes (but needs C++11) * * As this is only logging, hash-collisions are neglected, but probability is * low anyway: If the compile-time hash collides, only suppression will * most likely be not working, as *additionally* the hashes on the * strings would need to collide. * * A word on the compile-time: The hash-calculation has been configured for * strings up to 96 bytes. Larger strings will be truncated and if the truncated * string is equal, they will collide. This limitation is necessary as the * compile-time hash works with macro expansion and the needed time raises * exponentially with the limit. For example, while benchmarking, the * compile time for ILogger.cpp raised from 2 secs -> 4 secs -> 7 secs -> * 12 secs -> 90 secs for "no hash" -> 64 bytes -> 96 byts -> 128 bytes limit; * calculating 10 hashes of "ILogger::ILogger()" each. * * * Examples: * LOG_WARN_SA(logger, __COUNTER__, "I do not want to repeat myself!"); * * * if (!xy_available) { * LOG_WARN_SA(logger, LOG_SA_HASH("on_xy_avail", "XY is not available."); * // (...) * } * * // 1000's line of code later or the second if even in another part of the class * if (!xy_available) { * LOG_WARN_SA(logger, LOG_SA_HASH("on_xy_avail", "XY is available."); * // (...) * } * * * // use of __COUNTER__ to keep track * const int sa_zz_hash = __COUNTER__; * if (!zz_available) { * LOG_WARN_SA(logger, sa_zz_hash, "ZZ is not available."); * // 1000's line of code * } else { * LOG_WARN_SA(logger, sa_zz_hash, "ZZ is available."); * } * * // oneliner -- will log only if x changes. * LOG_WARN_SA(logger, __COUNTER__, "The value of x is " << x); * */ #ifndef ILOGGER_H_ #define ILOGGER_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include // unordered maps are not available without c++11 #if __cplusplus < 201103L #include #else #include #endif #ifdef HAVE_LIBLOG4CXX #include #endif #ifdef HAVE_LIBLOG4CXX #include /// Define for default number of repeations before a message is repeated #define LOG_STATEWARE_REPEAT (100) /// Define for default time before a already issued message is repeated #define LOG_STATEWARE_TIME (3600) // Compile-Time-Hashing function for state-aware logging // See http://www.chrissavoie.com/articles/15-research/14-hashing // Eclipse is getting very slow with the recursive macros, so I excluded to // index the macros.. This "hack" configures some replacement.. // (Eclipse sets HAVE_CONIG_H to "42", while configure won't) // Also coverity will choke on this ... #ifdef __COVERITY__ #define LOG_SA_HASH(string) (__LINE__) #elif (HAVE_CONFIG_H != 42) #include "ILogger_hashmacro.h" #else #error eclipse hack! should not compile #define LOG_SA_HASH(string) (__LINE__) #endif // the state-aware logging macros are only available for levels below (incl.) ERROR. #define LOGERROR_SA(logger, hash, message) do {\ if ((logger).IsEnabled(ILogger::LL_ERROR)) { \ std::stringstream ss;\ ss << message;\ logger.Log_sa(hash,ss); \ }\ } while(0) #define LOGWARN_SA(logger, hash, message) do {\ if ((logger).IsEnabled(ILogger::LL_WARN)) { \ std::stringstream ss;\ ss << message;\ logger.Log_sa(hash,ss); \ }\ } while(0) #define LOGINFO_SA(logger, hash, message) do {\ if ((logger).IsEnabled(ILogger::LL_INFO)) { \ std::stringstream ss;\ ss << message;\ logger.Log_sa(hash,ss); \ }\ } while(0) #define LOGDEBUG_SA(logger, hash, message) do {\ if ((logger).IsEnabled(ILogger::LL_DEBUG)) { \ std::stringstream ss;\ ss << message;\ logger.Log_sa(hash,ss); \ }\ } while(0) #define LOGTRACE_SA(logger, hash, message) do {\ if ((logger).IsEnabled(ILogger::LL_TRACE)) { \ std::stringstream ss;\ ss << message;\ logger.Log_sa(hash,ss); \ }\ } while(0) #define LOGALL_SA(logger, hash, message) do {\ if ((logger).IsEnabled(ILogger::LL_ALL)) { \ std::stringstream ss;\ ss << message;\ logger.Log_sa(hash,ss); \ }\ } while(0) #define LOGFATAL(logger, message) do {\ if ((logger).IsEnabled(ILogger::LL_FATAL)) { \ std::stringstream ss;\ ss << message;\ logger << ss;\ }\ } while(0) #define LOGERROR(logger, message) do {\ if ((logger).IsEnabled(ILogger::LL_ERROR)) { \ std::stringstream ss;\ ss << message;\ logger << ss;\ }\ } while(0) #define LOGWARN(logger, message) do {\ if ((logger).IsEnabled(ILogger::LL_WARN)) { \ std::stringstream ss;\ ss << message;\ logger << ss;\ }\ } while(0) #define LOGINFO(logger, message) do {\ if ((logger).IsEnabled(ILogger::LL_INFO)) { \ std::stringstream ss;\ ss << message;\ logger << ss;\ }\ } while(0) #define LOGDEBUG(logger, message) do {\ if ((logger).IsEnabled(ILogger::LL_DEBUG)) { \ std::stringstream ss;\ ss << message;\ logger << ss;\ }\ } while(0) #define LOGTRACE(logger, message) do {\ if ((logger).IsEnabled(ILogger::LL_TRACE)) { \ std::stringstream ss;\ ss << message;\ logger << ss;\ }\ } while(0) #define LOGALL(logger, message) do {\ if ((logger).IsEnabled(ILogger::LL_ALL)) { \ std::stringstream ss;\ ss << message;\ logger << ss;\ }\ } while(0) #else #define LOGWARN_SA(logger, hash, message) #define LOGINFO_SA(logger, hash, message) #define LOGDEBUG_SA(logger, hash, message) #define LOGTRACE_SA(logger, hash, message) #define LOGALL_SA(logger, hash, message) #define LOGFATAL(logger, message) do { \ cerr << message << endl;\ } while(0) #define LOGERROR(logger, message) do { \ cerr << message << endl;\ } while(0) #define LOGWARN(logger, message) #define LOGINFO(logger, message) #define LOGDEBUG(logger, message) #define LOGTRACE(logger, message) #define LOGALL(logger, message) #endif #ifdef HAVE_LIBLOG4CXX /// struct to keep state for stateaware logging . struct log_stateaware_info { uint32_t hash; int supressed_cnt; time_t last_seen; }; /** Interface for logging services * * This class is the interface to the underlying logging class. * (planned: log4cxx) * * Loggers can be attached to every object, and log4cxx allows to structure * them into a hierarchy. * * This class is responsible to give every object access to its own logger, * and extract the logger's configuration out of the configuration file. * (allowing to configure the log for each component individually) * * The class is intended to use a composition or by inheritance. * */ class ILogger : protected boost::mutex { public: enum level { LL_OFF = log4cxx::Level::OFF_INT, LL_FATAL = log4cxx::Level::FATAL_INT, LL_ERROR = log4cxx::Level::ERROR_INT, LL_WARN = log4cxx::Level::WARN_INT, LL_INFO = log4cxx::Level::INFO_INT, LL_DEBUG = log4cxx::Level::DEBUG_INT, LL_TRACE = log4cxx::Level::TRACE_INT, LL_ALL = log4cxx::Level::ALL_INT }; /** Configure the logger with a name (to identify) , * the configuration string (for retrieving logger config) * and a section (under what hierarchy to place the logger) * * \param name of the logger * \param configurationpath where to retrieve the config * \param section where to place the logger * */ void Setup( const std::string &name, const std::string &configuration, const std::string& section ); /** Adding a logger in a lower hierarchy level (below a parent object) * by just specifying the parent. * * This logger will inheritate all settings by its parent, or, the XML * file might configure it. * */ void Setup( const std::string &parent, const std::string &specialization ); /** the default constructor set up logging with the root logger. */ ILogger(); virtual ~ILogger(); /// Getter for loggername inline std::string getLoggername() const { return loggername_; } /// Modify the logger level (at runtime) void SetLoggerLevel(log4cxx::LevelPtr level); /** Check if a logging statement would go through and * if so setup logging level. * * This function should be used before using the << operator, as * this function will avoid calling all the ostream-operators, * when indeed the logging level is below that one configured * for the logger, * * The LOG_xxx macros will do that for you. * * Example: * \code * if (logger->IsEnabled(FATAL)) logger << "Fatal Error occured" <= currentloggerlevel_) { currentlevel = loglevel; return true; } else return false; } /// set the next loglevel (the level which the app will log with the next time) /// usually this is not needed, as IsEnabled and LOG_xxx() do that for you /// /// \param loglevel the level for subsequent logging. /// /// \note: Please use LOG_xxx whenever possible. inline void SetLogLevel( int loglevel ) { currentlevel = loglevel; } /// Log a string with the level. /// /// \param loglevel level to log with /// \param log string to log /// \note when logging a static string, this might be more performant. /// than LOG_xxx. inline void Log( int loglevel, const std::string &log ) { if (IsEnabled(loglevel)) loggerptr_->log(log4cxx::Level::toLevel(loglevel),log); } /** provides the << operator for convenient logging (std::string version) * * \note the loglevel of the message has to be setup prior logging * * \note Use LOG_xxx() macros whenever possible. */ std::string & operator <<( std::string &os ) { if (currentlevel >= currentloggerlevel_) { loggerptr_->forcedLog(log4cxx::Level::toLevel( currentlevel), os); } return os; } /** provides the << operator for convenient logging (stringstream version) * * \note the loglevel of the message has to be setup prior logging * * \note Use LOG_xxx() macros whenever possible. */ std::stringstream & operator <<( std::stringstream &os ) { if (currentlevel >= currentloggerlevel_) { std::string s = os.str(); loggerptr_->forcedLog(log4cxx::Level::toLevel( currentlevel), s); } return os; } /** State-aware logging * * This logging function will keep track on state-aware logging messages, * and suppress those messages when the content has not been changed, or * the message has either been repeated LOG_STATEWARE_REPEAT or not issued * since LOG_STATEAWARE seconds. * The */ void Log_sa(const int32_t hash, std::stringstream &ss); /// Get the max suppressions int getSaMaxSuppressRepetitions() const { return sa_max_suppress_repetitions_; } /** Set the maximum repetitions to suppress identical logs * * @param saMaxTimeSuppress maximum time to suppress identical messages. * "0" disables the repeating due to repetitions. */ void setSaMaxSuppressRepetitions(int saMaxSuppressRepetitions) { sa_max_suppress_repetitions_ = saMaxSuppressRepetitions; } /// Get the maximum time to suppress identical logs time_t getSaMaxSuppressTime() const { return sa_max_time_suppress_; } /** Set the maximum time suppressing identical logs * * @param saMaxTimeSuppress maximum time to suppress identical messages. * "0" disables repeating due to time-constraints */ void setSaMaxSuppressTime(time_t saMaxTimeSuppress) { sa_max_time_suppress_ = saMaxTimeSuppress; } /// Forget the history for one stateaware log entry /// (the next one will be issued anyway) bool sa_forgethistory(int32_t hash); /// Forget the history for all stateaware log entries /// (that means complete reset) void sa_forgethistory(); private: /// cache for the configuration string. std::string config_; /// cache for the loggers name. std::string loggername_; /// The logger of liblog4cxx... log4cxx::LoggerPtr loggerptr_; /// the last set log level from the application ("I want to log at this level next") int currentlevel; /// stores the logging level of the underlaying logger (cache) /// FIXME: Think about removing that one... /// should be queried from the logger, or at least updated when setting the level. int currentloggerlevel_; /// stateaware: repeat every this times when duplicates have found int sa_max_suppress_repetitions_; /// stateaware: repeat even when a duplicate after this time time_t sa_max_time_suppress_; /// Contains ths state-aware-logging data std::unordered_map sa_info; }; #endif // HAVE_LIBLOG4CXX #ifndef HAVE_LIBLOG4CXX /** Interface for logging services * * This class is the interface to the underlying logging class. * (planned: log4cxx) * * Loggers can be attached to every object, and log4cxx allows to structure * them into a hierarchy. * * This class is responsible to give every object access to its own logger, * and extract the logger's configuration out of the configuration file. * (allowing to configure the log for each component individually) * * The class is intended to use a composition or by inheritance. * */ class ILogger /*: public std::ostream*/ { public: enum level { LL_OFF = 0, LL_FATAL , LL_ERROR , LL_WARN , LL_INFO , LL_DEBUG , LL_TRACE , LL_ALL }; /** Configure the logger with a name (to identify) , * the configuration string (for retrieving logger config) * and a section (under what hierarchy to place the logger) * * \param name of the logger * \param configurationpath where to retrieve the config * \param sectin where to place the logger * */ void Setup( const std::string &, const std::string &, const std::string& ) { }; /** Adding a logger in a lower hierarchy level (below a parent object) * by just specifying the parent. * * This logger will inheritate all settings by its parent, or, the XML * file might configure it. * */ void Setup( const std::string &, const std::string & ) {}; /** the default constructor set up logging with the root logger. */ ILogger() {}; virtual ~ILogger() {}; std::string getLoggername() const { return ""; } /** Check if a logging statement would go through and * if so setup logging level. * * This function should be used before using the << operator, as * this function will avoid calling all the ostream-operators. * * Example: * \code * if (logger->IsEnabled(FATAL)) logger << "Fatal Error occured" < #include // From https://gist.github.com/Lee-R/3839813 , Public Domain from Lee-R: "Consider it public domain. " namespace compiletime_string_hash { // FNV-1a 32bit hashing algorithm. constexpr std::uint32_t fnv1a_32(char const* s, std::size_t count) { return ((count ? fnv1a_32(s, count - 1) : 2166136261u) ^ s[count]) * 16777619u; } } constexpr std::uint32_t LOG_SA_HASH(const char* string) { return compiletime_string_hash::fnv1a_32(string, std::strlen(string)); } static_assert(LOG_SA_HASH("0123456789ABCDEF") == 141695047u); #endif // ILOGGER_HASHMACRO_H solarpowerlog-solarpowerlog-0.26/src/configuration/Registry.cpp000066400000000000000000000110161444065341000252070ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /* * Registry.cpp * * Created on: May 9, 2009 * Author: tobi * * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include "configuration/Registry.h" #include "interfaces/CWorkScheduler.h" #include "Inverters/interfaces/InverterBase.h" using namespace std; /** constructor for the registry. */ Registry::Registry() { Config = NULL; mainscheduler = NULL; } bool Registry::LoadConfig( std::string name ) { if (Config) delete Config; Config = new libconfig::Config; // set include dir the directory, if name has a complete path. // this allows using @include with relative paths to the main config file. char *tmp = strdup(name.c_str()); char *tmp2 = tmp; tmp = dirname(tmp); if (tmp && strcmp(tmp,".")) Registry::Configuration()->setIncludeDir(tmp); free(tmp2); try { Config->readFile(name.c_str()); } catch (libconfig::ParseException &ex) { std::cerr << "Error parsing configuration file " << name << " at Line " << ex.getLine() << ". (" << ex.getError() << ")" << std::endl; delete Config; Config = 0; return false; } catch (libconfig::FileIOException &ex) { std::cerr << "Error parsing configuration file " << name << ". IO Exception " << std::endl; delete Config; Config = 0; return false; } // Be more sloppy on datatypes -> automatically convert if possible. Config->setAutoConvert(true); return true; } void Registry::FakeConfig(void) { static const std::string defaultconfig = "application: { \ndbglevel = \"OFF\"\n}\n" "inverters: " "{ " "inverters = (" "{ }" ")" "}" "loggers: " "{ " "inverters = (" "{ }" ")" "}"; if (Config) delete Config; Config = new libconfig::Config; Config->readString(defaultconfig); } libconfig::Setting & Registry::GetSettingsForObject( std::string section, std::string objname ) { assert(Config); libconfig::Setting &s = Config->lookup(section); if (objname == "") return s; for (int i = 0; i < s.getLength(); i++) { std::string tmp = s[i]["name"]; if (tmp == objname) return s[i]; } // note: we cannot deliver a object here ... we simply do not have one! // As libconfig::SettingNotFoundException is private only, we also cannot // throw here. So, as convention, we return the root of the configuration here.... // We "BUG" here, as it is the responsibility of the caller to ensure the // objects existence. // (Only Objects with a valid name should query their configuration) std::cerr << "BUG: " << __FILE__ << ":" << __LINE__ << " --> Queried for unknown Object " << objname << " in section " << section << std::endl; assert(false); return Config->getRoot(); } IInverterBase *Registry::GetInverter( const string & name ) const { list::const_iterator iter; for (iter = inverters.begin(); iter != inverters.end(); iter++) { if ((*iter)->GetName() == name) return (*iter); } return 0; } void Registry::AddInverter( const IInverterBase *inverter ) { assert (inverter); inverters.push_back((IInverterBase*) inverter); } /** destructor */ Registry::~Registry() { if (Config) delete Config; Config = NULL; if (mainscheduler) delete mainscheduler; } void Registry::Shutdown(void) { // Clear datafilters and inverters. std::list::iterator it; for (it = inverters.begin(); it != inverters.end(); it++) { delete *(it); } inverters.clear(); // shutdown mainscheduler. delete mainscheduler; mainscheduler = NULL; // delete config. delete Config; Config = NULL; } solarpowerlog-solarpowerlog-0.26/src/configuration/Registry.h000066400000000000000000000216051444065341000246610ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** * \file Registry.h * * \page The class Registry: Configuration, Scheduler and Object Database * * \date Created on: May 9, 2009 * \author Tobias Frost (coldtobi) * * \section LibConfig The Configuration Concept * * \sa \ref Registry * * Solarpowerlog uses libconfig as configuration backend. * * On startup it loads a file and extracts the configuration for the individual * components. * * The settings are structured to the indivdual sections, like general settings, * settings for inverters, settings for the loggers. * * The program parses the basic layout of configuration. * * Out of the infos stored in configuration, the program generates a number of * objects. * * The generated object will be told its own configuration path (in libconfig * notation) to allow it to extract its own configuration. (This makes us the * the live easier, as we do not need to care which configuration items a object * requires: It knows best.) * * \section Registry Registry: The Object Register * * \sa Registry * * The class Registry also maintains a list of all objects it has created during * startup. At the moment these are Inverters and Loggers. * * This allows other components to search for other components by its * name. * * Solarpowerlog will make sure, that no objects will exist with identical name. * * \section TWS The Work Scheduler * * \sa CWorkScheduler * * The Registry also maintains one CWorkScheduler object which is can be used * by all objects. * * \note By sharing one scheduler for all objects, the objects does not need to * care about concurrency, as all the work is done sequencially. This is also * true for timed work. * * \sa CWorkScheduler, ICommand, ICommandTarget * * \subsection TWS_snippet Default Scheduler example code * * This example code shows how to schedule work using the main scheduler: * \code * // Schedule the initialization and subscriptions later... * ICommand *cmd = new ICommand(CMD_INIT, this, 0); * Registry::GetMainScheduler()->ScheduleWork(cmd); * \endcode * * This example shows the usage with Timed Work. (1 second) * \code * timespec ts = { 1, 0 }; * ICommand *cmd = new ICommand(CMD_INIT, this, 0); * Registry::GetMainScheduler()->ScheduleWork(cmd, ts); * \endcode */ #ifndef REGISTRY_H_ #define REGISTRY_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include "interfaces/CWorkScheduler.h" #include "configuration/ILogger.h" #include "interfaces/CDebugHelper.h" class IInverterBase; using namespace std; /** The Registry stores some information needed by several program parts. * It also provides a global WorkScheduler. * * The Registry is a Singleton. * * \sa \ref LibConfig "The Configuration Concept" * \sa \ref Registry "The Object Register" * */ class Registry : boost::noncopyable { public: friend class CConfigHelper; /** return the static, global object of the registry. */ static Registry& Instance() { static Registry Instance; return Instance; } // C O N F I G U R A T I O N /** Shortcut to get the configuration. * * Please note, that it must be config_loaded beforehand. * (but if coding for solarpowerlog, this will be done early * in the startup phase, so one can expect a valid object here. */ inline static libconfig::Config* Configuration() { return Registry::Instance().Config; } inline static ILogger & GetMainLogger(void) { return Registry::Instance().l; }; /** (re)load configuration file * * Just brings it in memory... The old one will be deleted. * * \param [in] name Filename to load * * \returns false on error, true on success. */ bool LoadConfig( std::string name ); /** Fake a configuration * * (Well, this is a hack to make the snippets printing work without * mayor rework throughout the program) * * This function will initilalize an absolute minimum configuration set * tp have the libconfig objects available */ void FakeConfig(void); /* NOTE: This is obsolete! Use an Object of CConfigHelper to extract config! * * Extract the settings-subset for a specific object, * identified by section (like inverters) and name (like inverter_solarmax_1) * * ex: * * inverters = ( * { name = "Inverter_1"; * type = "Solarmax_XYZ"; * driver = "Sputnik_TCP"; * # (...) * }, * { name = "Inverter_2"; * type = "Solarmax_XYZ"; * driver = "Sputnik_TCP"; * # (...) * } * ); * * and GetSettingsForObject("inverters", "Inverter_1") will return the Settings object * for the group "inverters.[0]". * * Please note, that libconfig throws some exceptions: Especially, if the section is not found. * This is not handled here, as the Factories should check if the configuration is complete on * constructing them. (They also can add their own settings (default values)... * * [code] * * libconfig::Setting &set = Registry::Instance().GetSettingsForObject("inverters", "Inverter_1"); * libconfig::Setting &new = set.add("NewPropertyNotSet",libconfig::Setting::TypeString); * new = "New Default Setting"; * * [/code] * * Snippet to retrieve Settings: * [code] * libconfig::Setting &set = Registry::Instance().GetSettingsForObject("inverters", "Inverter_1"); * [/code] * * \warning This function is not intended to check if a setting exists! */ private: /** lookup a specific setting. * * \param section Configurationpath of the object. * \param objname Additional path * * \warning libconfig might throw exceptions if the setting is not * found. * However, if using the configurationpath (supplied by the bootstrap * code) and having objname empty, the path has been validated. */ libconfig::Setting & GetSettingsForObject( std::string section, std::string objname = "" ); public: /** search for an inverter/logger object with the object name * * \param name of the object * * \returns Pointer to the object or NULL if not found. */ IInverterBase* GetInverter( const string& name ) const; /** Add the a new created inveter/logger to the object database * * \note: This function is only for the bootstrap process!*/ void AddInverter( const IInverterBase *inverter ); /** Get the main scheduler to schedule work... * * \returns pointer to the scheduler */ static CWorkScheduler *GetMainScheduler( void ) { if(! Registry::Instance().mainscheduler) { Registry::Instance().mainscheduler = new CWorkScheduler; } return Registry::Instance().mainscheduler; } void AddDebugCollection(CDebugHelperCollection *obj) { debughelpercollection.push_back(obj); } void RemoveDebugCollection(CDebugHelperCollection *obj) { debughelpercollection.remove(obj); } void DumpDebugCollection() { std::list::iterator it = debughelpercollection.begin(); std::cerr << "--- cut here -----------------------------------------------"<< std::endl; std::cerr << "Starting DUMP "<< std::endl; while(it != debughelpercollection.end()) { (*it)->Dump(); it++; } std::cerr << "Done DUMP"<< std::endl; std::cerr << "--- cut here -----------------------------------------------"<< std::endl; } /** Destroy all inverter and datafilter objects. * Called before termination. */ void Shutdown(void); private: /// keeps the object for the main scheduler /// GetMainScheduler will create the object. (singleton) /// note: cpptest detects a memory leak, which is not true. Check the /// destructor in Registry.cpp CWorkScheduler *mainscheduler; protected: /** singleton: no object generation */ Registry(); virtual ~Registry(); private: libconfig::Config *Config; // TODO generalize this interface, as we might also store // other types of objects here. std::list inverters; ILogger l; std::list debughelpercollection; }; #endif /* REGISTRY_H_ */ solarpowerlog-solarpowerlog-0.26/src/ctemplate/000077500000000000000000000000001444065341000220035ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/ctemplate/README000066400000000000000000000005011444065341000226570ustar00rootroot00000000000000ctemplate GPL http://libctemplate.sourceforge.net Author: Author: Stephen C. Losen, University of Virginia License: GPL Used for templating the HTML output Modifications by Tobias Frost: Added C++ Wrapper to the header file. Emits smaller pages due filtering double-blanks Fixing compiler warnings. solarpowerlog-solarpowerlog-0.26/src/ctemplate/ctemplate.c000066400000000000000000001221741444065341000241340ustar00rootroot00000000000000/* * C Template: template expander library based on the perl * HTML::Template package. * * Version: 1.0 * * Author: Stephen C. Losen, University of Virginia * * Copyright 2009 Stephen C. Losen. Distributed under the terms * of the GNU General Public License (GPL) * * A template consists of text sequences, comments and template tags. * Anything that is not a tag and not a comment is considered text. * The tags include: * * * * * * * * * * * * * The "name =" attribute is required, and the "value =", "fmt =", * "default =", and "level =" attributes are optional. * * A comment is any text enclosed by <* and *> * * We read the entire template into memory, scan it, parse it, and * build a parse tree. To generate the output, we walk the parse * tree and output the tree nodes. A list of variables and values * determines what tree nodes we visit and what we output. * * The scanner splits the template into text sequences and tags. * Each call of scan() returns a tagnode struct representing the * next text sequence or tag. The parser uses recursive descent * to link the tagnodes into a parse tree. */ #include #include #include #include #include #include #include #include "ctemplate.h" /* To prevent infinite TMPL_INCLUDE cycles, we limit the depth */ #define MAX_INCLUDE_DEPTH 30 /* template tag kinds (used in bitmaps) */ typedef enum { tag_text = 0x001, /* text sequence */ tag_var = 0x002, /* TMPL_VAR */ tag_if = 0x004, /* TMPL_IF */ tag_elsif = 0x008, /* TMPL_ELSIF */ tag_else = 0x010, /* */ tag_endif = 0x020, /* */ tag_include = 0x040, /* TMPL_INCLUDE */ tag_loop = 0x080, /* TMPL_LOOP */ tag_break = 0x100, /* TMPL_BREAK */ tag_cont = 0x200, /* TMPL_CONTINUE */ tag_endloop = 0x400 /* */ } tag_kind; typedef struct tagnode tagnode; typedef struct template template; /* The parse tree consists of tagnodes */ struct tagnode { tag_kind kind; tagnode *next; union { /* text sequence */ struct { const char *start; int len; } text; /* TMPL_VAR tag */ struct { const char *varname, *dfltval; TMPL_fmtfunc fmtfunc; } var; /* TMPL_IF tag or TMPL_ELSIF tag */ struct { const char *varname, *testval; tagnode *tbranch, *fbranch; } ifelse; /* TMPL_LOOP tag */ struct { const char *loopname; tagnode *body; } loop; /* TMPL_BREAK tag or TMPL_CONTINUE tag */ struct { int level; } breakcont; /* TMPL_INCLUDE tag */ struct { const char *filename; template *tmpl; } include; } tag; }; /* template information */ struct template { const char *filename; /* name of template file */ const char *tmplstr; /* contents of template file */ FILE *out; /* template output file pointer */ FILE *errout; /* error output file pointer */ tagnode *roottag; /* root of parse tree */ const TMPL_fmtlist *fmtlist; /* list of format functions */ /* scanner and parser state variables */ const char *scanptr; /* next character to be scanned */ tagnode *nexttag; /* next tag to be returned by scanner */ tagnode *curtag; /* current tagnode being parsed */ int linenum; /* current template line number */ int tagline; /* line number of current tag's name */ int error; /* error indicator */ int include_depth; /* avoids TMPL_INCLUDE cycles */ int loop_depth; /* current loop nesting depth */ int break_level; /* for processing a TMPL_BREAK tag */ int cont_level; /* for processing a TMPL_CONTINUE tag */ tagnode reusable; /* reusable storage for simple tags */ }; /* * TMPL_fmtlist is a list of format functions, which are passed to * a template. A TMPL_VAR tag can specify a format function for * outputting the variable with the fmt="fmtname" attribute. */ struct TMPL_fmtlist { TMPL_fmtlist *next; /* next list member */ TMPL_fmtfunc fmtfunc; /* pointer to format function */ char name[1]; /* name of format function */ }; /* * variables are passed to a template in a tree consisting of * TMPL_var, TMPL_varlist and TMPL_loop nodes. * * TMPL_var is a simple variable (name and value) */ typedef struct TMPL_var TMPL_var; struct TMPL_var { TMPL_var *next; /* next simple variable on list */ const char *name; char value[1]; /* value and name stored here */ }; /* * TMPL_varlist is a variable list of simple variables and/or * loop variables */ struct TMPL_varlist { TMPL_varlist *next; /* next variable list on a list */ TMPL_var *var; /* list of my simple variables */ TMPL_loop *loop; /* list of my loop variables */ TMPL_loop *parent; /* my parent loop variable (if any) */ }; /* TMPL_loop is a loop variable, which is a list of variable lists */ struct TMPL_loop { TMPL_loop *next; /* next loop variable on a list */ const char *name; /* my name */ TMPL_varlist *varlist; /* list of my variable lists */ TMPL_varlist *tail; /* tail of "varlist" */ TMPL_varlist *parent; /* my parent variable list */ }; /* mymalloc() is a malloc wrapper that exits on failure */ static void * mymalloc(size_t size) { void *ret = malloc(size); if (ret == 0) { fputs("C Template library: out of memory\n", stderr); exit(1); } return ret; } /* * newtemplate() creates a new template struct and reads the template * file "filename" into memory. If "tmplstr" is non-null then it is * the template, so we do not read "filename". */ static template * newtemplate(const char *filename, const char *tmplstr, const TMPL_fmtlist *fmtlist, FILE *out, FILE *errout) { template *t; FILE *fp; char *buf = 0; struct stat stb; if (tmplstr == 0 && filename == 0) { if (errout != 0) { fputs("C Template library: no template specified\n", errout); } return 0; } if (tmplstr == 0) { if ((fp = fopen(filename, "r")) != 0 && fstat(fileno(fp), &stb) == 0 && S_ISREG(stb.st_mode) != 0 && (buf = (char *) mymalloc(stb.st_size + 1)) != 0 && (stb.st_size == 0 || fread(buf, 1, stb.st_size, fp) == (size_t)stb.st_size)) { fclose(fp); buf[stb.st_size] = 0; } else { if (errout != 0) { fprintf(errout, "C Template library: failed to read " "template from file \"%s\"\n", filename); } if (buf != 0) { free(buf); } if (fp != 0) { fclose(fp); } return 0; } } t = (template *) mymalloc(sizeof(*t)); t->filename = filename != 0 ? filename : "(none)"; t->tmplstr = tmplstr != 0 ? tmplstr : buf; t->fmtlist = fmtlist; t->scanptr = t->tmplstr; t->roottag = t->curtag = t->nexttag = 0; t->out = out; t->errout = errout; t->linenum = 1; t->error = 0; t->include_depth = 0; t->loop_depth = 0; t->break_level = t->cont_level = 0; return t; } /* newtag() allocates a new tagnode */ static tagnode * newtag(template *t, tag_kind kind) { tagnode *ret; switch(kind) { /* * The following tags are simple parse tokens that are * never linked into the parse tree so they share storage. */ case tag_else: case tag_endif: case tag_endloop: ret = &t->reusable; break; default: ret = (tagnode *) mymalloc(sizeof(*ret)); break; } ret->kind = kind; ret->next = 0; return ret; } /* * freetag() recursively frees parse tree tagnodes. We do not free * the text in a tag_text tagnode because it points to memory where * the input template is stored, which we free elsewhere. */ static void freetag(tagnode *tag) { template *t; if (tag == 0) { return; } switch(tag->kind) { case tag_var: free((void *) tag->tag.var.varname); if (tag->tag.var.dfltval != 0) { free((void *) tag->tag.var.dfltval); } break; case tag_if: case tag_elsif: free((void *) tag->tag.ifelse.varname); if (tag->tag.ifelse.testval != 0) { free((void *) tag->tag.ifelse.testval); } freetag(tag->tag.ifelse.tbranch); freetag(tag->tag.ifelse.fbranch); break; case tag_loop: free((void *) tag->tag.loop.loopname); freetag(tag->tag.loop.body); break; case tag_include: free((void *) tag->tag.include.filename); if ((t = tag->tag.include.tmpl) != 0) { free((void *) t->filename); free((void *) t->tmplstr); freetag(t->roottag); free(t); } break; default: // remove compiler warnings about unised enums: // tag_text tag_else tag_endif tag_break tag_cont and tag_endloop break; } freetag(tag->next); free(tag); } /* map tag_kind to a human readable string */ static const char * tagname(tag_kind kind) { switch(kind) { case tag_var: return "TMPL_VAR"; case tag_if: return "TMPL_IF"; case tag_elsif: return "TMPL_ELSIF"; case tag_else: return "TMPL_ELSE"; case tag_endif: return "/TMPL_IF"; case tag_include: return "TMPL_INCLUDE"; case tag_loop: return "TMPL_LOOP"; case tag_break: return "TMPL_BREAK"; case tag_cont: return "TMPL_CONTINUE"; case tag_endloop: return "/TMPL_LOOP"; default: break; // remove warning "enumeration value tag_next not handled" } return "unknown"; } /* * SCANNER FUNCTIONS * * scanspaces() scans white space */ static const char * scanspaces(template *t, const char *p) { while (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') { if (*p++ == '\n') { t->linenum++; } } return p; } /* * scancomment() scans a comment delimited by <* and *>. If we find a * comment then we advance t->scanptr to the first character after the * comment and return 1. Otherwise we return 0. */ static int scancomment(template *t, const char *p) { int linenum = t->linenum; if (p[0] != '<' || p[1] != '*') { return 0; } for (p += 2; *p != 0; p++) { if (*p == '\n') { t->linenum++; } if (p[0] == '*' && p[1] == '>') { t->scanptr = p + 2; return 1; } } /* end of template, comment not terminated */ if (t->errout != 0) { fprintf(t->errout, "\"<*\" in file \"%s\" line %d " "has no \"*>\"\n", t->filename, linenum); } t->error = 1; return 0; } /* * scanattr() scans an attribute such as: name="value". If * successful we advance t->scanptr to the character after the * attribute and return a copy of the attribute value. Otherwise * we return null. We accept double or single quotes around "value". * We accept no quotes if "value" contains only letters, digits, * '.' or '-'. */ static char * scanattr(template *t, const char *attrname, const char *p) { int i = strlen(attrname); int quote = 0; char *ret; if (strncasecmp(p, attrname, i) != 0) { return 0; } p = scanspaces(t, p + i); if (*p++ != '=') { return 0; } p = scanspaces(t, p); if (*p == '"' || *p == '\'') { quote = *p++; } /* p now points to the start of the attribute value */ if (quote != 0) { for (i = 0; p[i] != quote && p[i] != '\n' && p[i] != 0; i++) ; if (p[i] != quote) { return 0; } t->scanptr = p + i + 1; } else { for (i = 0; isalnum(p[i]) || p[i] == '.' || p[i] == '-'; i++) ; if (i == 0) { return 0; } t->scanptr = p + i; } /* i is now the length of the attribute value */ ret = (char *) mymalloc(i + 1); memcpy(ret, p, i); ret[i] = 0; return ret; } /* * findfmt() looks up a format function by name. If successful * we return a pointer to the function, otherwise we return null. */ static TMPL_fmtfunc findfmt(const TMPL_fmtlist *fmtlist, const char *name) { for (; fmtlist != 0; fmtlist = fmtlist->next) { if (strcmp(fmtlist->name, name) == 0) { return fmtlist->fmtfunc; } } return 0; } /* * scantag() scans a template tag. If successful we return a tagnode * for the tag and advance t->scanptr to the first character after the * tag. Otherwise we clean up and return null. */ static tagnode * scantag(template *t, const char *p) { tag_kind kind; int commentish = 0; /* true if tag enclosed by */ int hasname = 0; /* true if tag has name= attribute */ int container = 0; /* true if tag may not end with /> */ tagnode *tag; int linenum = t->linenum; int len, level; char *name = 0, *value = 0, *fmt = 0; TMPL_fmtfunc func; const char *err = ""; if (*p++ != '<') { return 0; } if (p[0] == '!' && p[1] == '-' && p[2] == '-') { commentish = 1; p = scanspaces(t, p + 3); } t->tagline = t->linenum; /* tag name is on this line */ if (strncasecmp(p, "TMPL_VAR", len = 8) == 0) { kind = tag_var; hasname = 1; } else if (strncasecmp(p, "TMPL_INCLUDE", len = 12) == 0) { kind = tag_include; hasname = 1; } else if (strncasecmp(p, "TMPL_IF", len = 7) == 0) { kind = tag_if; hasname = 1; container = 1; } else if (strncasecmp(p, "TMPL_ELSIF", len = 10) == 0) { kind = tag_elsif; hasname = 1; } else if (strncasecmp(p, "TMPL_ELSE", len = 9) == 0) { kind = tag_else; } else if (strncasecmp(p, "/TMPL_IF", len = 8) == 0) { kind = tag_endif; container = 1; } else if (strncasecmp(p, "TMPL_LOOP", len = 9) == 0) { kind = tag_loop; hasname = 1; container = 1; } else if (strncasecmp(p, "/TMPL_LOOP", len = 10) == 0) { kind = tag_endloop; container = 1; } else if (strncasecmp(p, "TMPL_BREAK", len = 10) == 0) { kind = tag_break; } else if (strncasecmp(p, "TMPL_CONTINUE", len = 13) == 0) { kind = tag_cont; } else { kind = 0; goto failure; } t->scanptr = p + len; /* white space required between tag name and attributes */ p = scanspaces(t, t->scanptr); if (hasname != 0 && p == t->scanptr) { goto failure; } /* * These tags require one "name =" attribute. The TMPL_VAR tag * may have optional "fmt =" and "default =" attributes. The * TMPL_IF and TMPL_ELSIF tags may have an optional "value =" * attribute. Attributes can come in any order. */ switch(kind) { case tag_include: case tag_loop: if ((name = scanattr(t, "name", p)) != 0) { p = scanspaces(t, t->scanptr); } break; case tag_var: while ((name == 0 && (name = scanattr(t, "name", p)) != 0) || (fmt == 0 && (fmt = scanattr(t, "fmt", p)) != 0) || (value == 0 && (value = scanattr(t, "default", p)) != 0)) { p = scanspaces(t, t->scanptr); } break; case tag_if: case tag_elsif: while ((name == 0 && (name = scanattr(t, "name", p)) != 0) || (value == 0 && (value = scanattr(t, "value", p)) != 0)) { p = scanspaces(t, t->scanptr); } break; /* * These tags may have an optional "level =" attribute, which * must be preceded by white space. */ case tag_break: case tag_cont: if (p != t->scanptr && (value = scanattr(t, "level", p)) != 0) { p = scanspaces(t, t->scanptr); } break; default: // remove compiler warning for unhandled enum values tag_text, // tag_else, tag_endif and tag_endloop break; } /* check for end of tag */ if (commentish == 0 && p[0] == '>') { t->scanptr = p + 1; } else if (commentish == 0 && container == 0 && p[0] == '/' && p[1] == '>') { t->scanptr = p + 2; } else if (commentish != 0 && p[0] == '-' && p[1] == '-' && p[2] == '>') { t->scanptr = p + 3; } else { goto failure; } /* check attributes and build tag node */ if (hasname != 0 && name == 0) { err = "(missing \"name=\" attribute) "; goto failure; } switch(kind) { case tag_var: func = 0; if (fmt != 0) { if ((func = findfmt(t->fmtlist, fmt)) == 0) { err = "(bad \"fmt=\" attribute) "; goto failure; } free(fmt); } tag = newtag(t, kind); tag->tag.var.varname = name; tag->tag.var.dfltval = value; tag->tag.var.fmtfunc = func; break; case tag_include: if (t->include_depth >= MAX_INCLUDE_DEPTH) { err = "(check for include cycle) "; goto failure; } tag = newtag(t, kind); tag->tag.include.filename = name; tag->tag.include.tmpl = 0; break; case tag_loop: tag = newtag(t, kind); tag->tag.loop.loopname = name; tag->tag.loop.body = 0; break; case tag_break: case tag_cont: if (t->loop_depth < 1) { err = "(not inside a loop) "; goto failure; } level = 1; if (value != 0) { if ((level = atoi(value)) < 1 || level > t->loop_depth) { err = "(bad \"level=\" attribute) "; goto failure; } free(value); } tag = newtag(t, kind); tag->tag.breakcont.level = level; break; case tag_if: case tag_elsif: tag = newtag(t, kind); tag->tag.ifelse.varname = name; tag->tag.ifelse.testval = value; tag->tag.ifelse.tbranch = 0; tag->tag.ifelse.fbranch = 0; break; default: tag = newtag(t, kind); break; } return tag; failure: /* restore line number, clean up and return null */ t->linenum = linenum; if (name != 0) { free(name); } if (value != 0) { free(value); } if (fmt != 0) { free(fmt); } if (kind != 0 && t->errout != 0) { fprintf(t->errout, "Ignoring bad %s tag %sin file \"%s\" line %d\n", tagname(kind), err, t->filename, t->tagline); } return 0; } /* * scan() is the main scanner function. We return the next text sequence * or template tag in t->curtag or we set it to null at the end of the * template. We start scanning at t->scanptr and when we are done, * t->scanptr points to where the next call to scan() should * start scanning. We scan text until we find a tag or comment. If we * find a comment, then we return the text. If we find a tag, then we * save it in t->nexttag and return the text. We will return t->nexttag * the next time scan() is called. If we find a tag with no preceding * text, then we return the tag. If we find a comment with no preceding * text, then we try again. */ static void scan(template *t) { tagnode *tag = 0; const char *p; int i; if (t->nexttag != 0) { /* return tag from previous call */ t->curtag = t->nexttag; t->nexttag = 0; return; } /* scan text until we find a tag or a comment or null */ p = t->scanptr; for (i = 0; p[i] != 0; i++) { if (p[i] == '\n') { t->linenum++; } if (p[i] != '<') { continue; } /* we found a '<' so we look for a comment or tag */ if (scancomment(t, p + i) != 0) { if (i == 0) { scan(t); /* no text so try again */ return; } break; } if ((tag = scantag(t, p + i)) != 0) { break; } } /* * At this point p is where we started scanning and p[i] is * the first character of the tag or comment that ended this * scan or else p[i] is the null at the end of the template. */ if (p[i] == 0) { t->scanptr = p + i; } if (i > 0) { t->nexttag = tag; /* save the tag (if any) */ tag = newtag(t, tag_text); /* return the text sequence */ tag->tag.text.start = p; tag->tag.text.len = i; } t->curtag = tag; } /* * PARSER FUNCTIONS * * forward declaration for recursive calls */ static tagnode *parselist(template *t, int stop); /* * parseif() parses a TMPL_IF statement, which looks like this: * * * template-list * * template-list * * template-list * * * A template-list is any sequence (including an empty sequence) * of text, template tags, if statements or loop statements. There * can be zero or more TMPL_ELSIF tags followed by zero or one * TMPL_ELSE tag. There must be a final /TMPL_IF tag. * * "iftag" is a TMPL_IF tagnode, which has pointers for a true branch * and a false branch. We construct a parse tree for the if statement * with "iftag" at the root. When we are done t->curtag points to * the tag that follows the /TMPL_IF tag for this statement. */ static void parseif(template *t, int stop) { tagnode *iftag = t->curtag; int linenum = t->tagline; int mystop = stop | tag_else | tag_elsif | tag_endif; iftag->tag.ifelse.tbranch = parselist(t, mystop); while (t->curtag != 0 && t->curtag->kind == tag_elsif) { iftag->tag.ifelse.fbranch = t->curtag; iftag = t->curtag; iftag->tag.ifelse.tbranch = parselist(t, mystop); } if (t->curtag != 0 && t->curtag->kind == tag_else) { iftag->tag.ifelse.fbranch = parselist(t, stop | tag_endif); } if (t->curtag != 0 && t->curtag->kind == tag_endif) { scan(t); /* success, scan next tag */ } else { if (t->errout != 0) { fprintf(t->errout, "TMPL_IF tag in file \"%s\" line %d " "has no /TMPL_IF tag\n", t->filename, linenum); } t->error = 1; } } /* * parseloop() parses a TMPL_LOOP statement which looks like this: * * * template-list * * * "looptag" is a TMPL_LOOP tagnode, which has a pointer for the * loop body. We construct a parse tree for the loop statement * with "looptag" at the root. When we are done, t->curtag points * to the tag that follows the /TMPL_LOOP tag for this statement. */ static void parseloop(template *t, int stop) { tagnode *looptag = t->curtag; int linenum = t->tagline; t->loop_depth++; looptag->tag.loop.body = parselist(t, stop | tag_endloop); t->loop_depth--; if (t->curtag != 0 && t->curtag->kind == tag_endloop) { scan(t); /* success, scan next tag */ } else { if (t->errout != 0) { fprintf(t->errout, "TMPL_LOOP tag in file \"%s\" line %d " "has no /TMPL_LOOP tag\n", t->filename, linenum); } t->error = 1; } } /* * parselist() is the top level parser function. It parses a * template-list which is any sequence of text, TMPL_VAR tags, * TMPL_INCLUDE tags, if statements or loop statements. * We return a parse tree which is a linked list of tagnodes. The * "stop" parameter is a bitmap of tag kinds that we expect to end * this list. For example, if we are parsing the template-list * following a TMPL_IF tag, then we expect the list to end with a * TMPL_ELSIF tag or TMPL_ELSE tag or /TMPL_IF tag. If we are parsing the * template-list that comprises the entire template, then "stop" is zero * so that we keep going to the end of the template. When we are done, * t->curtag is the tag that caused us to stop, or null at the end of * the template. */ static tagnode * parselist(template *t, int stop) { tagnode *list = 0, *tail = NULL, *tag; scan(t); while ((tag = t->curtag) != 0) { switch(tag->kind) { case tag_elsif: /* check for terminator tag */ case tag_else: case tag_endif: case tag_endloop: if ((tag->kind & stop) != 0) { return list; } /* unexpected terminator tag -- keep going */ if (t->errout != 0) { fprintf(t->errout, "Unexpected %s tag in file \"%s\" " "line %d\n", tagname(tag->kind), t->filename, t->tagline); } t->error = 1; scan(t); if (tag->kind == tag_elsif) { break; /* tag linked to list to be freed later */ } continue; /* tag not linked to list */ case tag_if: parseif(t, stop); break; case tag_loop: parseloop(t, stop); break; default: scan(t); break; } /* link the tag into the list of tags */ tag->next = 0; if (list == 0) { list = tail = tag; } else { tail->next = tag; tail = tag; } } return list; } /* * PARSE TREE WALKING FUNCTIONS * * valueof() looks up a variable by name and returns its value * or returns null if not found. We search "varlist" and any * enclosing variable lists. The parent of "varlist" is a * loop variable, whose parent is a variable list that encloses * "varlist". */ static char * valueof(const char *varname, const TMPL_varlist *varlist) { TMPL_var *var; while(varlist != 0) { for (var = varlist->var; var != 0; var = var->next) { if (strcmp(varname, var->name) == 0) { return var->value; } } varlist = varlist->parent == 0 ? 0 : varlist->parent->parent; } return 0; } /* * findloop() looks up a loop variable by name and returns it or * returns null if not found. We search "varlist" and any * enclosing variable lists. */ static TMPL_loop * findloop(const char *loopname, const TMPL_varlist *varlist) { TMPL_loop *loop; while (varlist != 0) { for (loop = varlist->loop; loop != 0; loop = loop->next) { if (strcmp(loopname, loop->name) == 0) { return loop; } } varlist = varlist->parent == 0 ? 0 : varlist->parent->parent; } return 0; } /* * istrue() evaluates a TMPL_IF (or TMPL_ELSIF) tag for true or false. * * is true if 1) simple variable "varname" * exists and is not the null string or 2) the loop variable "varname" * exists. Otherwise false. * * is true if "varname" does not exist * or if "varname" has a null value. * * is true if simple variable * "varname" has value "testvalue". Otherwise false. */ static int is_true(const tagnode *iftag, const TMPL_varlist *varlist) { const char *testval = iftag->tag.ifelse.testval; const char *value; TMPL_loop *loop = 0; if ((value = valueof(iftag->tag.ifelse.varname, varlist)) == 0) { loop = findloop(iftag->tag.ifelse.varname, varlist); } return (testval == 0 && value != 0 && *value != 0) || (testval == 0 && loop != 0) || (testval != 0 && *testval == 0 && loop == 0 && (value == 0 || *value == 0)) || (testval != 0 && value != 0 && strcmp(value, testval) == 0); } /* * write_text() writes a text sequence handling \ escapes. * * A single \ at the end of a line is not output and neither * is the line terminator (\n or \r\n). * * \\ at the end of a line is output as a single \ followed * by the line terminator. * * Any other \ is output unchanged. */ static void write_text(const char *p, int len, FILE *out) { int i, k; char lastwasblank = 0; for (i = 0; i < len; i++) { switch (p[i]) { case ' ': case '\t': case '\r': case '\n': if (lastwasblank) { continue; } lastwasblank = 1; break; default: lastwasblank = 0; } /* check for \ or \\ before \n or \r\n */ if (p[i] == '\\') { k = i + 1; if (k < len && p[k] == '\\') { k++; } if (k < len && p[k] == '\r') { k++; } if (k < len && p[k] == '\n') { if (p[i + 1] == '\\') { i++; /* skip first \ */ } else { i = k; /* skip \ and line terminator */ continue; } } } fputc(p[i], out); } } /* * newfilename() returns a copy of an include file name with * possible modifications. If the include file name begins * with ".../" then we replace "..." with the directory name * of the parent template file. If there is no directory * name then we strip ".../". */ static const char * newfilename(const char *inclfile, const char *parentfile) { char *newfile, *cp; newfile = mymalloc(strlen(parentfile) + strlen(inclfile) + 1); if (strncmp(inclfile, ".../", 4) != 0) { return strcpy(newfile, inclfile); } strcpy(newfile, parentfile); cp = strrchr(newfile, '/'); strcpy(cp == 0 ? newfile : cp + 1, inclfile + 4); return newfile; } /* * walk() walks the template parse tree and outputs the result. We * process the tree nodes according to the data in "varlist". */ static void walk(template *t, tagnode *tag, const TMPL_varlist *varlist) { const char *value; TMPL_loop *loop; TMPL_varlist *vl; template *t2; const char *newfile; /* * if t->break_level is non zero then we are unwinding the * recursion after encountering a TMPL_BREAK tag. The same * is true for t->cont_level and TMPL_CONTINUE. */ if (tag == 0 || t->break_level > 0 || t->cont_level > 0 || t->error != 0) { return; } switch(tag->kind) { case tag_text: write_text(tag->tag.text.start, tag->tag.text.len, t->out); break; case tag_var: if ((value = valueof(tag->tag.var.varname, varlist)) == 0 && (value = tag->tag.var.dfltval) == 0) { break; } /* Use the tag's format function or else just use fputs() */ if (tag->tag.var.fmtfunc != 0) { tag->tag.var.fmtfunc(value, t->out); } else { fputs(value, t->out); } break; case tag_if: case tag_elsif: if (is_true(tag, varlist)) { walk(t, tag->tag.ifelse.tbranch, varlist); } else { walk(t, tag->tag.ifelse.fbranch, varlist); } break; case tag_loop: if ((loop = findloop(tag->tag.loop.loopname, varlist)) == 0) { break; } for (vl = loop->varlist; vl != 0; vl = vl->next) { walk(t, tag->tag.loop.body, vl); /* * if t->break_level is nonzero then we encountered a * TMPL_BREAK tag inside this TMPL_LOOP so we need to * break here. */ if (t->break_level > 0) { t->break_level--; break; } /* * if t->cont_level is nonzero then we encountered a * TMPL_CONTINUE inside this TMPL_LOOP. Depending * on the level we either break here or continue */ if (t->cont_level > 0 && --t->cont_level > 0) { break; } } break; /* * For a TMPL_BREAK or TMPL_CONTINUE tag we terminate the walk * of this TMPL_LOOP body and set t->break_level or t->cont_level * to unwind the recursion. */ case tag_break: t->break_level = tag->tag.breakcont.level; return; case tag_cont: t->cont_level = tag->tag.breakcont.level; return; case tag_include: /* if first visit, open and parse the included file */ if ((t2 = tag->tag.include.tmpl) == 0) { newfile = newfilename(tag->tag.include.filename, t->filename); t2 = newtemplate(newfile, 0, t->fmtlist, t->out, t->errout); if (t2 == 0) { free((void *) newfile); t->error = 1; break; } tag->tag.include.tmpl = t2; t2->include_depth = t->include_depth + 1; t2->roottag = parselist(t2, 0); } /* walk the included file's parse tree */ walk(t2, t2->roottag, varlist); t->error = t2->error; break; default: // remove compiler warning for unhandled enum values: // tag_else, tag_endif and tag_endloop break; } walk(t, tag->next, varlist); } /* * EXPORTED FUNCTIONS * * TMPL_add_var() adds one or more simple variables to variable list * "varlist" and returns the result. If "varlist" is null, then we * create it. The parameter list has a variable number of "char *" * parameters terminated by a null parameter. Each pair of parameters * is a variable name and value that we store in a TMPL_var struct and * link into "varlist". */ TMPL_varlist * TMPL_add_var(TMPL_varlist *varlist, ...) { va_list ap; const char *name, *value; TMPL_var *var; int nlen, vlen; va_start(ap, varlist); while ((name = va_arg(ap, char *)) != 0 && (value = va_arg(ap, char *)) != 0) { /* * get enough memory to store the TMPL_var struct and * the name string and the value string */ nlen = strlen(name) + 1; vlen = strlen(value) + 1; var = (TMPL_var *) mymalloc(sizeof(*var) + nlen + vlen); strcpy(var->value, value); var->name = strcpy(var->value + vlen, name); if (varlist == 0) { varlist = (TMPL_varlist *) mymalloc(sizeof(*varlist)); memset(varlist, 0, sizeof(*varlist)); } var->next = varlist->var; varlist->var = var; } va_end(ap); return varlist; } /* * TMPL_add_loop() adds loop variable "loop" to variable list "varlist" * and returns the result. If "varlist" is null, then we create it. * We decline to add "loop" if 1) "loop" has already been added to a * variable list or 2) adding "loop" would create a cycle because * "loop" contains "varlist". */ TMPL_varlist * TMPL_add_loop(TMPL_varlist *varlist, const char *name, TMPL_loop *loop) { TMPL_loop *lp; /* if sanity check fails, just return */ if (name == 0 || loop == 0 || loop->parent != 0) { return varlist; } if (varlist == 0) { varlist = (TMPL_varlist *) mymalloc(sizeof(*varlist)); memset(varlist, 0, sizeof(*varlist)); } /* if sanity check for cycle fails, just return */ for (lp = varlist->parent; lp != 0; lp = lp->parent == 0 ? 0 : lp->parent->parent) { if (lp == loop) { return varlist; } } loop->name = strdup(name); loop->parent = varlist; loop->next = varlist->loop; varlist->loop = loop; return varlist; } /* * TMPL_add_varlist() adds variable list "varlist" to loop variable * "loop" and returns the result. If "loop" is null, then we create it. * We decline to add "varlist" if 1) "varlist" has already been added * to a loop variable or 2) adding "varlist" would create a cycle * because "varlist" contains "loop". */ TMPL_loop * TMPL_add_varlist(TMPL_loop *loop, TMPL_varlist *varlist) { TMPL_varlist *vl; /* if sanity check fails, just return */ if (varlist == 0 || varlist->parent != 0) { return loop; } if (loop == 0) { loop = (TMPL_loop *) mymalloc(sizeof(*loop)); memset(loop, 0, sizeof(*loop)); } /* if sanity check for cycle fails, just return */ for (vl = loop->parent; vl != 0; vl = vl->parent == 0 ? 0 : vl->parent->parent) { if (vl == varlist) { return loop; } } varlist->parent = loop; varlist->next = 0; if (loop->varlist == 0) { loop->varlist = loop->tail = varlist; } else { loop->tail->next = varlist; loop->tail = varlist; } return loop; } /* TMPL_free_varlist() recursively frees memory used by a TMPL_varlist */ void TMPL_free_varlist(TMPL_varlist *varlist) { TMPL_loop *loop, *loopnext; TMPL_var *var, *varnext; if (varlist == 0) { return; } for (loop = varlist->loop; loop != 0; loop = loopnext) { loopnext = loop->next; TMPL_free_varlist(loop->varlist); free((void *) loop->name); free(loop); } for (var = varlist->var; var != 0; var = varnext) { varnext = var->next; free(var); } TMPL_free_varlist(varlist->next); free(varlist); } /* * TMPL_add_fmt() adds a name and function pointer to format function * list "fmtlist" and returns the result. If "fmtlist" is null, then * we create it, otherwise we return "fmtlist". Parameter "name" is * the name used in the in the fmt="fmtname" attribute of a TMPL_VAR * tag. Parameter "fmtfunc" is a pointer to a user supplied function * with a prototype like this: * * void funcname(const char *value, FILE *out); * * The function should output "value" to "out" with appropriate * formatting or encoding. */ TMPL_fmtlist * TMPL_add_fmt(TMPL_fmtlist *fmtlist, const char *name, TMPL_fmtfunc fmtfunc) { TMPL_fmtlist *newfmt; if (name == 0 || fmtfunc == 0) { return fmtlist; } newfmt = (TMPL_fmtlist *) mymalloc(sizeof(*newfmt) + strlen(name)); strcpy(newfmt->name, name); newfmt->fmtfunc = fmtfunc; if (fmtlist == 0) { newfmt->next = 0; return newfmt; } /* if fmtlist is not null, then we return its original value */ newfmt->next = fmtlist->next; fmtlist->next = newfmt; return fmtlist; } /* TMPL_free_fmtlist frees memory used by a format function list */ void TMPL_free_fmtlist(TMPL_fmtlist *fmtlist) { if (fmtlist != 0) { TMPL_free_fmtlist(fmtlist->next); free(fmtlist); } } /* * TMPL_write() outputs a template to open file pointer "out" using * variable list "varlist". If "tmplstr" is null, then we read the * template from "filename", otherwise "tmplstr" is the template. * Parameter "fmtlist" is a format function list that contains * functions that TMPL_VAR tags can specify to output variables. * We return 0 on success otherwise -1. We write errors to open * file pointer "errout". */ int TMPL_write(const char *filename, const char *tmplstr, const TMPL_fmtlist *fmtlist, const TMPL_varlist *varlist, FILE *out, FILE *errout) { int ret; template *t; if ((t = newtemplate(filename, tmplstr, fmtlist, out, errout)) == 0) { return -1; } t->roottag = parselist(t, 0); walk(t, t->roottag, varlist); ret = t->error == 0 ? 0 : -1; if (tmplstr == 0 && t->tmplstr != 0) { free((void *) t->tmplstr); } freetag(t->roottag); free(t); return ret; } /* * Some handy format functions * * TMPL_encode_entity() converts HTML markup characters to entities */ void TMPL_encode_entity(const char *value, FILE *out) { for (; *value != 0; value++) { switch(*value) { case '&': fputs("&", out); break; case '<': fputs("<", out); break; case '>': fputs(">", out); break; case '"': fputs(""", out); break; case '\'': fputs("'", out); break; case '\n': fputs(" ", out); break; case '\r': fputs(" ", out); break; default: fputc(*value, out); break; } } } /* TMPL_encode_url() does URL encoding (%xx) */ void TMPL_encode_url(const char *value, FILE *out) { static const char hexdigit[] = "0123456789ABCDEF"; int c; for (; *value != 0; value++) { if (isalnum(*value) || *value == '.' || *value == '-' || *value == '_') { fputc(*value, out); continue; } if (*value == ' ') { fputc('+', out); continue; } c = (unsigned char) *value; fputc('%', out); fputc(hexdigit[c >> 4], out); fputc(hexdigit[c & 0xf], out); } } solarpowerlog-solarpowerlog-0.26/src/ctemplate/ctemplate.h000066400000000000000000000022061444065341000241320ustar00rootroot00000000000000/* * C Template Library 1.0 * * Copyright 2009 Stephen C. Losen. Distributed under the terms * of the GNU General Public License (GPL) */ #ifndef _CTEMPLATE_H #define _CTEMPLATE_H typedef struct TMPL_varlist TMPL_varlist; typedef struct TMPL_loop TMPL_loop; typedef struct TMPL_fmtlist TMPL_fmtlist; typedef void (*TMPL_fmtfunc) (const char *, FILE *); /* TMPL_varlist *TMPL_add_var(TMPL_varlist *varlist, const char *varname1, const char *value1, ... , 0); */ TMPL_varlist *TMPL_add_var(TMPL_varlist *varlist, ...); TMPL_varlist *TMPL_add_loop(TMPL_varlist *varlist, const char *name, TMPL_loop *loop); TMPL_loop *TMPL_add_varlist(TMPL_loop *loop, TMPL_varlist *varlist); void TMPL_free_varlist(TMPL_varlist *varlist); TMPL_fmtlist *TMPL_add_fmt(TMPL_fmtlist *fmtlist, const char *name, TMPL_fmtfunc fmtfunc); void TMPL_free_fmtlist(TMPL_fmtlist *fmtlist); int TMPL_write(const char *filename, const char *tmplstr, const TMPL_fmtlist *fmtlist, const TMPL_varlist *varlist, FILE *out, FILE *errout); void TMPL_encode_entity(const char *value, FILE *out); void TMPL_encode_url (const char *value, FILE *out); #endif solarpowerlog-solarpowerlog-0.26/src/daemon.cpp000066400000000000000000000170601444065341000220000ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /* * signalhandler.c * * Created on: 01.10.2012 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "daemon.h" #include "configuration/ILogger.h" #include #include #include #include #include #include "configuration/Registry.h" #ifdef HAVE_BACKTRACE_SYMBOLS_FD #include #endif #include "Inverters/BasicCommands.h" #include "patterns/ICommand.h" volatile sig_atomic_t killsignal = false; volatile sig_atomic_t sigusr1 = false; char *progname; std::string rundir("/"); std::string daemon_stdout("/dev/null"); std::string daemon_stderr("/dev/null"); std::string pidfile=""; int pidfile_fd = 0; boost::thread terminator_thread; bool background = false; void cleanup(void) { if (background && pidfile_fd) unlink(pidfile.c_str()); pidfile_fd=0; } void logreopen(bool rotate) { ILogger mainlogger; if (!rotate) { if (!freopen("/dev/null", "r", stdin)) { LOGWARN(mainlogger, "daemonize: Could not reopen stdin. errno=" << errno); } } // do not rotate stdout when redirected to /dev/null if (!rotate || daemon_stdout != "/dev/null" ) { if (!freopen(daemon_stdout.c_str(), "a", stdout)) { LOGFATAL(mainlogger, "daemonize: Could not reopen stdout. errno=" << errno); // try to rectify things... a closed stdout might not be a good idea... exit(1); } } // also, do not rotate /dev/null on stderr if (!rotate || daemon_stderr != "/dev/null" ) { if (!freopen(daemon_stderr.c_str(), "a", stderr)) { LOGFATAL(mainlogger, "daemonize: Could not reopen stderr. errno=" << errno); // try to rectify things... a closed stderr might not be a good idea... exit(1); } } } void daemonize(void) { ILogger mainlogger; // generate pidfile if (pidfile != "") { LOGINFO(mainlogger, "Using PID file " << pidfile ); pidfile_fd = open(pidfile.c_str(),O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if( pidfile_fd == -1) { LOGFATAL(mainlogger,"Cannot generate pidfile " << pidfile <<" Reason: " << strerror(errno)); exit(1); } } else { pidfile_fd = 0; } // dameonize. LOGDEBUG(mainlogger, "daemonize: Rising a daemon."); // reopen stdxxx now, because afterwards we will loose our communication channel. LOGDEBUG(mainlogger, "reopening stdout / stderr to log file"); logreopen(false); LOGDEBUG(mainlogger, "daemonize: logfiles redirected."); pid_t pid, sid; pid = fork(); if (pid < 0) { LOGFATAL(mainlogger, "Could not dameonize. fork() error="< 0) { // on daemons, the parent use _exit. _exit(EXIT_SUCCESS); } sid = setsid(); if (sid < 0) { cleanup(); LOGFATAL(mainlogger, "Could not dameonize. setsid() error="<ScheduleWork(cmd); }; }; void SignalHandler(int signal) { switch (signal) { case SIGTERM: // die. if (!killsignal) { killsignal = true; LOGINFO( Registry::GetMainLogger(), progname << " Termination requested. Will terminate at next opportunity."); ICommand* cmd = new ICommand(BasicCommands::CMD_BRC_SHUTDOWN, NULL); if (!Registry::GetMainScheduler()->ScheduleWork(cmd,true)) { // bad luck -- received SIGTERM in the moment when the mutex was hold. // so we spawn a task to do it for us... boostthreadcallable callable; terminator_thread = boost::thread(callable,boost::ref(cmd)); } } else { LOGFATAL( Registry::GetMainLogger(), progname << " Termination signal received. Please be patient."); } break; case SIGSEGV: { cleanup(); cerr << progname << " Segmentation fault. " << endl; cerr << "Trying to dump internal state information" << endl; Registry::Instance().DumpDebugCollection(); LOGFATAL(Registry::GetMainLogger(), progname << " Segmentation fault."); #ifdef HAVE_BACKTRACE_SYMBOLS_FD // try to print a backtrace. LOGFATAL(Registry::GetMainLogger(),"Trying a backtrace to stderr:"); { void *trace[64]; int count = backtrace( trace, 64 ); backtrace_symbols_fd(trace, count, 2); } #endif raise(signal); break; } case SIGUSR1: { LOGINFO(Registry::GetMainLogger(),"SIGUSR1 received"); sigusr1 = true; break; } case SIGUSR2: { cerr << "SIGUSR1 received" << endl; cerr << "Trying to dump internal state information" << endl; Registry::Instance().DumpDebugCollection(); break; } } } void SetupSignalHandler(void) { struct sigaction sa; sa.sa_handler = SignalHandler; sigfillset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sigaction(SIGTERM, &sa, NULL); sigaction(SIGUSR1, &sa, NULL); sigaction(SIGUSR2, &sa, NULL); // Sigsegv - after trigered, set to default behaviour. sa.sa_flags = SA_RESETHAND; sigaction(SIGSEGV, &sa, NULL); } solarpowerlog-solarpowerlog-0.26/src/daemon.h000066400000000000000000000046511444065341000214470ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file signalhandler.h * * Created on: 01.10.2012 * Author: tobi * * Bundles the Daemon and Signalhandler functionalities. * Stores also the relevant data variables. */ #ifndef SIGNALHANDLER_H_ #define SIGNALHANDLER_H_ #include #include #include /// Filename for the pidfile. extern std::string pidfile; /// Directory where to chdir to. extern std::string rundir; /// File where to redirect stdout extern std::string daemon_stdout; /// File where to redirect stderr extern std::string daemon_stderr; /// Termination request received. extern volatile sig_atomic_t killsignal; /// SIGUSR1 has been received. extern volatile sig_atomic_t sigusr1; /// stores the programmame (argv[0]) extern char *progname; /// Filedescriptor for pid-file /// Note: pid file will be closed right after generation, then this will be kept /// non-zero to indicate that we have a pid file and need to unlink it on exit. extern int pidfile_fd; /// Should solarpowerlog run in the background? extern bool background; /// Perform some cleanups at exit extern void cleanup(void); /// rotate logfiles (reopening) void logreopen(bool rotate); /// Daemonizes solarpowerlog extern void daemonize(void); /// The signal handler extern void SignalHandler(int signal); /// Setup the signal handler /// must be called from main extern void SetupSignalHandler(void); /// This thread is spawn when receiving SIGTERM and the mutex in the CWorkScheduler /// was locked at that time extern boost::thread terminator_thread; #endif /* SIGNALHANDLER_H_ */ solarpowerlog-solarpowerlog-0.26/src/interfaces/000077500000000000000000000000001444065341000221505ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/interfaces/CCapability.cpp000066400000000000000000000025621444065341000250450ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file CCapability.cpp * * \date Created on: May 16, 2009 * \author Tobias Frost (coldtobi) * * \sa \ref CapaConcept "Capability Concept" * \sa \ref CCapability */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "CCapability.h" using namespace std; CCapability::CCapability( const string& descr, IValue *val, IInverterBase *datasrc ) { description = descr; source = datasrc; value = val; } CCapability::~CCapability() { if (value) delete value; value = NULL; } solarpowerlog-solarpowerlog-0.26/src/interfaces/CCapability.h000066400000000000000000000150401444065341000245050ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file CCapability.h * * \date Created on: May 16, 2009 * \author Tobias Frost (coldtobi) */ /** \page CapaConcept "The Capabilites Concept" * * This gives an introduction how capabilites works and whats their purpose is. * For details, please also see the relevant interfaces, mostly CCapability and * IValue. * * Further information can also be found on the chapter over Inverters and * DataFilters. * * \section CapPurpose "Purpose" * * Inverters have some datas to report. These datas might be states, * measurements or other information. Solarpowerlog bundles these kind of * information into one object type, a Capability. The bundle contains as well * the name as also the associated value. * * The name of a capability is used to identified the kind of information * stored. This way most information are abstraced and brought to the same * common denominator: * * The inverter (data source) sets up the value and the receiver of the data can * query for it by the nameas know exactly how to interpret it, as this is * exactly defined. * * The inverter is not required to set up the complete set of information -- it * will just give the infos it has. On the other side, the receiver can not * expect that all information are always available. * * Even if this increases complexity of the receivers implementation -- it has * to make the features it has dependent on the information diveristy it * gets -- it also makes it far more flexible: The program will work on * inverters which delivers many information as well with inverters which will * deliver only a basic set. * * Also, if a inverter decides to give even more information, this data can * easily be added: The receiver which do not know about the infos won't care, * and the others can make use of it. * * Another use-case is, that data receiver can also use redudnt data sources: * They can reconstruct (unknown) pieces of information out of others. * For example, if the power in and power out is known, a filter can calculate * the efficeancy. If the inverter gives that value, the data receiver can use * that value. (actually a DataFilter is planned to make use of that...) * * \sa Please see the file Capabilities.h for some defined capabilites. * \sa IValue for the Value storage * * \section CapaObserverSubscriberPattern "Capability Change Notifications" * * The Capability interface is designed using the observer design pattern: * The Capability is the Subject and all data sources are the observers, * which will get notified on changes. * * Note, that this notification is not automated: The one setting the value * has still to call the CCapabiltiy::notify() function. * * The Subject-Pattern is implemented by inheritating from the IObserverSubject * interface. * * \section Cappredefined "Required Capabilites" * * In the file Capabilites.h some of the predefined Capbabilites are markes as * required. These means, that everyone using the interface has to implement * (data source) or at least is required to subscribe to this information. * * These Capabilites are helping to manage the capability system. * * \section CapaWarnings "Important Implementation Notes" * * - The class also contains a back-link to the generating inverter, using the * data field source. A user of this field should always be aware, that the * pointer might be NULL. * * - Even if the interface allows you, only the data-source should be change a * value. * * */ #ifndef CCAPABILITY_H_ #define CCAPABILITY_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "patterns/IObserverSubject.h" #include "patterns/IValue.h" #include class IInverterBase; class IValue; using namespace std; /** Implements the CCapability Concept * * Please see the \ref CapaConcept "Capability Concept" description for details on the concepts. * * The class is noncopyable as usually a Capability is non-transferable due * to the assosicated Value. * * OBJECTS ASSESTS (OBJECT OWNERSHIPS) * * To understand object responsibility (espcially who's task it is to delete * that object) , and as the class contains some pointers * to foreign objects, it is important to define the responsbility on these * objects. * * - source NOT RESPONSIBLE * - value TAKES OWNERSHIP. DELETES ON DESTRUCTION. */ class CCapability : public IObserverSubject , boost::noncopyable { public: /** Construct a Capability, specify the concrete objects it should use. * * \param descr String used to identify the Capabilty. Should be * descriptive,as it might be present to the end-user. * * \param val Associated Value object. * * \param datasrc Pointer to the generating Inverter. Can be used for * datafilter to find out what the origin of the data is. * * */ CCapability( const string& descr, IValue *val, IInverterBase *datasrc = NULL ); /** Destructor. Deletes the value object as well. (See CCapability for details)*/ virtual ~CCapability(); /** Get Access to the description * * \returns description. */ const string & getDescription() const { return description; } /** Get a the pointer to the one feeding this data. * * \returns Pointer. * * \warning may return NULL*/ virtual IInverterBase *getSource() const { return source; } /** Returns the pointer t*/ virtual IValue *getValue() const { return value; } protected: /** storage for the description passed by the creator */ string description; /** storage for the source-pointer passed by the creator */ IInverterBase *source; /** storage for the value-pointer passed by the creator */ IValue *value; }; #endif /* CCAPABILITY_H_ */ solarpowerlog-solarpowerlog-0.26/src/interfaces/CDebugHelper.cpp000066400000000000000000000036561444065341000251570ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2011-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file CDebugHelper.cpp * * Created on: 30.12.2011 * Author: tobi */ #include "CDebugHelper.h" #include "configuration/Registry.h" CDebugHelperCollection::~CDebugHelperCollection() { if (registered) Registry::Instance().RemoveDebugCollection(this); std::list::iterator it = this->dobjlist.begin(); while (it != dobjlist.end()) { // IDebugObject *p = delete (*it); it++; } } void CDebugHelperCollection::Register(IDebugObject *dobj) { dobjlist.push_back(dobj); if (!registered) { Registry::Instance().AddDebugCollection(this); registered = true; } } void CDebugHelperCollection::Unregister(IDebugObject *dobj) { dobjlist.remove(dobj); } void CDebugHelperCollection::Dump() { std::cerr << "CONTEXT " << context << std::endl; std::list::iterator it = this->dobjlist.begin(); while (it != dobjlist.end()) { // IDebugObject *p = std::cerr << "\t"; (*it)->Dump(); it++; } } IDebugObject::~IDebugObject() { } solarpowerlog-solarpowerlog-0.26/src/interfaces/CDebugHelper.h000066400000000000000000000054051444065341000246160ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2011-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file CDebugHelper.h * * Class to collect internal runtime data helpful for debugging. * * DbugHelper is a runtime-debug-data storage to registers runtime data from the * components and offers a interface to dump this inforation to cerr. * * \section cdebughelperusage Usage * * - Register your #include #include /** \fixme COMMENT ME * * * TODO DOCUMENT ME! */ /** \fixme COMMENT ME * * * TODO DOCUMENT ME! */ class IDebugObject { public: IDebugObject() {}; virtual ~IDebugObject(); virtual void Dump(void) = 0; }; template class CDebugObject : public IDebugObject { public: virtual ~CDebugObject() {}; CDebugObject(std::string &identifier, T &v) { id = identifier; value = &v; } CDebugObject(const char *identifier, T &v) { id = identifier; value = &v; } CDebugObject(const char *identifier, void *v) { id = identifier; value = &v; } virtual void Dump(void) { std::cerr << id << "=" << *value << std::endl; } std::string id; T *value; }; class CDebugHelperCollection { public: virtual ~CDebugHelperCollection(); CDebugHelperCollection(std::string &context) { this->context = context; this->registered = false; // not yet registered at Registry } CDebugHelperCollection(const char *context) { this->context = context; this->registered = false; // not yet registered at Registry } void Register(IDebugObject *dobj); void Unregister(IDebugObject *dobj); void Dump(); private: bool registered; std::string context; std::list dobjlist; }; #endif /* CDEBUGHELPER_H_ */ solarpowerlog-solarpowerlog-0.26/src/interfaces/CMutexHelper.cpp000066400000000000000000000023571444065341000252300ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2014 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file CMutexAutoLock.cpp * * Created on: May 17, 2009 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "CMutexHelper.h" CMutexAutoLock::CMutexAutoLock(boost::mutex &mutex, bool allow_recursive) : _mutex(mutex), _allow_recursive(allow_recursive), _locked(0) { lock(); } CMutexAutoLock::~CMutexAutoLock() { if (_locked) _mutex.unlock(); } solarpowerlog-solarpowerlog-0.26/src/interfaces/CMutexHelper.h000066400000000000000000000057741444065341000247030ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2014 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file CMutexAutoLock.h * * Created on: May 17, 2009 * Author: tobi */ #ifndef CMUTEXHELPER_H_ #define CMUTEXHELPER_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include /** Mutex Wrapper * * The library makes you keeping track when you entered the * Mutex. This generates the problem, that if the function * is left without considering the lock, the lock remains forever. * * This class helps in this case: Just create an object of it, * passing the "this" pointer to the constructor * (assuming your class is derived from Mutex) and enjoy the lock. * * The lock is automatically destroyed on destrucing the object. */ class CMutexAutoLock { public: /** Construct the MutexAutoLock object and obtain lock to the mutex. * * @param mutex object to control * @param allow_recursive whether additional lock() and unlock() * should be accounted. if false, every call to lock() and unlock() * will lock and unlock, if true, the mutex will only be unlocked if * the amount of calls to lock() matches the unlock()s -- and on object * destruction. */ CMutexAutoLock(boost::mutex &mutex, bool allow_recursive = false); /** Destructor. Will also unlock the mutex if held during destruction * */ virtual ~CMutexAutoLock(); /** Unlock a previously locked mutex * * In recursive mode, only unlocked when the number of calls to lock() * matches the ones to unlock() */ void unlock(void) { if (_locked) { _locked--; if (0 == _locked || !_allow_recursive) { _mutex.unlock(); _locked = 0; } } } /** Lock the mutex * * \note This will block if the mutex is already held by e.g another * instance. */ void lock(void) { if (!_locked) _mutex.lock(); _locked++; } private: /// mutex object boost::mutex &_mutex; /// store state whether recursive mode or not bool _allow_recursive; /// current lock state -- u int _locked; }; #endif /* CMUTEXHELPER_H_ */ solarpowerlog-solarpowerlog-0.26/src/interfaces/CTimedWork.cpp000066400000000000000000000170531444065341000246720ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2014 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file CTimedWork.cpp * * Created on: May 18, 2009 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #endif // #define CTIMEDWORK_DEBUG #include #include #include #include #include #include "CTimedWork.h" #include "interfaces/CMutexHelper.h" #include "configuration/Registry.h" CTimedWork::CTimedWork( CWorkScheduler *sch ) : sch(sch), terminate(false) #ifdef CTIMEDWORK_DEBUG , dhc("CTimedWork") #endif { #ifdef CTIMEDWORK_DEBUG this->work_completed=0; this->work_received=0; zero_waits = 0; dhc.Register(new CDebugObject("work_received",work_received)); dhc.Register(new CDebugObject("work_completed",work_completed)); thread_interrupts_count = thread_interrupts_sighandler = thread_interrupts_balance = 0; dhc.Register(new CDebugObject("thread_interrupts_count",thread_interrupts_count)); dhc.Register(new CDebugObject("thread_interrupts_sighandler",thread_interrupts_sighandler)); dhc.Register(new CDebugObject("thread_interrupts_balance",thread_interrupts_balance)); dhc.Register(new CDebugObject("works_pending",works_pending)); dhc.Register(new CDebugObject("zero_waits",zero_waits)); thread_at_wait_point = 0; thread_wants_mutex = 0; thread_has_mutex = 0; ctimedwork_has_mutex = 0; ctimedwork_wants_mutex = 0; dhc.Register(new CDebugObject("thread_at_wait_point",thread_at_wait_point)); dhc.Register(new CDebugObject("thread_wants_mutex",thread_wants_mutex)); dhc.Register(new CDebugObject("ctimedwork_wants_mutex",ctimedwork_wants_mutex)); dhc.Register(new CDebugObject("thread_has_mutex",thread_wants_mutex)); dhc.Register(new CDebugObject("ctimedwork_has_mutex",ctimedwork_wants_mutex)); #endif } CTimedWork::~CTimedWork() { if (!terminate) RequestTermination(); thread.join(); std::multimap::iterator it; for(it = TimedCommands.begin(); it != TimedCommands.end(); it++) { delete (*it).second; } TimedCommands.clear(); } // Called on execution of the thread. void CTimedWork::run() { thread = boost::thread(boost::bind(&CTimedWork::_main, this)); } void CTimedWork::ScheduleWork( ICommand *Command, struct timespec ts ) { bool need_interrupt = false; boost::posix_time::ptime first; boost::posix_time::ptime n = boost::posix_time::microsec_clock::local_time(); #ifdef CTIMEDWORK_DEBUG LOGDEBUG(Registry::GetMainLogger(),"NOW:\t" << n); #endif boost::posix_time::seconds s(ts.tv_sec); boost::posix_time::millisec ms(ts.tv_nsec / (1000 * 1000)); n = n + s + ms; { #ifdef CTIMEDWORK_DEBUG ctimedwork_wants_mutex=1; #endif CMutexAutoLock m(mut); #ifdef CTIMEDWORK_DEBUG ctimedwork_wants_mutex=1;ctimedwork_has_mutex=1; this->work_received++; #endif if (0 == TimedCommands.size()) { #ifdef CTIMEDWORK_DEBUG LOGDEBUG(Registry::GetMainLogger(),"Q empty"); #endif need_interrupt = true; } else { first = (TimedCommands.begin())->first; #ifdef CTIMEDWORK_DEBUG LOGDEBUG(Registry::GetMainLogger(),"first\t" << first << "\tnew " << n << " \t\tdelta " << n-first ); #endif if (first > n) { need_interrupt = true; } } TimedCommands.insert( pair(n, Command)); first = (TimedCommands.begin())->first; #ifdef CTIMEDWORK_DEBUG if (need_interrupt) LOGDEBUG(Registry::GetMainLogger(),"new 1st\t" << first ); #endif #ifdef CTIMEDWORK_DEBUG ctimedwork_wants_mutex=0; #endif m.unlock(); // should not be needed, but paranoid me... #ifdef CTIMEDWORK_DEBUG ctimedwork_has_mutex=0; #endif } #ifdef CTIMEDWORK_DEBUG (volatile int)thread_interrupts_balance++; thread_interrupts_count++; #endif if (need_interrupt) { #ifdef CTIMEDWORK_DEBUG LOGDEBUG(Registry::GetMainLogger(),"interrupted"); #endif thread.interrupt(); } #ifdef CTIMEDWORK_DEBUG (volatile int)thread_interrupts_balance--; #endif } void CTimedWork::_main() { boost::posix_time::ptime n, w; boost::posix_time::time_duration s; while (!terminate) { #ifdef CTIMEDWORK_DEBUG this->thread_wants_mutex=1; #endif mut.lock(); #ifdef CTIMEDWORK_DEBUG this->thread_has_mutex=1; #endif // get now n = boost::posix_time::microsec_clock::local_time(); #if 0 cerr << "Now: " << (boost::posix_time::ptime)n << endl; #endif #if 0 cerr << "TIMED WORKS EXPIRATION" << endl; multimap::iterator it; for (it = TimedCommands.begin(); it != TimedCommands.end(); it++) { cerr << (*it).first << endl; } cerr << "TIMED WORKS LIST END" << endl; #endif #ifdef CTIMEDWORK_DEBUG this->works_pending = TimedCommands.size(); #endif if (!TimedCommands.empty()) { w = (TimedCommands.begin())->first; // cerr << "Waiting: " << to_simple_string(w) << endl; if (w > n) { s = w - n; // cerr << "Difference: " << to_simple_string(s) << endl; } else { ICommand *cmd = (TimedCommands.begin())->second; TimedCommands.erase(TimedCommands.begin()); #ifdef CTIMEDWORK_DEBUG this->works_pending = TimedCommands.size(); work_completed++; this->thread_wants_mutex=0; #endif mut.unlock(); #ifdef CTIMEDWORK_DEBUG this->thread_has_mutex=0; #endif sch->ScheduleWork(cmd); continue; } } else { #ifdef CTIMEDWORK_DEBUG this->zero_waits++; // wait requests for empty queues #endif s = boost::posix_time::hours(1); } #ifdef CTIMEDWORK_DEBUG this->thread_wants_mutex=0; #endif mut.unlock(); #ifdef CTIMEDWORK_DEBUG this->thread_has_mutex=0; #endif try { // cerr << "sleeping for " << boost::posix_time::to_simple_string(s) << endl; #ifdef CTIMEDWORK_DEBUG this->thread_at_wait_point=1; #endif boost::this_thread::sleep(s); #ifdef CTIMEDWORK_DEBUG this->thread_at_wait_point=0; #endif } catch (boost::thread_interrupted &e) { // sleep was interrupted, probably by new work. #ifdef CTIMEDWORK_DEBUG thread_interrupts_sighandler++; #endif } } } void CTimedWork::RequestTermination( void ) { terminate = true; thread.interrupt(); } solarpowerlog-solarpowerlog-0.26/src/interfaces/CTimedWork.h000066400000000000000000000067301444065341000243370ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2014 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file CTimedWork.h * * Created on: May 18, 2009 * Author: tobi */ #ifndef CTIMEDWORK_H_ #define CTIMEDWORK_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "patterns/ICommand.h" #include "CWorkScheduler.h" #include "boost/date_time/posix_time/posix_time.hpp" #include #include "interfaces/CDebugHelper.h" /** This class bundles a timed activity. * * The Timedwork are fed into a worker thread, which just waits for the time * to come and signal the event back to the CWorkScheduler object. * * On new requests, the worker thread will only be interrupted if the new event * expires earlier than the current one. * * \note: There was an bug in boost::thread in boost release 1.46 which * caused solarpowerlog to freeze after a while. This is why the code is * still littered with debug statements, enabled with the define * CTIMEDWORK_DEBUG (via CTimedWork.cpp or via C[XX]FLAGS) */ class CTimedWork { public: /** Constructor: Takes the scheduler to inform, the command to execute and the time when */ explicit CTimedWork( CWorkScheduler *sch ); virtual ~CTimedWork(); /// Thread entry point. void run(); /// Issue work in the future. (when ts elapsed) void ScheduleWork( ICommand *Command, struct timespec ts ); /// Ask thread to terminate. void RequestTermination( void ); private: CTimedWork() : sch(NULL) #ifdef CTIMEDWORK_DEBUG ,dhc("CTimedWork") #endif { } void _main( void ); /** Helper struct for the multimap -- to sort the entries. */ struct time_compare { bool operator()( const boost::posix_time::ptime t1, const boost::posix_time::ptime t2 ) const { if (t1 > t2) return false; return true; } }; /** holds the command and timing information. * The multimap is sorted accordingly. */ std::multimap TimedCommands; /** it is attached to this scheduler. * (The scheduler keeps books of its processes)*/ CWorkScheduler *sch; volatile bool terminate; boost::thread thread; boost::mutex mut; #ifdef CTIMEDWORK_DEBUG private: CDebugHelperCollection dhc; int work_received, work_completed; int thread_interrupts_count; int thread_interrupts_sighandler; int thread_interrupts_balance; int works_pending; int zero_waits; int thread_at_wait_point; int thread_wants_mutex; int ctimedwork_wants_mutex; int thread_has_mutex; int ctimedwork_has_mutex; #endif }; #endif /* CTIMEDWORK_H_ */ solarpowerlog-solarpowerlog-0.26/src/interfaces/CWorkScheduler.cpp000066400000000000000000000117331444065341000255450ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2014 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file CWorkScheduler.cpp * * Created on: May 17, 2009 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "CWorkScheduler.h" #include "patterns/ICommand.h" #include "CTimedWork.h" #include "semaphore.h" #include "interfaces/CMutexHelper.h" #include "patterns/ICommandTarget.h" #include "configuration/Registry.h" using namespace std; CWorkScheduler::CWorkScheduler() : dhc("CWorkScheduler") { works_received = 0; works_completed = 0; works_timed_scheduled = 0; dhc.Register(new CDebugObject("instance", this)); dhc.Register(new CDebugObject("works_received", works_received)); dhc.Register(new CDebugObject("works_completed", works_completed)); dhc.Register( new CDebugObject("works_timed_scheduled", works_timed_scheduled)); sem_init(&semaphore, 0, 0); // generate thread for the timed work facility. timedwork = new CTimedWork(this); timedwork->run(); } CWorkScheduler::~CWorkScheduler() { delete timedwork; broadcast_subscribers.clear(); sem_destroy(&semaphore); } bool CWorkScheduler::DoWork(bool block) { if (!block) { CMutexAutoLock cma(mut); if (CommandsDue.empty()) { return false; } } sem_wait(&semaphore); ICommand *cmd = getnextcmd(); if (cmd->getCmd() > BasicCommands::CMD_BROADCAST_MAX) { cmd->execute(); } else { if (!broadcast_subscribers.empty()) { LOGDEBUG_SA(Registry::GetMainLogger(), LOG_SA_HASH("CWSSubscriber"), "Handling broadcast-event cmd=" << cmd->getCmd() << " subscribers=" << broadcast_subscribers.size()); std::set::iterator it; for (it = broadcast_subscribers.begin(); it != broadcast_subscribers.end(); it++) { (*it)->ExecuteCommand(cmd); } } else { LOGDEBUG_SA(Registry::GetMainLogger(), LOG_SA_HASH("CWSSubscriber"), "Handling broadcast-event cmd=" << cmd->getCmd() << " NO subscribers"); } } delete cmd; return true; } void CWorkScheduler::RegisterBroadcasts(ICommandTarget* target, bool subscribe) { if (subscribe) { broadcast_subscribers.insert(target); } else { broadcast_subscribers.erase(target); } } ICommand *CWorkScheduler::getnextcmd(void) { // Obtain Mutex to make sure... CMutexAutoLock cma(mut); ICommand *cmd = CommandsDue.front(); CommandsDue.pop_front(); works_completed++; return cmd; } bool CWorkScheduler::ScheduleWork(ICommand *Command, bool tryonly) { // assert if a broadcast event has a ITarget set. (This indicates a bug) //LOGERROR(Registry::GetMainLogger(),"cmd=" <getCmd() << " trgt="<< Command->getTrgt()); assert( !((Command->getCmd() <= BasicCommands::CMD_BROADCAST_MAX && Command->getTrgt()))); if (Command->getCmd() >= BasicCommands::CMD_BROADCAST_MAX && !Command->getTrgt()) { // Fire-and-Forget commmand. Just delete it. delete Command; return true; } if (tryonly) { if (!mut.try_lock()) return false; } else { mut.lock(); } if (Command->getCmd() <= BasicCommands::CMD_BROADCAST_MAX) { LOGDEBUG(Registry::GetMainLogger(), "Broadcast event accepted cmd=" << Command->getCmd()); } CommandsDue.push_back(Command); works_received++; mut.unlock(); sem_post(&semaphore); return true; } void CWorkScheduler::ScheduleWork(ICommand *Command, struct timespec ts) { // assert if a broadcast event has a ITarget set. (This indicates a bug) assert( !((Command->getCmd() <= BasicCommands::CMD_BROADCAST_MAX && Command->getTrgt()))); if (Command->getCmd() >= BasicCommands::CMD_BROADCAST_MAX && !Command->getTrgt()) { // Fire-and-Forget command. Just delete it. delete Command; return; } works_timed_scheduled++; timedwork->ScheduleWork(Command, ts); } solarpowerlog-solarpowerlog-0.26/src/interfaces/CWorkScheduler.h000066400000000000000000000100531444065341000252040ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2014 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file CWorkScheduler.h * * Created on: May 17, 2009 * Author: tobi */ #ifndef CWORKSCHEDULER_H_ #define CWORKSCHEDULER_H_ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif #include #include #include #include #include #include "interfaces/CDebugHelper.h" class ICommand; class ICommandTarget; class CTimedWork; /** This class implements the work scheduler: * * Objects derived from CommandTarget can schedule work to be done: * * call again later: * (when they are not able to complete it immediately) * * call again at .... * (when they expect to do some work in some specific time * * \warning for all actions, the Objects will be passed by reference. At this * point of time, CWorkScheduler is owner of the object and will destroy it later, * when used. * */ class CWorkScheduler { friend class CTimedWork; public: CWorkScheduler(); virtual ~CWorkScheduler(); /** Schedule an immediate work * * \param Command to be issued * \param tryonly does not issue the work if the underlying mutex cannot be * obtained immediately (useful if called from e.g signal handler) * * \returns true if work has been scheduled, false if not * It is guaranteed to return true if tryonly is false. */ bool ScheduleWork(ICommand *Command, bool tryonly=false); /** Schedule a work for later */ void ScheduleWork(ICommand *Commmand, struct timespec ts); /** Register for broadcast events. * * There (will) be some broadcast events, and if your datafilter/inverter * is interested in those. * * See the file BasicCommands.h for defined broadcast events, but those * events are only broadcasted through the mainscheduler. * * A specialty about broadcast events is, that they will have no callback * in the ICommand. This is asserted by ScheduleWork(). * * \param target which wants to receive the broadcast events * \param subscribe set to false if you want to unsubscribe to the events. * * \note solarpowerlog will only issue */ void RegisterBroadcasts(ICommandTarget *target, bool subscribe=true); /** Call this method to do dispatch due work. * Note: Returns after each piece of work has been done! * * returns true, if work has been done, false, if no work was available. * * \param block if false (default), it will return if there is no work, else it will * wait for work. */ bool DoWork(bool block=false); private: /// Stores the CTimedWork Object, the handler for works to be executed in /// at a specific time. CTimedWork *timedwork; /// Stores the pending work std::list CommandsDue; /** get the next new command in the list. * (Thread safe)*/ ICommand *getnextcmd(void); private: /// Semaphore to wait on until work arrives (probably via timed works sem_t semaphore; /// stores for the mainscheduler the list of broadcast subscribers std::set broadcast_subscribers; protected: /// Mutex to protect against concurrent accesses. boost::mutex mut; private: CDebugHelperCollection dhc; int works_received; int works_completed; int works_timed_scheduled; }; #endif /* CWORKSCHEDULER_H_ */ solarpowerlog-solarpowerlog-0.26/src/patterns/000077500000000000000000000000001444065341000216655ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/src/patterns/CValue.h000066400000000000000000000113641444065341000232220ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2014 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file CValue.h * * Created on: May 14, 2009 * Author: tobi * * Template-Class for the concrete Values. * * CValue is used to story any type as data. * It also offers type check to ensure that the cast will be ok. * * */ #ifndef CVALUEX_H_ #define CVALUEX_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "IValue.h" #include #include #include /** This helper class allows type-identificatino without RTTI. * See http://ciaranm.wordpress.com/2010/05/24/runtime-type-checking-in-c-without-rtti/ * for the idea. * Basically, MagicNumbers is a singleton to supply unique numbers and * the templated function will keep the number same for each type. */ class MagicNumbers { protected: static int next_magic_number() { static int magic = 0; return magic++; } template static int magic_number_for() { static int result(next_magic_number()); return result; } template friend class CValue; }; /** Generalized storage for data. * A CValue stores one information, regardless of the type.*/ template class CValue : public IValue { public: CValue() : IValue(MagicNumbers::magic_number_for()) { value = T(); } CValue(const T &set) : IValue(MagicNumbers::magic_number_for()) { value = set; SetTimestamp(boost::posix_time::second_clock::local_time()); SetValid(); } virtual ~CValue() {}; /// Serves as a virtual copy constructor. virtual CValue* clone() { return new CValue(*this); } void Set(T value, boost::posix_time::ptime timestamp = boost::posix_time::second_clock::local_time()) { this->value = value; SetTimestamp(timestamp); SetValid(); } T Get(void) const { return value; } virtual void operator=(const T& val) { value = val; SetTimestamp(boost::posix_time::second_clock::local_time()); SetValid(); } virtual void operator=(const CValue &val) { value = val.Get(); SetTimestamp(val.GetTimestamp()); SetValid(val.IsValid()); } virtual operator std::string() { std::stringstream ss; ss << value; return ss.str(); } virtual bool operator==(IValue &v) { if (GetInternalType() == v.GetInternalType()) { CValue &realv = (CValue&)v; return (realv.Get() == Get()); } throw std::bad_cast(); return false; } virtual bool operator!=(IValue &v) { if (GetInternalType() == v.GetInternalType()) { CValue &realv = (CValue&)v; return (realv.Get() != Get()); } throw std::bad_cast(); return false; } virtual IValue& operator=(const IValue &v) { if (&v == this) return *this; if (this->IsType(&v)) { CValue *rv = (CValue*)&v; this->value = rv->value; SetTimestamp(rv->GetTimestamp()); SetValid(rv->IsValid()); return *this; } throw std::bad_cast(); return *this; } /** Static interface function to determine at runtime the type of the CValue * object. * Usage example: * CValue cv_int; * IValue *iv1 = &cv_int; * cout << CValue::IsType(iv1); */ static bool IsType(const IValue *totest) { if (MagicNumbers::magic_number_for() == totest->GetInternalType()) { return true; } return false; } private: T value; }; // TODO check if factory really needed or substituted already by some other // pattern class CValueFactory { public: template static IValue* Factory() { return new CValue; } }; #endif /* CVALUEX_H_ */ solarpowerlog-solarpowerlog-0.26/src/patterns/ICommand.cpp000066400000000000000000000051621444065341000240640ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file ICommand.cpp * * Created on: May 17, 2009 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "patterns/ICommand.h" #include "patterns/ICommandTarget.h" ICommand::ICommand(int command, ICommandTarget *target, std::map dat) { this->cmd = command; this->trgt = target; this->dat = dat; } ICommand::ICommand(int command, ICommandTarget *target) { cmd = command; trgt = target; } /** Destructor, even for no need for destruction */ ICommand::~ICommand() { } /** Delegate the command to the one that should do the work */ void ICommand::execute() { // Allow also "fire-and-forget" commmands which will be done but never a // callback issued. if (trgt) trgt->ExecuteCommand(this); } /** Getter for the private cmd field (field is for Commandees use) */ int ICommand::getCmd() const { return cmd; } const boost::any ICommand::findData(const std::string &key) const { std::map::const_iterator it = dat.find(key); if (it != dat.end()) { return (*it).second; } throw(std::invalid_argument(key)); } void ICommand::mergeData(const ICommand &other) { // first delete all duplicate data, but only if the containers have data. // (this is needed as std::map insert won't change the content if a key is // alredy there) // FIXME: missed optimization: iterate only over the shorter one of both containers. if (dat.size() && other.dat.size()) { std::map::const_iterator it; for(it = dat.begin(); it != dat.end(); it++) { if(other.dat.count(it->first)) dat.erase(it->first); } } // and then merge the others data into the map dat.insert(other.dat.begin(), other.dat.end()); } solarpowerlog-solarpowerlog-0.26/src/patterns/ICommand.h000066400000000000000000000120311444065341000235220ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file ICommand.h * * Created on: May 17, 2009 * Author: tobi */ #ifndef ICOMMAND_H_ #define ICOMMAND_H_ #ifdef HAVE_CONFIG_H #include "config.h" #include "porting.h" #endif /// #define ICMD_WITH_DUMP_MEMBER #ifdef HAVE_INV_DUMMY /// Debug-Helper, define and you have the DumpData Member to dump at runtime /// A Icommand, /// (Will be automatically enabled if you have the dummy inverter enabled...) #define ICMD_WITH_DUMP_MEMBER #endif #include #include #include #include #include "configuration/ILogger.h" #include "Inverters/BasicCommands.h" #include // Tokens for ICommands (general meanings) /// Error indicator. Same as errno, integer. 0 for no error. #define ICMD_ERRNO "ICMD_ERRNO" /// Error indicator. /// Optional, but if exists it contains human readable error message #define ICMD_ERRNO_STR "ICMD_ERRMSG" class ICommandTarget; /** Encapsulates a command * * See the command pattern for details.... * * Commands are used, for example, to schedule work, * to check for completion, etc... * * NOTE: The one that calls execute should delete the object afterwards! * (like CWorkScheduler does...) * * \note New from Oct 2012: "Fire-and-forget"-ICommands. This are commands whose * ICommandTarget is NULL. They will be be answered, just deleted... * Purpose is to allow calls to subsystems which takes ICommands as callbacks * (like the IConnect ones) when the actual callback is unimportant. * */ class ICommand { public: ICommand(int command, ICommandTarget *target, std::map dat); ICommand(int command, ICommandTarget *target); virtual ~ICommand(); /// excecute the command void execute(); /// Getter for the command int getCmd() const; /** Find Data in Command * * Returns the data associated in the map to the given key * * \in param key to search for * \returns boost::any object with the data * * \throw std::invalid_argument { throws this if data is not existant. The * data of the invalid_argument is the key which was not found } */ const boost::any findData(const std::string & key) const; /// Setter for Command void setCmd(int cmd) { this->cmd = cmd; } /// Setter for the target of the command void setTrgt(ICommandTarget *trgt) { this->trgt = trgt; } /// Getter for the target ICommandTarget* getTrgt() const { return trgt; } /** Remove Data from Command * * Removes the named key from the data list of the command. * \note: As the underlaying storage is a std::map, * the associated boost::any object will be deleted. */ inline void RemoveData(const std::string & key) { dat.erase(key); } /** Remove all data from command. * * \note: As the underlaying storage is a std::map, * the associated boost::any objects will be deleted. */ inline void RemoveData() { dat.clear(); } /** Add/Replace Data from the Command * * Add new data, or if the data is already existing, replace * the data with the new one. * */ void addData(const std::string &key, const boost::any &data) { if (dat.count(key)) { dat.erase(key); } dat.insert(std::pair(key, data)); } /// Merge data from other ICommand into this one. void mergeData(const ICommand &other); #ifdef ICMD_WITH_DUMP_MEMBER /// Debug-Helper to dump all data which is stored in the ICommand. void DumpData(ILogger& logger) const { std::map::const_iterator it; LOGDEBUG(logger, "ICommand::DumpData() with command " << this->cmd); if (this->cmd < BasicCommands::CMD_BROADCAST_MAX) { LOGDEBUG(logger, "(BROADCAST COMMAND) " << this->cmd); } else { assert(this->trgt); LOGDEBUG(logger, "Target: " << trgt); } LOGDEBUG(logger, "dumping available data: "); for (it = dat.begin(); it != dat.end(); it++) { LOGDEBUG(logger, it->first); } LOGDEBUG(logger, "dumping done."); } #endif private: int cmd; ICommandTarget *trgt; std::map dat; }; #endif /* ICOMMAND_H_ */ solarpowerlog-solarpowerlog-0.26/src/patterns/ICommandTarget.cpp000066400000000000000000000023061444065341000252300ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file ICommandTarget.cpp * * Created on: May 17, 2009 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "ICommandTarget.h" ICommandTarget::ICommandTarget() { // TODO Auto-generated constructor stub } ICommandTarget::~ICommandTarget() { // TODO Auto-generated destructor stub } solarpowerlog-solarpowerlog-0.26/src/patterns/ICommandTarget.h000066400000000000000000000026311444065341000246760ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file ICommandTarget.h * * Created on: May 17, 2009 * Author: tobi */ #ifndef ICOMMANDTARGET_H_ #define ICOMMANDTARGET_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "ICommand.h" /** Interface for a command's target. * * * TODO DOCUMENT ME! */ class ICommandTarget { public: ICommandTarget(); virtual ~ICommandTarget(); virtual void ExecuteCommand(const ICommand *Command) = 0 ; }; #endif /* COMMANDTARGET_H_ */ solarpowerlog-solarpowerlog-0.26/src/patterns/IObserverObserver.cpp000066400000000000000000000044201444065341000260010ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file IObserverObserver.cpp * * Created on: May 12, 2009 * Author: tobi * * This files implements the Observer for the Observer Design Pattern. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "IObserverObserver.h" #include "IObserverSubject.h" using namespace std; /** Constructor for the Observer (Observer Pattern) * * The Observer will auto-susbscribe to the Subject, if the * parameter is supplied. (But what should do a observer without an subject?) */ IObserverObserver::IObserverObserver(IObserverSubject *subject) { /* auto-subscribe */ this->subject = NULL; if (subject != NULL) setSubject(subject); } /** The destructor will auto-unsubsribe before destroying the object. */ IObserverObserver::~IObserverObserver() { /* auto-unsubsribe */ if (subject) subject->UnSubscribe(this); // TODO Auto-generated destructor stub } /** Getter for the current Subject */ IObserverSubject *IObserverObserver::getSubject() const { return subject; } /** Set a new Subject, subscribe to it. * * Will also unsubscribe to the old subject, if available. * * Note: Will do nothing, if current Subject is the same as the new one. * */ void IObserverObserver::setSubject(IObserverSubject *subject) { if(this->subject == subject) return; if(this->subject) subject->UnSubscribe(this); this->subject = subject; subject->Subscribe(this); } solarpowerlog-solarpowerlog-0.26/src/patterns/IObserverObserver.h000066400000000000000000000030601444065341000254450ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file IObserverObserver.h * * Created on: May 12, 2009 * Author: tobi */ #ifndef OBSERVEROBSERVER_H_ #define OBSERVEROBSERVER_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif using namespace std; class IObserverSubject; /** \fixme COMMENT ME * * * TODO DOCUMENT ME! */ class IObserverObserver { public: IObserverObserver(IObserverSubject *subject = 0); virtual ~IObserverObserver(); virtual void Update(const class IObserverSubject * subject) = 0; virtual IObserverSubject *getSubject() const; virtual void setSubject(IObserverSubject *subject); private: IObserverSubject *subject; }; #endif /* OBSERVEROBSERVER_H_ */ solarpowerlog-solarpowerlog-0.26/src/patterns/IObserverSubject.cpp000066400000000000000000000047761444065341000256270ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file IObserverSubject.cpp * * Created on: May 12, 2009 * Author: tobi * * This file implements the Subject of the Observer Design Pattern */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "IObserverSubject.h" #include "IObserverObserver.h" #include using namespace std; #warning TODO Check if we should switch from std::list as container to smth searchable (to optimize CheckSubsctipzion / SetSubscription / Subscribe / Unsubscribe) void IObserverSubject::Subscribe( class IObserverObserver *observer ) { if (!CheckSubscription(observer)) listobservers.push_back(observer); } void IObserverSubject::UnSubscribe( class IObserverObserver *observer ) { if (CheckSubscription(observer)) listobservers.remove(observer); } void IObserverSubject::Notify( void ) { std::list::iterator i; for (i = listobservers.begin(); i != listobservers.end(); ++i) { (*i)->Update(this); } } unsigned int IObserverSubject::GetNumSubscribers( void ) { return listobservers.size(); } bool IObserverSubject::CheckSubscription( class IObserverObserver *observer ) { std::list::iterator i; for (i = listobservers.begin(); i != listobservers.end(); ++i) { if ((*i) == observer) return true; } return false; } void IObserverSubject::SetSubscription( class IObserverObserver *observer, bool subscribe ) { if (subscribe) Subscribe(observer); else UnSubscribe(observer); } IObserverSubject::IObserverSubject() { // TODO Auto-generated constructor stub } IObserverSubject::~IObserverSubject() { listobservers.clear(); // TODO Auto-generated destructor stub } solarpowerlog-solarpowerlog-0.26/src/patterns/IObserverSubject.h000066400000000000000000000035451444065341000252650ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file IObserverSubject.h * * Created on: May 12, 2009 * Author: tobi */ #ifndef OBSERVERSUBJECT_H_ #define OBSERVERSUBJECT_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include using namespace std; class IObserverObserver; /** \fixme COMMENT ME * * * TODO DOCUMENT ME! */ class IObserverSubject { public: virtual ~IObserverSubject(); virtual void Subscribe( class IObserverObserver* observer ); virtual void UnSubscribe( class IObserverObserver* observer ); virtual void SetSubscription( class IObserverObserver* observer, bool subscribe = true ); virtual bool CheckSubscription( class IObserverObserver *observer ); virtual void Notify( void ); virtual unsigned int GetNumSubscribers( void ); protected: IObserverSubject(); private: std::list listobservers; }; #endif /* OBSERVERSUBJECT_H_ */ /* Infos on pattern See: http://www.cs.clemson.edu/~malloy/courses/patterns/observerCo.html */ solarpowerlog-solarpowerlog-0.26/src/patterns/IValue.cpp000066400000000000000000000020531444065341000235560ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \file IValue.cpp * * Created on: May 13, 2009 * Author: tobi */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "IValue.h" // currently empty. solarpowerlog-solarpowerlog-0.26/src/patterns/IValue.h000066400000000000000000000067131444065341000232320ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2014 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file IValue.h * * \date May 13, 2009 * \Author: Tobias Frost (coldtobi) */ #ifndef IVALUE_H_ #define IVALUE_H_ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include /** IValue is the interface to arbitrary value storage. * * It is supposed to be derived, and the derived class is responsible for * type-correct storage. * * \ingroup factories */ class IValue { public: protected: template friend class CValue; public: int GetInternalType(void) const { return _type; } /** Interface method for easier transfer to strings. */ virtual operator std::string() = 0; /// Serves as a virtual copy constructor. virtual IValue* clone() = 0; virtual bool operator==(IValue &v) = 0; virtual bool operator!=(IValue &v) = 0; virtual IValue& operator=(const IValue &v) = 0; /** Determine if the data is valid * * \note the data will be set to valid automatically on * every call to Set or using the = operator. * * @return true if valid, false if invalid */ virtual bool IsValid(void) const { return _valid; } /** Invalidate datastate * * record data as invalid. */ virtual void Invalidate(void) { _valid = false; } /*** Get the timestamp associated with the last set * * @return timestamps. If never set, boost::posix_time::not_a_date_time */ virtual boost::posix_time::ptime GetTimestamp(void) const { return _timestamp; } /*** Set the timestamp to be associated with the data (usually the last set)* * * @param newtime boost::posix_time::ptime representing the timestamp */ virtual void SetTimestamp(const boost::posix_time::ptime &newtime) { _timestamp = newtime; } protected: /// Constructor IValue(int type) : _type(type), _valid(false), _timestamp(boost::posix_time::min_date_time) { } /** protected setter to explicitly set validity. */ virtual void SetValid(bool v = true) { _valid = v; } public: virtual ~IValue() { } private: /// Private to avoid accidental creation with default constructor. IValue() : _type(0), _valid(false), _timestamp(boost::posix_time::min_date_time) { } /// Internal typeid int _type; /// Data validty mark. (Set automatically with every set. use Invalidate() to /// void data bool _valid; /// Timestamp of last update. boost::posix_time::ptime _timestamp; }; #endif /* IVALUE_H_ */ solarpowerlog-solarpowerlog-0.26/src/porting.h000066400000000000000000000033621444065341000216640ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2012 Tobias Frost This program 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 program. If not, see . ---------------------------------------------------------------------------- */ /** \file porting.h * * This file defines anything that is needed to get solarpowerlog ported other operating systems. * * Even if the componenets of solarpowerlog are carefully selected to ensure portability, some "changes" are * needed for some systems. * * Currently, this file is needed to enable a build under cygwin/win32. * * Created on: Jun 24, 2009 * Author: tobi */ #ifndef PORTING_H_ #define PORTING_H_ #ifndef VERSION #error include config.h prior this header! #endif /* Windows stuff : For building under cygwin. */ #ifdef HAVE_WIN32_API #if 0 // compiles using XP and latest cygwin without this tweaks. #define _POSIX_SOURCE #define _WIN32_WINNT 0x0501 #define __USE_W32_SOCKETS 1 #endif #endif /** declare explicit if not understood by compiler */ #ifndef HAVE_EXPLICIT #define explict #endif #endif /* PORTING_H_ */ solarpowerlog-solarpowerlog-0.26/src/solarpowerlog.cpp000066400000000000000000000443371444065341000234430ustar00rootroot00000000000000/* ---------------------------------------------------------------------------- solarpowerlog -- photovoltaic data logging Copyright (C) 2009-2015 Tobias Frost 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 . ---------------------------------------------------------------------------- */ /** \defgroup Concepts Developer: Basic Concepts * * This section describes some basics concepts used in deveoping the software. */ /** * \file solarpowerlog.cpp * * \mainpage *

Welcome to solarpowerlog's developer documentation.

* * This documentation purpose is to understand the internals and conecpts used, * so that the software can easily be enhanced. * * However, the best way to understand the program is jumping into cold water: * Fire up your debugger and look how the program executes. * * \sa \ref mainBasicConcepts "Basic Concepts" * * \page mainBasicConcepts Basic Concepts * * To get an overview how solarpowerlog is designed, lets take a look at some * basic concepts. * * \section mbcinterfaces "Code against interfaces, not implementations" * * All base classes are designed as interfaces. This allows loosly coupled * objects and also allows to easier code reuse. * * For example, the connection classes are defined through IConnect. When using * IConnects, the inverter simply does not need to know which is its * communication method, it will just use the interface and will be fine. * * \section mbcfactories Design Pattern: Factories * * Usually object generation is done by factories. The Factories gets an * identifier (usually a string) and return the created object. * * Factories allows that new specializations of interfaces can be added to * the program without the need to change any of the other classes. * * For example, if you add a fancy bluetooth class, you just create a IConnect * based CConnectionBluetooth, implement it and add its id-string to the * IConnectFactory. * Now, all the inverters can instanciate (via their comms settings) a * CConnectionBluetooth without knowing actually knowing the details. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #if defined HAVE_LIBLOG4CXX #include #include #include #include #include #endif #ifdef HAVE_OPENLOG #include #endif #include "configuration/ILogger.h" #include "configuration/ConfigCentral/CConfigCentral.h" #ifdef HAVE_CMDLINEOPTS #include #endif #include "configuration/Registry.h" #include "interfaces/CWorkScheduler.h" #include "patterns/ICommand.h" #include "patterns/ICommandTarget.h" #include "Inverters/factories/IInverterFactory.h" #include "Inverters/factories/InverterFactoryFactory.h" #include "Inverters/interfaces/InverterBase.h" #include "DataFilters/interfaces/factories/IDataFilterFactory.h" #include "DataFilters/interfaces/IDataFilter.h" #include "configuration/CConfigHelper.h" #include "interfaces/CDebugHelper.h" #include "daemon.h" using namespace std; #ifdef HAVE_LIBLOG4CXX using namespace log4cxx; #endif /** this array of string specifies which sections int the config file must be present. * The program will abort if any of these is missing. */ static const char *required_sections[] = { "application", "inverter", "inverter.inverters", "logger", "logger.loggers" }; /** Just dump the read config to cout.... (the values are automatically promoted to a string...) Use with: [code] libconfig::Setting &set = Registry::Configuration()->getRoot(); if (set.getName()) cout << set.getName(); else cout << "anonymous"; cout << " has the Path \"" << set.getPath() << "\" and Type " << set.getType() << " and has a Lenght of " << set.getLength() << endl; DumpSettings(set); [/code] */ void DumpSettings(libconfig::Setting &set) { cout << "line " << set.getSourceLine() << " "; if (set.getPath() != "") cout << set.getPath() << " "; if (!set.getName()) { cout << "(anonymous) "; } try { std::string s = set; cout << "= " << s << "\t is"; } catch (...) { } try { float s = set; cout << "= " << s << "\t is"; } catch (...) { } try { bool s = set; cout << "= " << s << "\t is"; } catch (...) { } if (set.isAggregate()) { cout << " aggregate"; }; if (set.isArray()) { cout << " array"; } if (set.isGroup()) { cout << " group"; } if (set.isList()) { cout << " list"; } //if (set.getPath() != "" ) cout << set.getPath() << "." ; if (set.isNumber()) cout << " number"; if (set.isScalar()) cout << " scalar"; cout << endl; for (int i = 0; i < set.getLength(); i++) { libconfig::Setting &s2 = set[i]; DumpSettings(s2); } } int main(int argc, char* argv[]) { CDebugHelperCollection dhc("main section"); bool error_detected = false; bool dumpconfig = false; string configfile = "solarpowerlog.conf"; long autoterminate = 0; string printsnippets; progname = argv[0]; dhc.Register(new CDebugObject("progname", progname)); dhc.Register(new CDebugObject("argc", argc)); for (int i=0; i(buf, argv[i])); } #ifdef HAVE_CMDLINEOPTS using namespace boost::program_options; options_description desc("Program Options"); desc.add_options() ("help", "this message") ("conf,c", value(&configfile), "specify configuration file") ("version,v", "display solarpowerlog version") ("background,b", value(&background)->zero_tokens(), "run in background.") ("dumpcfg", value(&dumpconfig)->zero_tokens(), "Dump configuration structure, then exit") ("chdir", value(&rundir), "working directory for daemon " " (only used when running as a daemon). Defaults to /") ("stdout",value(&daemon_stdout), "redirect stdout to this file " "(only used when running as a daemon). Defaults to /dev/null") ("stderr", value(&daemon_stderr), "redirect stderr to this " "file (only used when running as a daemon). Defaults to /dev/null") ("pidfile", value(&pidfile), "create a pidfile after the daemon has been started. " "(only used when running as a daemon.) Default: no pid file") ("autoterminate", value(&autoterminate), "terminate after performing this amount of work. " "This feature is intended for debugging, e.g valgrind runs.") ("printsnippet", value(&printsnippets), "print configuration snippets to stdout. " "the parameter specifies which snippet should be generated and is " "of the format [::]::[.] " "category is either inverter, filter or communication. " "subcategroy is for inverters specifing the manufacturer and " "target is the final target to get the snippet from. Note that " "this function is intended as development aid to keep snippets and code in sync. " " solarpowerlog will exit after the snippet has been dumped." ); variables_map vm; try { store(parse_command_line(argc, argv, desc), vm); notify(vm); } catch (exception &e) { cerr << "commandlinge options problem:" << desc << "\n" << e.what() << "\n"; return 1; } if (vm.count("help")) { cout << desc << "\n"; return 0; } if (vm.count("version")) { cout << PACKAGE_STRING << endl; return 0; } #else if (argc > 1) { (void) argv; // remove warning about unused parameter. cerr << "This version does not support command line options." << endl; exit(1); } #endif if (!printsnippets.empty()) { // minimum bootstraping: Fake config and logging set to OFF. Registry::Instance().FakeConfig(); BasicConfigurator::configure(); LoggerPtr l = Logger::getRootLogger(); l->setLevel(Level::toLevel(Level::OFF_INT)); Registry::Instance().GetMainLogger().SetLoggerLevel(Level::toLevel(Level::OFF_INT)); std::string cat, subcat, target; try { size_t s = printsnippets.find("::"); cat = printsnippets.substr(0,s); printsnippets = printsnippets.substr(s+2); if (cat == "inverter") { s = printsnippets.find("::"); cat = printsnippets.substr(0,s); printsnippets = printsnippets.substr(s+2); std::auto_ptr factory(InverterFactoryFactory::createInverterFactory(cat)); if (!factory.get()) { cerr << "printsnippet: Could not create inverter factory." << endl; return 1; } std::auto_ptr inverter(factory->Factory(printsnippets,"name","empty")); if (!inverter.get()) { cerr << "printsnippet: Could not create inverter object." << endl; return 1; } std::auto_ptr cfg(inverter->getConfigCentralObject(NULL)); if (!cfg.get()) { cerr << "printsnippet: Could not create inverter's configuration information." << endl; return 1; } cout << cfg->GetConfigSnippet(); return 0; } else if ( cat == "communication") { } else if ( cat == "datafilter") { s = printsnippets.find("::"); cat = printsnippets.substr(0,s); IDataFilterFactory dffactory; std::auto_ptr datafilter( dffactory.FactoryByName(cat, "name", "empty")); if (!datafilter.get()) { cerr << "printsnippet: Could not create datafilter object." << endl; return 1; } std::auto_ptr cfg(datafilter->getConfigCentralObject(NULL)); if (!cfg.get()) { cerr << "printsnippet: Could not create inverter's configuration information." << endl; return 1; } cout << cfg->GetConfigSnippet(); return 0; } else { cerr << "printsnippets: unkown category." << endl; return 1; } } catch (...) { cerr << "Error while handling printsnippets" << endl; return 1; } return 0; } if (!Registry::Instance().LoadConfig(configfile)) { cerr << "Could not load configuration " << configfile << endl; exit(1); } if (dumpconfig) { cout << "Dumping structure of " << configfile << endl; Registry::Configuration()->setAutoConvert(true); DumpSettings(Registry::Configuration()->getRoot()); exit(0); } // Note: As a limitation of libconfig, one cannot create the configs // structure. // Therefore we check here for the basic required sections and abort // if they are missing { libconfig::Config *cfg = Registry::Configuration(); libconfig::Setting &rt = cfg->getRoot(); for (unsigned int i = 0; i < sizeof(required_sections) / sizeof(char*); i++) { if (!rt.exists(required_sections[i])) { cerr << " Configuration Check: Required Section " << required_sections[i] << " missing" << endl; error_detected = true; } } } if (error_detected) { cerr << "Error detected" << endl; exit(1); } #if defined HAVE_LIBLOG4CXX #ifdef HAVE_OPENLOG // prepare the syslog, needed if we gonna log to it // (if the user configures this, as the liblog4cxx supports syslog as well) openlog(progname, LOG_PID, LOG_USER); #endif // Activate Logging framework { string tmp; LoggerPtr l = Logger::getRootLogger(); CConfigHelper global("application"); global.GetConfig("dbglevel", tmp, (std::string) "ERROR"); l->setLevel(Level::toLevel(tmp)); // Set the mainlogger priority to this prio as well. Registry::Instance().GetMainLogger().SetLoggerLevel(Level::toLevel(tmp)); try { // Choose your poison .. aem .. config file format if (global.GetConfig("logconfig", tmp)) { if (tmp.substr(tmp.length() - 4, string::npos) == ".xml") { xml::DOMConfigurator::configure(tmp); } else { PropertyConfigurator::configure(tmp); } } else { BasicConfigurator::configure(); } } catch (...) { cerr << "WARNING: Could not configure logging." << endl; } LOG4CXX_INFO(l,"Logging set up."); } #endif // fork to background if demanded if (background) daemonize(); SetupSignalHandler(); /** bootstraping the system */ ILogger mainlogger; LOGINFO(mainlogger, "Instanciating Inverter objects"); /** create the inverters via its factories. */ { string section = "inverter.inverters"; libconfig::Setting &rt = Registry::Configuration()->lookup(section); for (int i = 0; i < rt.getLength(); i++) { std::string name; std::string manufacturer; std::string model; // alias for manufacturer is manufactor -- old try { manufacturer = (const char *)rt[i]["manufacturer"]; } catch (libconfig::SettingNotFoundException &e1) { try { manufacturer = (const char *)rt[i]["manufactor"]; LOGWARN(mainlogger, "NOTE: \"manufactor\" is depreciated. " "Please update to \"manufacturer\"."); } catch (libconfig::SettingNotFoundException &e2) { LOGFATAL(mainlogger, "Configuration Error: Required Setting was not found: \"" << e1.getPath() << '\"'); cleanup(); exit(1); } } try { name = (const char *) rt[i]["name"]; model = (const char *) rt[i]["model"]; LOGINFO(mainlogger, "Setting up inverter " << name << " (" << manufacturer << ")"); } catch (libconfig::SettingNotFoundException &e) { LOGFATAL(mainlogger, "Configuration Error: Required Setting was not found in \"" << e.getPath() << '\"'); cleanup(); exit(1); } if (Registry::Instance().GetInverter(name)) { LOGFATAL(mainlogger, "Inverter " << name << " declared more than once"); cleanup(); exit(1); } IInverterFactory *factory = InverterFactoryFactory::createInverterFactory(manufacturer); if (!factory) { LOGFATAL(mainlogger, "Unknown inverter manufacturer \"" << manufacturer << '\"'); cleanup(); exit(1); } IInverterBase *inverter = factory->Factory(model, name, rt[i].getPath()); if (!inverter) { LOGFATAL(mainlogger, "Cannot create inverter model " << model << " for manufacturer \"" << manufacturer << '\"'); LOGFATAL(mainlogger, "Supported models are: " << factory->GetSupportedModels()); cleanup(); exit(1); } if (!inverter->CheckConfig()) { LOGFATAL(mainlogger, "Inverter " << name << " ( " << manufacturer << ", " << model << ") reported configuration error"); cleanup(); exit(1); } Registry::Instance().AddInverter(inverter); // destroy the (used) factory. delete factory; } } LOGINFO(mainlogger, "Instantiating data filter objects"); { IDataFilterFactory factory; /* create the data filters via its factories. */ string section = "logger.loggers"; libconfig::Setting & rt = Registry::Configuration()->lookup(section); for (int i = 0; i < rt.getLength(); i++) { std::string name; std::string previousfilter; std::string type; try { name = (const char *) rt[i]["name"]; previousfilter = (const char *) rt[i]["datasource"]; type = (const char *) rt[i]["type"]; LOGINFO(mainlogger, "Datafilter " << name << " (" << type << ") connects to " << previousfilter << " with Config-path " << rt[i].getPath()); } catch (libconfig::SettingNotFoundException &e) { LOGFATAL(mainlogger, "Configuration Error: Required Setting was not found in \"" << e.getPath() << '\"' ); cleanup(); exit(1); } // TODO Also check for duplicate DataFilters. if (Registry::Instance().GetInverter(name)) { LOGFATAL(mainlogger, "CONFIG ERROR: Inverter or Logger Nameclash: " << name << " declared more than once" ); cleanup(); exit(1); } IDataFilter *filter = factory.Factory(rt[i].getPath()); if (!filter) { LOGFATAL(mainlogger, "Couldn't create DataFilter " << name << "(" << type << ") connecting to " << previousfilter << " Config-path " << rt[i].getPath() ); cleanup(); exit(1); } if (!filter->CheckConfig()) { LOGFATAL(mainlogger, "DataFilter " << name << "(" << type << ") reported config error" << previousfilter ); cleanup(); exit(1); } Registry::Instance().AddInverter(filter); // Filter is ready. } } while (!killsignal) { if (sigusr1) { if (background ) { logreopen(true); LOGINFO(mainlogger, "Logfiles rotated"); } sigusr1= false; } Registry::GetMainScheduler()->DoWork(true); if (autoterminate) { LOGERROR(Registry::GetMainLogger(), " !!!!! AUTOTERMINATE ENABLED. !!!!! left=" << autoterminate); if (0 == --autoterminate) { LOGDEBUG(Registry::GetMainLogger(), "Raising SIGTERM"); raise(SIGTERM); } } } // Ensure that the "terminator thread" (if spawn) is finished before // handling the remaining events (or the broadcast shutdown event // might get lost) terminator_thread.join(); // Termination requested -- execute all pending events. while(Registry::GetMainScheduler()->DoWork(false)); LOGINFO(Registry::GetMainLogger(), "Terminating."); Registry::Instance().Shutdown(); cleanup(); return 0; } solarpowerlog-solarpowerlog-0.26/tools/000077500000000000000000000000001444065341000203765ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/tools/README.txt000066400000000000000000000024411444065341000220750ustar00rootroot00000000000000This folder contains some supplemental files which are not (or not mandatory) required to run solarpowerlog. solarpowerlog.init, solarpowerlog.default The file solarpowerlog.init contains the init-script file (usually copied to "/etc/init.d/solarpowerlog") to start solarpowerlog in daemon mode. It should support any LSB aware distribution, but might need modifications on other platforms. The file solarpowerlog.default (stored as "/etc/default/solarpowerlog") sourced by the init-file to load default values for the daemon behaviour. The following files target to help me to develop solarpowerlog. If you have use for them, good! (Note, they will not be released in the tarball) make-slp-dist This script is a convenience script for my use to semi-automatically test the generation of new releases and testing the debian package generation. It helps me to always follow the same procedure when I release a new version. In a few words, it automates the upstream tar-ball generation, tests the tar-ball and then tries to generate a debian package using pbuilder (to check dependencies completeness) and an additional dpkg-buildpackage using this tarball. buildbot/* This files are auxiliary files to maintain my local buildbot (http://trac.buildbot.net/) setup. There's an dedicated README in that folder. solarpowerlog-solarpowerlog-0.26/tools/buildbot/000077500000000000000000000000001444065341000222025ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/tools/buildbot/README000066400000000000000000000004231444065341000230610ustar00rootroot00000000000000This files are used to help automate things for buildbot builds. Buildbot will use those files when testing the debian package generation (autobuilding the debian branch) They are kept here soley for convenience reasons. See the individual files for comments/descriptions solarpowerlog-solarpowerlog-0.26/tools/buildbot/buildbot_dpkgbuildpackage000077500000000000000000000004721444065341000273000ustar00rootroot00000000000000#!/bin/bash # dpkg-buildpackage the debian package # Prerequesite: # builbot_make_orig_source run # Result: # hopfully a debian package... set -e VERSION=$(grep "PACKAGE_VERSION=" configure | cut -d"=" -f 2- | tr --delete "\'") ( cd stagedir_debbuild/solarpowerlog-$VERSION && dpkg-buildpackage -uc -us ) solarpowerlog-solarpowerlog-0.26/tools/buildbot/buildbot_linitian000077500000000000000000000005001444065341000256160ustar00rootroot00000000000000#!/bin/bash # dpkg-buildpackage the debian package # Prerequesite: # builbot_make_orig_source run # Result: # hopfully a debian package... set -e VERSION=$(grep "PACKAGE_VERSION=" configure | cut -d"=" -f 2- | tr --delete "\'") ( cd stagedir_debbuild && lintian -IE --pedantic solarpowerlog_$VERSION*.changes ) solarpowerlog-solarpowerlog-0.26/tools/buildbot/buildbot_make_orig_source000077500000000000000000000024641444065341000273370ustar00rootroot00000000000000#!/bin/bash # Prepare the build-dir for the debian package # Prerequesite: # make dist done by buildbot # make dist will place a cp solarpowerlog-$VERSION.tar.gz in the builddir. # Result: # stagedir_debbuild contains untarred source and orig.tar.gz, debian dir integrated # debian changelog contains buildbot information. set -e PWD=$(pwd) VERSION=$(grep "PACKAGE_VERSION=" configure | cut -d"=" -f 2- | tr --delete "\'") # copy orig.tar.gz into position rm -rf stagedir_debbuild mkdir -p stagedir_debbuild mv solarpowerlog-$VERSION.tar.gz stagedir_debbuild/solarpowerlog_$VERSION.orig.tar.gz # extract it ( cd stagedir_debbuild && tar xzf solarpowerlog_$VERSION.orig.tar.gz) # copy debian dir cp -r debian stagedir_debbuild/solarpowerlog-$VERSION # record that we are autobuilding. GIT_DESCRIBE=$(git describe) cd stagedir_debbuild/solarpowerlog-$VERSION DEB_VERSION=$(head -n1 debian/changelog | cut -d\( -f 2 | cut -d\) -f -1 | rev | cut -d- -f 2- | rev) [[ "x$DEB_VERSION" == "x" ]] && ( echo "cannot determine existing debian version"; exit 1) echo "DEB_VERSION = $DEB_VERSION" if [[ "$DEB_VERSION" != "$VERSION" ]] then dch -M --newversion $VERSION-1 "New upstream version $VERSION" dch -M -a "upstream git reference: $GIT_DESCRIBE" else dch -M -a "upstream git reference: $GIT_DESCRIBE" fi cd $PWD solarpowerlog-solarpowerlog-0.26/tools/buildbot/buildbot_pdebuilder000077500000000000000000000014431444065341000261350ustar00rootroot00000000000000#!/bin/bash # buildpackge using pbuilder. # Prerequesite: # builbot_make_orig_source run # pbuilder setup. # you need to setup sudo to allow the buildbot user to running pdebuild. # you also need to allow sudo pbuilder update # Result: # hopfully a debian package... set -e VERSION=$(grep "PACKAGE_VERSION=" configure | cut -d"=" -f 2- | tr --delete "\'") # check if we can skip pbuilder update -- we do it only once after an apt-get update # for this we compare the time against the last modification of /var/lib/apt/lists LAST_UPDATE=$(stat -c %Y /var/cache/pbuilder/base.tgz) LISTS_TIMESTAMP=$(stat -c %Y /var/lib/apt/lists) if [[ $LAST_UPDATE -lt $LISTS_TIMESTAMP ]] then sudo /usr/sbin/pbuilder update fi ( cd stagedir_debbuild/solarpowerlog-$VERSION && pdebuild --debbuildopts "-j2" ) solarpowerlog-solarpowerlog-0.26/tools/buildbot/configuration/000077500000000000000000000000001444065341000250515ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/tools/buildbot/configuration/README000066400000000000000000000002521444065341000257300ustar00rootroot00000000000000This file contains the buildbot configuration I use to run my copy, if you are interested in setup your own buildbot for solarpowerlog. (Password if of course removed.) solarpowerlog-solarpowerlog-0.26/tools/buildbot/configuration/master/000077500000000000000000000000001444065341000263445ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/tools/buildbot/configuration/master/master.cfg000066400000000000000000000201201444065341000303130ustar00rootroot00000000000000# -*- python -*- # ex: set syntax=python: # This is a sample buildmaster config file. It must be installed as # 'master.cfg' in your buildmaster's base directory. # This is the dictionary that the buildmaster pays attention to. We also use # a shorter alias to save typing. c = BuildmasterConfig = {} ##### Locks from buildbot import locks build_lock = locks.SlaveLock("solarpowerlog_lock",maxCount=1) ####### BUILDSLAVES # The 'slaves' list defines the set of recognized buildslaves. Each element is # a BuildSlave object, specifying a unique slave name and password. The same # slave name and password must be configured on the slave. from buildbot.buildslave import BuildSlave c['slaves'] = [BuildSlave("solarpowerlog", "PASSWORD!",max_builds=2)] # 'slavePortnum' defines the TCP port to listen on for connections from slaves. # This must match the value configured into the buildslaves (with their # --master option) c['slavePortnum'] = 9989 ####### CHANGESOURCES # the 'change_source' setting tells the buildmaster how it should find out # about source code changes. Here we point to the buildbot clone of pyflakes. from buildbot.changes.gitpoller import GitPoller c['change_source'] = [] c['change_source'].append(GitPoller( 'git://solarpowerlog.git.sourceforge.net/gitroot/solarpowerlog/solarpowerlog', workdir='gitpoller-slp-workdir', branch='trunk', project='solarpowerlog', pollinterval=7200)) c['change_source'].append(GitPoller( 'git://solarpowerlog.git.sourceforge.net/gitroot/solarpowerlog/solarpowerlog', workdir='gitpoller-slp-workdir-deb', branch='debian', project='solarpowerlog-deb', pollinterval=7200)) ####### SCHEDULERS # Configure the Schedulers, which decide how to react to incoming changes. In this # case, just kick off a 'runtests' build # filter to find configure / autotools files --- triggers a complete build. def has_autoconf_files(change): for name in change.files: if name.endswith(".am"): return True elif name.endswith(".in"): return True elif name.endswith("bootstrap.sh"): return True return False from buildbot.schedulers.basic import SingleBranchScheduler from buildbot.changes import filter c['schedulers'] = [] c['schedulers'].append(SingleBranchScheduler( name="all", change_filter=filter.ChangeFilter( branch='trunk', project='solarpowerlog'), treeStableTimer=600, builderNames=["build"])) c['schedulers'].append(SingleBranchScheduler( name="autotools", change_filter=filter.ChangeFilter( branch='trunk', project='solarpowerlog'), fileIsImportant=has_autoconf_files, treeStableTimer=120, onlyImportant=True, builderNames=["build-autotools"])) c['schedulers'].append(SingleBranchScheduler( name="slp-deb", change_filter=filter.ChangeFilter( branch='debian', project='solarpowerlog-deb'), treeStableTimer=120, onlyImportant=True, builderNames=["build-deb"])) ####### BUILDERS # The 'builders' list defines the Builders, which tell Buildbot how to perform a build: # what steps, and which slaves can execute them. Note that any particular build will # only take place on one slave. from buildbot.process.factory import BuildFactory from buildbot.steps.source import Git from buildbot.steps.shell import ShellCommand from buildbot.steps.shell import Configure from buildbot.steps.shell import Compile factory = BuildFactory() # check out the source factory.addStep(Git( repourl='git://solarpowerlog.git.sourceforge.net/gitroot/solarpowerlog/solarpowerlog', branch='trunk', mode='update')) factory.addStep(Compile( timeout=3600 )) from buildbot.config import BuilderConfig factory2 = BuildFactory() # check out the source factory2.addStep(Git( repourl='git://solarpowerlog.git.sourceforge.net/gitroot/solarpowerlog/solarpowerlog', branch='trunk', mode='copy')) factory2.addStep(ShellCommand( command=["./bootstrap.sh", ""], timeout=3600, description="Bootstrapping autoconf", descriptionDone ="Bootstrapping done")) factory2.addStep(Configure( timeout=3600 )) factory2.addStep(Compile( timeout=3600 )) factory_deb = BuildFactory() # check out the source -- debian trunk factory_deb.addStep(Git( repourl='git://solarpowerlog.git.sourceforge.net/gitroot/solarpowerlog/solarpowerlog', branch='debian', mode='copy')) # bootstrap and make dist factory_deb.addStep(ShellCommand( command=["./bootstrap.sh", ""], timeout=3600, description="Bootstrapping autoconf", descriptionDone ="Bootstrapping done")) factory_deb.addStep(Configure( timeout=3600 )) factory_deb.addStep(ShellCommand( command=["make", "dist"], timeout=3600, description="make dist: Making tar", descriptionDone ="make dist: done")) factory_deb.addStep(ShellCommand( command=["make", "distcheck"], timeout=3600, description="Make distcheck: Checking release tar", descriptionDone ="Make distcheck done")) # preparing debian stagedir factory_deb.addStep(ShellCommand( command=["bash","-x", "tools/buildbot/buildbot_make_orig_source",""], description="Preparing stagedir")) # dpkg-buildpackage factory_deb.addStep(ShellCommand( command=["bash" ,"-x", "tools/buildbot/buildbot_dpkgbuildpackage"], timeout=3600, description="dpkg-buildpackage", descriptionDone ="dpkg-buildpackage done")) # linitian factory_deb.addStep(ShellCommand( command=["bash" ,"-x", "tools/buildbot/buildbot_linitian"], description="lintian check")) # now build the package in pdebuilder factory_deb.addStep(ShellCommand( command=["bash","-x","tools/buildbot/buildbot_pdebuilder"], timeout=3600, description="pdebuilder build")) c['builders'] = [] c['builders'].append( BuilderConfig(name="build", slavenames=["solarpowerlog"], locks= [build_lock.access('exclusive')], slavebuilddir='build/solarpowerlog', factory=factory)) c['builders'].append( BuilderConfig(name="build-autotools", slavenames=["solarpowerlog"], locks= [build_lock.access('exclusive')], slavebuilddir='build/solarpowerlog', factory=factory2)) c['builders'].append( BuilderConfig(name="build-deb", slavenames=["solarpowerlog"], locks= [build_lock.access('exclusive')], slavebuilddir='build/solarpowerlog-deb', factory=factory_deb)) ####### STATUS TARGETS # 'status' is a list of Status Targets. The results of each build will be # pushed to these targets. buildbot/status/*.py has a variety to choose from, # including web pages, email senders, and IRC bots. c['status'] = [] from buildbot.status import html from buildbot.status.web import authz authz_cfg=authz.Authz( # change any of these to True to enable; see the manual for more # options gracefulShutdown = False, forceBuild = True, # use this to test your slave once it is set up forceAllBuilds = False, pingBuilder = False, stopBuild = False, stopAllBuilds = False, cancelPendingBuild = False, ) c['status'].append(html.WebStatus(http_port=8010, authz=authz_cfg)) ####### PROJECT IDENTITY # the 'title' string will appear at the top of this buildbot # installation's html.WebStatus home page (linked to the # 'titleURL') and is embedded in the title of the waterfall HTML page. c['title'] = "solarpowerlog" c['titleURL'] = "http://sourceforge.net/projects/solarpowerlog/" # the 'buildbotURL' string should point to the location where the buildbot's # internal web server (usually the html.WebStatus page) is visible. This # typically uses the port number set in the Waterfall 'status' entry, but # with an externally-visible host name which the buildbot cannot figure out # without some help. c['buildbotURL'] = "http://localhost:8010/" ####### DB URL # This specifies what database buildbot uses to store change and scheduler # state. You can leave this at its default for all but the largest # installations. c['db_url'] = "sqlite:///state.sqlite" solarpowerlog-solarpowerlog-0.26/tools/buildbot/configuration/slave/000077500000000000000000000000001444065341000261635ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/tools/buildbot/configuration/slave/buildbot.tac000066400000000000000000000027651444065341000304720ustar00rootroot00000000000000 import os from twisted.application import service from buildslave.bot import BuildSlave basedir = r'.' rotateLength = 10000000 maxRotatedFiles = 10 # if this is a relocatable tac file, get the directory containing the TAC if basedir == '.': import os.path basedir = os.path.abspath(os.path.dirname(__file__)) # note: this line is matched against to check that this is a buildslave # directory; do not edit it. application = service.Application('buildslave') try: from twisted.python.logfile import LogFile from twisted.python.log import ILogObserver, FileLogObserver logfile = LogFile.fromFullPath(os.path.join(basedir, "twistd.log"), rotateLength=rotateLength, maxRotatedFiles=maxRotatedFiles) application.setComponent(ILogObserver, FileLogObserver(logfile).emit) except ImportError: # probably not yet twisted 8.2.0 and beyond, can't set log yet pass buildmaster_host = 'localhost' port = 9989 slavename = 'solarpowerlog' passwd = 'PASSWORD!' keepalive = 600 usepty = 0 umask = None maxdelay = 300 s = BuildSlave(buildmaster_host, port, slavename, passwd, basedir, keepalive, usepty, umask=umask, maxdelay=maxdelay) s.setServiceParent(application) # ccache -- uncomment to enable cc-cache and set CCCACHE_DIR to a good location! #import os #PATH = os.environ['PATH'] #PATH = "/usr/lib/ccache:" + PATH #CXX = "ccache g++" #CC = "ccache gcc" #CCACHE_DIR = "../ccache" #os.environ = { 'PATH': PATH, 'CXX': CXX, 'CC': CC, 'CCACHE_DIR': CCACHE_DIR } solarpowerlog-solarpowerlog-0.26/tools/eclipse_codestyle.xml000066400000000000000000000421521444065341000246230ustar00rootroot00000000000000 solarpowerlog-solarpowerlog-0.26/tools/make-slp-dist000077500000000000000000000067021444065341000230030ustar00rootroot00000000000000#!/bin/bash # script to automate the process to uilds solarpowerlog release tars and debian packages. # this ensures that this process is always the same and it also makes some saftety-checks # on the package. # This script needs to be excuted in the root directory of solarpowerlog sources # It also needs the git repository present. # note, this relies on e.g that the git repo is present # and it will always build the currently selected branch! set -e # checks.... [[ ! -d .git ]] && ( echo ".git not present. Are you cd'ed to the right directory and have the repository present?"; exit 1) [[ ! -e src/solarpowerlog.cpp ]] && (echo "Are you sure that you are in the solarpowerlog directory?"; exit 1) # store current dir SLP_ROOTDIR=$(pwd) CURRENT_BRANCH=$(git symbolic-ref -q HEAD | cut -d/ -f 3-) [[ "x$CURRENT_BRANCH" == "x" ]] && (echo "Cannot determin git branch to preserve branch." ; exit 1) echo "######## Stage 1: generate release tar (make dist, make distcheck.)" # As side, generates the changelog from the git repository and we automatically retrieve the version from the configure script. [[ -e debian/rules ]] && (fakeroot make -f debian/rules clean || /bin/true) [ ! -e Makefile ] && ./bootstrap.sh ; ./configure --cache-file=/tmp/cc; VERSION=$(grep "PACKAGE_VERSION=" configure | cut -d"=" -f 2- | tr --delete "\'") if [[ "x$VERSION" == "x" ]] then echo "Retrieving version failed" exit 1 fi echo "######## Building Version $VERSION" make dist -j3 make distcheck -j3 echo "######## Stage #2: prepare staging directory for debian package build" echo "######## 2a) prepare orig.tar" [[ ! -e ../stagedir_debbuild ]] && mkdir ../stagedir_debbuild # this archive could be uploaded to sf.net: cp solarpowerlog-$VERSION.tar.gz ../stagedir_debbuild/solarpowerlog-$VERSION.tar.gz # this is needed for the debian build mv solarpowerlog-$VERSION.tar.gz ../stagedir_debbuild/solarpowerlog_$VERSION.orig.tar.gz git status >>../stagedir_debbuild/build-time-branch.txt echo "######## 2b) get the debian directory ready" # merge the debian directory, but be sure to switch to that branch before... # note that this will fail if you have uncommited changes and you were not on the debian branch before. cd ../stagedir_debbuild/ rm -rf solarpowerlog-$VERSION tar xzf solarpowerlog_$VERSION.orig.tar.gz # integrate debian dir cd $SLP_ROOTDIR GIT_DESCRIBE=$(git describe) git checkout debian cp -r debian ../stagedir_debbuild/solarpowerlog-$VERSION # returb ti previous branch git checkout $CURRENT_BRANCH echo "######## 2c) check if we need to tweak the debian changelog" cd ../stagedir_debbuild/solarpowerlog-$VERSION # retrieve debian version, but stip of the upload path (the last -x) DEB_VERSION=$(head -n1 debian/changelog | cut -d\( -f 2 | cut -d\) -f -1 | rev | cut -d- -f 2- | rev) [[ "x$DEB_VERSION" == "x" ]] && ( echo "cannot determine existing debian version"; exit 1) echo "DEB_VERSION = $DEB_VERSION" if [[ "$DEB_VERSION" != "$VERSION" ]] then dch --newversion $VERSION-1 "New upstream version $VERSION (automated build with make-slp-disp)" dch -a "upstream git reference: $GIT_DESCRIBE" else dch -a "upstream git reference: $GIT_DESCRIBE" fi echo "######## Stage #3 Check packaging using pdebuilder (ensuring correct dependencies)" pdebuild --debbuildopts "-I -i -j3" echo "######## Stage #4 dpkg-buildpackage " dpkg-buildpackage echo "######## Stage #5 Linitian check of generated package" lintian -I -E --pedantic ../solarpowerlog_$VERSION-*_i386.changes cd $SLP_ROOTDIR solarpowerlog-solarpowerlog-0.26/tools/solarmaxwatcher/000077500000000000000000000000001444065341000236025ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/tools/solarmaxwatcher/README000066400000000000000000000046021444065341000244640ustar00rootroot00000000000000########################################################################### ### ### ### S O L A R M A X W A T C H E R ### ### graphical display for Solarmax inverters ### ### ### ########################################################################### NOTICE This is a php example that works with the DB Writer. Thanks for the solarmax watcher folks for programming it (it was not programmed for solarpowerlog, but it works with it.) (Note that the code is quite hacky and would need some refactoring; please provide patches.... I did not do any checks for security problems, so I do not recommend to serve that to the Internet without doing so) For solarpowerlog, I removed the unrelated parts not needed for the PHP part. The tar file on which this work based was versioned 0.4.0, (2012-01-10) Some changes were necessary to adapt it to solarpowerlog, as e.g there is are different approach to datatypes: solaepowerlog uses real data scaling, while the solarmaxwatcher used unscaled intergers for everything. See also db_scheme.txt for the MYSQL Database scheme used. Configuration: The database and password needs to be set in (look for mysql_connect for the locations) - analyzer.php (2 locations!) - solarertrag.php - There are a few references to the table names in the code. If you use something other than Inverter_$i where i is a number, you need to patch it. - Many configuratons options are hardcoded in the individual files. (Sorry for the lack of more documentation) ------------------------ Original Author and license --------------------------- A simple solarmax visualizer php program written by zagibu in July 2010 This program was originally licensed under WTFPL 2 http://sam.zoy.org/wtfpl/ Improvements and enhancements by Frank Lassowski in August/September 2010 Further improvements by sleepprogger in January 2012 This program is now relicensed under GPLv2 or later http://www.gnu.org/licenses/gpl2.html Contributors ------------ zagibu zagibu@gmx.ch Frank Lassowski flassowski@gmx.de Stephan Collet stephan@collet-online.de Rene Essink supergudrun@web.de Thomas Kattenbeck kattenbeck@gmx.de sleepprogger wwrStuff@gmx.de solarpowerlog-solarpowerlog-0.26/tools/solarmaxwatcher/db_scheme.txt000066400000000000000000000020261444065341000262540ustar00rootroot00000000000000Database scheme for visualization and solarpowerlog capability mapping the types are the one used by solarpowerlog originally the visualization uses int(11) for everything. created timestamp (PRIMARY KEY) kdy float "Energy produced today (kWh)" kmt float "Energy produced this month (kWh)" kyr float "Energy produced this year (kWh)" kt0 float "Energy produced accumulated all time (kWh)" tnf float "Net frequency (Hz)" tkk float "Inverter Temperature (C)" pac float "Current Grid Feeding Power" prl float "relative Power (%)" il1 float "AC grid feeding current (A)" idc float "DC current in (A)" ul1 float "AC grid voltage (V)" udc float "DC voltage in (V)" sys (no easy mapping which will work over different brands) -- above retrieved from the db-creation-script on Nov 8 2014 note: scaling -- as solarmaxwatcher uses int for everything, the data must be scaled on the php side. I hope I have patched the php scripts accordingly .. if not, please file bugs (add patches ;-)) coldtobi solarpowerlog-solarpowerlog-0.26/tools/solarmaxwatcher/web/000077500000000000000000000000001444065341000243575ustar00rootroot00000000000000solarpowerlog-solarpowerlog-0.26/tools/solarmaxwatcher/web/analyzer.php000066400000000000000000000064311444065341000267210ustar00rootroot00000000000000 solarpowerlog-solarpowerlog-0.26/tools/solarmaxwatcher/web/colors.php000066400000000000000000000005761444065341000264010ustar00rootroot00000000000000 solarpowerlog-solarpowerlog-0.26/tools/solarmaxwatcher/web/drawday.php000066400000000000000000000166351444065341000265360ustar00rootroot00000000000000'; } // Initialize pac image // Sun rise and fall during longest day at your place, must be integer $rise = 5; $fall = 22; // Pixel per hour should be equal to log entries per hour (I log every minute) $px_per_hour = 39; // Width = hours * px per hour + gap $width = ($fall - $rise) * $px_per_hour +157; $maxpac = 5200; $maxkdy = 25; $maxtkk = 60; $lasttkk = 0; $maxudc = 400; $lastudc = 0; // How many W per diagram line $step_w = 500; $step = floor ( $maxpac / $step_w); $step_kdy = $maxkdy / $step; $step_tkk = $maxtkk / $step; $step_udc = $maxudc / $step; // How many px per diagram line (W/px = $step_w / $vert_px) $vert_px = 30; // Height = number of lines * px per line + px per line (for 0-line) + gap $gap = 50; $height = $maxpac / $step_w * $vert_px + $gap + 10; // Create image, prepare colors and set background to white $image = imagecreatetruecolor($width, $height); // get the colors include 'colors.php'; imagefill($image, 0, 0, $white); if (preg_match('/gridday/', $show_text)) { // Draw horizontal lines with some space above and below for ($i = 0; $i <= $step; $i++) { // Create horizontal grid line $ypos = $height - $i * $vert_px - $gap; imageline($image, 12, $ypos, $width - 110, $ypos, $gray); // Draw the needed scales at the end of the horizontal line $pac = $i * $step_w; $kdy = $i * $step_kdy; $tkk = $i * $step_tkk; $udc = $i * $step_udc; if (preg_match('/yield/', $show_text)) imagefttext($image, 7, 0, $width - 109, $ypos + 4, $black, $fontfile, floor ($pac)); if (preg_match('/accu/', $show_text) | preg_match('/predday/', $show_text)) imagefttext($image, 7, 0, $width - 74, $ypos + 4, $blue, $fontfile, floor ($kdy)); if (preg_match('/temp/', $show_text)) imagefttext($image, 7, 0, $width - 47, $ypos + 4, $black, $fontfile, floor ($tkk)); if (preg_match('/volt/', $show_text)) imagefttext($image, 7, 0, $width - 25, $ypos + 4, $red, $fontfile, floor ($udc)); } // Draw vertical lines with some space at the left and right for ($i = 0; $i <= $fall - $rise; $i++) { // Create vertical grid line $xpos = $i * $px_per_hour + 25; imageline($image, $xpos, 5, $xpos, $height - $gap + 6, $gray); // Draw the hour value at the end of the vertical line $hour = ($i + $rise) % 24 . ':00'; imagefttext($image, 8, 0, $xpos - 10, $height - $gap + 18, $black, $fontfile, $hour); } } //explain colored lines if (preg_match('/yield/', $show_text) & preg_match('/gridday/', $show_text)) imagefttext($image, 7, 0, $width - 107, 10, $black, $fontfile, "(W)"); if (preg_match('/accu/', $show_text) | preg_match('/predday/', $show_text)) { imageline($image, $width - ($width * 4 / 4) + 10, $height - 15, $width - ($width * 4 / 4) + 25, $height - 15, $blue); imagefttext($image, 7, 0, $width - ($width * 4 / 4) + 40, $height - 12, $black, $fontfile, $GLOBALS["graphday2".$GLOBALS['lang']]); if (preg_match('/gridday/', $show_text)) imagefttext($image, 6, 0, $width - 81, 10, $blue, $fontfile, "(kWh)"); } if (preg_match('/temp/', $show_text)) { imageline($image, $width - ($width * 2 / 4) + 0, $height - 15, $width - ($width * 2 / 4) + 15, $height - 15, $black); imagefttext($image, 7, 0, $width - ($width * 2 / 4) + 30, $height - 12, $black, $fontfile, $GLOBALS["graphday3".$GLOBALS['lang']]); if (preg_match('/gridday/', $show_text)) imagefttext($image, 6, 0, $width - 50, 10, $black, $fontfile, "(°C)"); } if (preg_match('/volt/', $show_text)) { imageline($image, $width - ($width * 1 / 4) - 30, $height - 15, $width - ($width * 1 / 4) - 15, $height - 15, $red); imagefttext($image, 7, 0, $width - ($width * 1 / 4) + 0, $height - 12, $black, $fontfile, $GLOBALS["graphday4".$GLOBALS['lang']]); if (preg_match('/gridday/', $show_text)) imagefttext($image, 7, 0, $width - 25, 10, $red, $fontfile, "(V)"); } // Draw pac values $lastxpos=0; if (preg_match('/yield/', $show_text)) { while($row = mysql_fetch_assoc($result1)) { // Determine x position $xpos = floor (($row['hour'] - $rise) * $px_per_hour + $row['minute'] / 60 * $px_per_hour + 25); if ($xpos > $lastxpos) { // Calculate y position with logged pac $pac = $row['pac'] / $step_w * $vert_px; // Draw pac line imageline($image, $xpos, $height - $gap, $xpos, $height - $gap - $pac, $green); } $lastxpos = $xpos; } } // Draw prediction line if (preg_match('/predday/', $show_text)) { $pred = $pred_day / $step_kdy * $vert_px; imageline($image, 12, $height - $pred - $gap, $width - 122, $height - $pred - $gap, $blue); } // Draw other logged values: kdy, tkk, udc while($row = mysql_fetch_assoc($result2)) { // Determine x position $xpos = ($row['hour'] - $rise) * $px_per_hour + $row['minute'] / 60 * $px_per_hour + 25; $lastxpos = $xpos; // Logged kdy is ten times as high as effective if (preg_match('/accu/', $show_text)) { $kdy = $row['kdy'] / $step_kdy * $vert_px; // Draw kdy dot imagesetpixel($image, $xpos, $height - $gap - $kdy, $blue); } // draw tkk as a stair-line if (preg_match('/temp/', $show_text)) { $tkk = $row['tkk'] / $step_tkk * $vert_px; if ($lasttkk == 0) { imagesetpixel($image, $xpos, $height - $gap - $tkk, $black); } else { imageline($image, $lastxpos, $height - $gap - $lasttkk, $xpos, $height - $gap - $tkk, $black); } $lasttkk = $tkk; } if (preg_match('/volt/', $show_text)) { $udc = $row['udc'] / $step_udc * $vert_px; if ($lastudc == 0) { imagesetpixel($image, $xpos, $height - $gap - $udc, $red); } else { imageline($image, $lastxpos, $height - $gap - $lastudc, $xpos, $height - $gap - $udc, $red); } $lastudc = $udc; } } imagepng($image, $image_name); imagedestroy($image); return '

' . $GLOBALS["graphday1".$GLOBALS['lang']] . '

'; } ?> solarpowerlog-solarpowerlog-0.26/tools/solarmaxwatcher/web/drawmonth.php000066400000000000000000000125051444065341000270760ustar00rootroot00000000000000'; } // include monthly predictions include 'solarertrag_month_predictions.php'; $pred_month = ${'m_'.date('m', mktime(0, 0, 0, $row['month'], 1, 0))}; // Initialize kdy image $sum=0; // Pixel per day determines bar width $px_per_day = 25; // Width = days * px per day + gap $width = $date['mday'] * $px_per_day + 45; $maxkwh = 25; // How many kWh per diagram line $step_w = 2; // How many px per diagram line (W/px = $step_w / $vert_px) $vert_px = 30; // Height = number of lines * px per line + px per line (for 0-line) + gap $gap = 72; $height = $maxkwh / $step_w * $vert_px + $gap; // Create image, prepare colors and set background to white $image = imagecreatetruecolor($width, $height); // get the colors include 'colors.php'; imagefill($image, 0, 0, $white); if (preg_match('/gridmonth/', $show_text)) { // Draw horizontal lines with some space above and below for ($i = 0; $i <= $maxkwh / $step_w; $i++) { // Create horizontal grid line $ypos = $height - $i * $vert_px - $gap + 22; imageline($image, 12, $ypos, $width - 35, $ypos, $gray); // Draw the kWh value at the end of the horizontal line imagefttext($image, 8, 0, $width - 30, $ypos + 4, $black, $fontfile, $i * $step_w); } // Draw vertical lines with some space at the left and right for ($i = 1; $i <= $date['mday']; $i++) { // Create vertical grid line $xpos = $i * $px_per_day; imageline($image, $xpos, 5, $xpos, $height - $gap + 27, $gray); // Draw the hour value at the end of the vertical line imagefttext($image, 8, 0, $xpos - 4, $height - $gap + 40, $black, $fontfile, $i); } } // Draw kdy values while($row = mysql_fetch_assoc($result)) { // Determine x position $xpos = $row['day'] * $px_per_day - $px_per_day / 2; $lastxpos = $xpos; // Transform kWh to pixel height $kwh = $row['kdy'] / $step_w * $vert_px; $days++; $sum = $sum + $row['kdy'] ; // Draw acumulated yield $factor = $height/50*$pred_month; $totalkwh = $totalkwh + kwh; if ($lasttotalkwh == 0) { imagesetpixel($image, $xpos, $height - $gap + 22 - $totalkwh, $black); } else { imageline($image, $lastxpos - $px_per_day/2, ($height - $gap + 22 - $lasttotalkwh), $xpos + $px_per_day/2, ($height - $gap + 22 - $totalkwh), $black); } $lasttotalkwh = $totalkwh; // Draw kWh bar imagefilledrectangle($image, $xpos + 2, $height - $gap + 22, $xpos + $px_per_day - 2, $height - $gap - $kwh + 22, $green); if (preg_match('/numbersmonth/', $show_text)) { imagefttext($image, 12, 90, $xpos + 19, $height - $gap - $kwh + 17, $black, $fontfile, $row['kdy']); } } // Draw prediction line if (preg_match('/predmonth/', $show_text)) { $pred = $pred_day / $step_w * $vert_px; imageline($image, 12, $height - $pred - $gap + 22, $width - 35, $height - $pred - $gap + 22, $blue); imageline($image, $width - ($width * 3 / 4) -10, $height - 15, $width - ($width * 3 / 4) + 5, $height - 15, $blue); imagefttext($image, 7, 0, $width - ($width * 3 / 4) + 20, $height - 12, $black, $fontfile, $GLOBALS["graphmonth2".$GLOBALS['lang']]); } // Draw average line if (preg_match('/avg/', $show_text)) { $avg =$sum / $days / $step_w * $vert_px; imageline($image, 12, $height - $avg - $gap + 22, $width - 35, $height - $avg - $gap + 22, $yellow); imageline($image, $width - ($width * 2 / 4) - 5, $height - 15, $width - ($width * 2 / 4) +10, $height - 15, $yellow); imagefttext($image, 7, 0, $width - ($width * 2 / 4) +25, $height - 12, $black, $fontfile, $GLOBALS["graphmonth3".$GLOBALS['lang']]); } imagepng($image, $image_name); imagedestroy($image); return '

' . $GLOBALS["graphmonth1".$GLOBALS['lang']] . '

'; } ?> solarpowerlog-solarpowerlog-0.26/tools/solarmaxwatcher/web/drawyear.php000066400000000000000000000111261444065341000267070ustar00rootroot00000000000000'; } // include monthly predictions include 'solarertrag_month_predictions.php'; // Initialize kdy image // Pixel per month determines bar width $px_per_month = 64; // Width = months * px per month + gap $width = $date['mon'] * $px_per_month + 52; $maxkwh = 600; // How many kWh per diagram line $step_w = 50; // How many px per diagram line (W/px = $step_w / $vert_px) $vert_px = 26; // Height = number of lines * px per line + px per line (for 0-line) + gap $gap = 60; $height = $maxkwh / $step_w * $vert_px + $gap; // Create image, prepare colors and set background to white $image = imagecreatetruecolor($width, $height); // get the colors include 'colors.php'; imagefill($image, 0, 0, $white); if (preg_match('/gridyear/', $show_text)) { // Draw horizontal lines with some space above and below for ($i = 0; $i <= $maxkwh / $step_w; $i++) { // Create horizontal grid line $ypos = $height - $i * $vert_px - $gap + 10; imageline($image, 12, $ypos, $width - 35, $ypos, $gray); // Draw the kWh value at the end of the horizontal line imagefttext($image, 8, 0, $width - 30, $ypos + 4, $black, $fontfile, $i * $step_w); } // Draw vertical lines with some space at the left and right for ($i = 1; $i <= $date['mon']; $i++) { // Create vertical grid line $xpos = $i * $px_per_month; imageline($image, $xpos, 5, $xpos, $height - $gap + 15, $gray); // Draw the hour value at the end of the vertical line imagefttext($image, 8, 0, $xpos - 4, $height - $gap + 28, $black, $fontfile, $i); } } // Draw kdy values while($row = mysql_fetch_assoc($result)) { // Determine x position $xpos = $row['month'] * $px_per_month - $px_per_month / 2; // Transform kWh to pixel height $kwh = $row['kmt'] / $step_w * $vert_px; // Draw kWh bar and prediction line $pred_month = ${'m_'.date('m', mktime(0, 0, 0, $row['month'], 1, 0))}; $pred = $pred_month / $step_w * $vert_px; imagefilledrectangle($image, $xpos + 2, $height - $gap + 10, $xpos + $px_per_month - 2, $height - $gap - $kwh + 10, $green); imageline($image, $xpos, $height - $pred - $gap + 10, $xpos + $px_per_month, $height - $pred - $gap + 10, $blue); if (preg_match('/numbersyear/', $show_text)) { imagefttext($image, 12, 90, $xpos + 31, $height - $gap - $kwh + 4, $black, $fontfile, $row['kmt']); imagefttext($image, 7, 0, $xpos + 45, $height - $gap - $pred + 8, $blue, $fontfile, $pred_month); if (preg_match('/percent/', $show_text)) { imagefttext($image, 6 , 90, $xpos + 42, $height - $gap - $kwh + 10, $black, $fontfile, "(".round($row['kmt'] / $pred_month,3) *100 ." %)"); } } } //explain colored lines imageline($image, $width - ($width * 2 / 4) - 55, $height - 15, $width - ($width * 2 / 4) - 70, $height - 15, $blue); imagefttext($image, 7, 0, $width - ($width * 2 / 4) - 40, $height - 12, $black, $fontfile, $GLOBALS["graphyear2".$GLOBALS['lang']]); imagepng($image, $image_name); imagedestroy($image); return '

' . $GLOBALS["graphyear1".$GLOBALS['lang']] . '

'; } ?>solarpowerlog-solarpowerlog-0.26/tools/solarmaxwatcher/web/lang.php000066400000000000000000000255371444065341000260250ustar00rootroot000000000000002 -Einsparung dieses Jahr:"; $text6de = "Jahresertrag:"; $text7de = "CO2 -Einsparung insgesamt:"; $text8de = "Gesamtertrag:"; $text9de = "Betriebsstatus:"; $text10de = "Gesamtvergütung:"; $text11de = "Ansicht:"; $text12de = "Tag"; $text13de = "Monat"; $text14de = "Jahr"; $text15de = "heute"; $text1en = "current inverter output:"; $text2en = "yield today:"; $text3en = "current inverter temperature:"; $text4en = "monthly yield:"; $text5en = "annual CO2 savings:"; $text6en = "annual yield:"; $text7en = "total CO2 savings:"; $text8en = "total yield:"; $text9en = "operating status:"; $text10en = "total remuneration:"; $text11en = "view:"; $text12en = "day"; $text13en = "month"; $text14en = "year"; $text15en = "today"; $text1nl = "Actuele vermogen van de generator:"; $text2nl = "Dagproductie van de generator:"; $text3nl = "Actuele temperatuur van de omvormer:"; $text4nl = "Maandproductie van de generator:"; $text5nl = "CO2 -bezuiniging loopende jaar:"; $text6nl = "Jaarproductie van de generator:"; $text7nl = "CO2 -bezuiniging totaal:"; $text8nl = "Totale productie van de generator:"; $text9nl = "Actuele omvormer status:"; $text10nl = "Totaal opbrengst:"; $text11nl = "Uitzicht:"; $text12nl = "Dag"; $text13nl = "Maand"; $text14nl = "Jaar"; $text15nl = "vandaag"; $text1fr = "puissance injectée actuelle:"; $text2fr = "production aujourd'hui:"; $text3fr = "température actuelle:"; $text4fr = "production du mois:"; $text5fr = "annual CO2 savings:"; $text6fr = "production de l'année:"; $text7fr = "total CO2 savings:"; $text8fr = "production totale:"; $text9fr = "état d'exploitation:"; $text10fr = "rémunération totale:"; $text11fr = "vue:"; $text12fr = "jour"; $text13fr = "mois"; $text14fr = "année"; $text15fr = "aujourd'hui"; $text1es = "rendimiento de alimentación actual:"; $text2es = "rendimiento de hoy:"; $text3es = "temperatura actual:"; $text4es = "rendimiento del mes:"; $text5es = "ahorro anual de CO2:"; $text6es = "rendimiento anual:"; $text7es = "ahorro total de CO2:"; $text8es = "rendimiento total:"; $text9es = "estado de operación:"; $text10es = "remuneración total:"; $text11es = "vista:"; $text12es = "día"; $text13es = "mes"; $text14es = "año"; $text15es = "hoy"; $text1it = "produzione dell’invertitore di corrente:"; $text2it = "produzione odierna:"; $text3it = "temperatura dell’invertitore di corrente:"; $text4it = "produzione mensile:"; $text5it = "risparmio annuale di CO2:"; $text6it = "produzione annuale:"; $text7it = "risparmio totale di CO2:"; $text8it = "produzione totale:"; $text9it = "stato operativo:"; $text10it = "compenso totale:"; $text11it = "prospetto:"; $text12it = "giorno"; $text13it = "mese"; $text14it = "anno"; $text15it = "oggi"; /* strings for the graphs */ $graphday1de = "Leistung in Watt im Verlauf des Tages, Tagesertrag, Temperatur WR, DC-Spannung"; $graphday2de = "gerade Linie: erwarteter Tagesertrag Kurve: Tagesertrag"; $graphday3de = "Temperatur WR"; $graphday4de = "Generatorspannung "; $graphmonth1de = "Ertrag in kWh pro Tag"; $graphmonth2de = "erwarteter Tagesertrag"; $graphmonth3de = "durchschnittlicher Tagesertrag"; $graphyear1de = "Ertrag in kWh pro Monat"; $graphyear2de = "erwarteter Monatsertrag"; $switch_arrayde = array("Ertrag \n", "akkumulierter Ertrag \n", "Vorhersage \n", "Spannung \n", "Temperatur \n", "Gitter

\n\n", "Zahlen \n", "Vorhersage \n", "Durchschnitt \n", "Gitter

\n\n", "Zahlen \n", "Prozent \n", "Gitter

\n\n"); $graphday1en = "power in Watts, todays yield, inverter temperature, generator voltage"; $graphday2en = "straight line: expected daily yield curve: yield today"; $graphday3en = "inverter temperature"; $graphday4en = "generator voltage"; $graphmonth1en = "yield in kWh per day"; $graphmonth2en = "expected daily yield"; $graphmonth3en = "average daily yield"; $graphyear1en = "yield in kWh per month"; $graphyear2en = "expected monthly yield"; $switch_arrayen = array("yield \n", "accumulated yield \n", "prediction \n", "voltage \n", "temperature \n", "grid

\n\n", "numbers \n", "prediction \n", "average \n", "grid

\n\n", "numbers \n", "percent \n", "grid

\n\n"); $graphday1nl = "vermogen in watt in de daagverloop, dagopbrengst, temperatuur omvormer, dc-spanning"; $graphday2nl = "rechte lijn: verwachte dagproductie, curve: dagproductie "; $graphday3nl = "temperatuur omvormer "; $graphday4nl = "spanning van de generator"; $graphmonth1nl = "energie in kWh per daag"; $graphmonth2nl = "verwachte dagopbrengst"; $graphmonth3nl = "gemiddelte dagopbrengst"; $graphyear1nl = "energie in kWh per maand"; $graphyear2nl = "verwachte maandopbrengst"; $switch_arraynl = array("Opbrengst \n", "geakkumuleerde Opbrengst \n", "Voorspelling \n", "Spanning \n", "Temperatuur \n", "Net

\n\n", "Cijfers \n", "Voorspelling \n", "Gemiddelde \n", "Net

\n\n", "Cijfers \n", "Percent \n", "Net

\n\n"); $graphday1fr = "power in Watts, todays yield, inverter temperature, generator voltage"; $graphday2fr = "ligne droite: rendement attendu, courbe: production aujourd'hui"; $graphday3fr = "température d'onduleur"; $graphday4fr = "tension du générateur solaire"; $graphmonth1fr = "rendement quotidien en kWh"; $graphmonth2fr = "rendement attendu quotidien"; $graphmonth3fr = "rendement moyen quotidien"; $graphyear1fr = "rendement mensuel en kWh"; $graphyear2fr = "rendement attendu mensuel"; $switch_arrayfr = array("rendement \n", "rendement accumulés \n", "prévisions \n", "tension \n", "température \n", "grille

\n\n", "nombres \n", "prévisions \n", "moyenne \n", "grille

\n\n", "nombres \n", "pour cent \n", "grille

\n\n"); $graphday1es = "potencia eléctrica en Vatios, producción del día, temperatura del ondulador, voltaje del generador solar"; $graphday2es = "línea recta: producción diaria esperada, curva: producción del día"; $graphday3es = "temperatura del ondulador"; $graphday4es = "voltaje del generador"; $graphmonth1es = "producción diaria en kWh"; $graphmonth2es = "producción diaria esperada"; $graphmonth3es = "producción promedio por día"; $graphyear1es = "producción en kWh por mes"; $graphyear2es = "producción mensual esperada"; $switch_arrayes = array("producción \n", "producción acumuladas \n", "pronóstico \n", "voltaje \n", "temperatura \n", "la red

\n\n", "números \n", "pronóstico \n", "promedio \n", "la red

\n\n", "números \n", "por ciento \n", "la red

\n\n"); $graphday1it = "Potenza in Watt, produzione odierna, temperatura dell’invertitore, voltaggio del generatore"; $graphday2it = "linea retta: produzione giornaliera prevista curva: produzione odierna"; $graphday3it = "temperatura dell’invertitore"; $graphday4it = "voltaggio del generatore"; $graphmonth1it = "produzione in kWh al giorno"; $graphmonth2it = "produzione giornaliera prevista"; $graphmonth3it = "produzione giornaliera media"; $graphyear1it = "produzione in kWh al mese"; $graphyear2it = "produzione mensile prevista"; $switch_arrayit = array("produzione \n", "produzione accumulati \n", "previsione \n", "voltaggio \n", "temperatura \n", "griglia

\n\n", "numeri \n", "previsione \n", "media \n", "griglia

\n\n", "numeri \n", "per cento \n", "griglia

\n\n"); /* error strings */ $error1de = "Ungültige Werte in den Datumsfeldern "; $error2de = "Ungültiges Datum: "; $error3de = "Keine Daten für diese Periode."; $error1en = "invalid data in date fields "; $error2en = "invalid date: "; $error3en = "No data for this period."; $error1nl = "ongeldige datumswaarden "; $error2nl = "ongeldig datum: "; $error3nl = "geen data voor deze periode."; $error1fr = "invalid data in date fields "; $error2fr = "invalid date: "; $error3fr = "No data for this period."; $error1es = "datos incorrectos en los campos de fecha "; $error2es = "fecha incorrecta: "; $error3es = "no existe información para este periodo."; $error1it = "dati non validi nel campi data "; $error2it = "data non valida: "; $error3it = "nessun dato per questo periodo."; ?>solarpowerlog-solarpowerlog-0.26/tools/solarmaxwatcher/web/solarertrag.css000066400000000000000000000033331444065341000274200ustar00rootroot00000000000000/* Layout für Solarmax Watcher written by Frank Lassowski flassowski@gmx.de in August 2010 Licensed under GPLv2 or later http://www.gnu.org/licenses/gpl2.html */ body { font-family: sans-serif; font-size: 0.7em; background:#80806e; color:#404040; } #wrap { background:#ffffff; color:#404040; margin:0 auto; width:820px; } table { background-color: #DDDDDD; width:100%; padding-left: 20px; padding-right: 20px; } p { font-family: sans-serif; font-size: 1.2em; font-weight:bold; text-align: center; } sub { font-family: sans-serif; font-size: 0.6em; } .right { text-align: right; vertical-align: middle; padding-right: 20px; } .right2 { text-align: right; vertical-align: middle; padding-right: 60px; } .left { text-align: left; vertical-align: middle; padding-left: 20px; } /******** Header ********/ #header {font:Verdana,Tahoma,Arial,sans-serif; background:url(img/header.jpg) top center repeat-x; height:121px;} #header h1 {font-size:35px; font-weight:normal; letter-spacing:1.0px; color:#cdd5ea; padding:25px 0px 0px 25px; text-align:left;} #header h1 a {position:relative; top:-5px; left:5px; color:#cdd5ea; text-decoration:none;} #header h1 a:hover {color:#add1ea;} #header h5 a {position:relative; top:-26px; left:7px; color:#ffffff; margin:0 0 0 25px; font-size:1.1em; letter-spacing:0.1px; text-align:left; text-decoration:none;} #header h5 a:hover {color:#add1ea;} /***** Footer *****/ #footer { clear:both; margin:0 auto; padding:2px 0; border-top:2px solid #f0f0f0; width:820px; text-align:center; color:#808080; background:#ffffff; } #footer p { font-size: 0.6em; margin:0; padding:0; } #footer a { color:#808080; background-color:#ffffff; text-decoration:none; font-weight:bold; } #footer a:hover { text-decoration:underline; }solarpowerlog-solarpowerlog-0.26/tools/solarmaxwatcher/web/solarertrag.php000066400000000000000000000344571444065341000274320ustar00rootroot00000000000000 in July 2010 This program was originally licensed under WTFPL 2 http://sam.zoy.org/wtfpl/ Improvements by Frank Lassowski in August 2010 Further improvements by sleepprogger in January 2012 This program is now licensed under GPLv2 or later http://www.gnu.org/licenses/gpl2.html To run this program your server must have PHP and the gd extension enabled. Put this and all the other files contained in 'solarertrag.tar.gz' in your web directory, for example /var/www/ and call it with http://yourwebadress/solarertrag.php */ //check cookie and $_POST['show'] $switch_array = array("yield", "accu", "predday", "volt", "temp", "gridday", "numbersmonth", "predmonth", "avg", "gridmonth", "numbersyear", "percent", "gridyear"); if (empty($_POST['show']) and (!isset($_COOKIE['values']))) { $show = $switch_array; setcookie("values",implode(' ', $switch_array), time()+7*24*3600); } elseif (!empty($_POST['show']) and (!isset($_COOKIE['values']))){ $show = $_POST['show']; setcookie("values",implode(' ', $show), time()+7*24*3600); } elseif (empty($_POST['show']) and isset($_COOKIE['values'])) { $show = array($_COOKIE['values']); } else { $show = $_POST['show']; setcookie("values",implode(' ', $show), time()+7*24*3600); } $show_text = implode(' ', $show); // select table by page query ?wr= // to hopefully avoid SQL injections we only accept numbers :-) if (empty($_GET['wr'])) { $wr = 1; } elseif (preg_match('/[^0-9]/', $_GET['wr'])) { $wr = 1; } else { $wr = $_GET['wr']; } $table="Inverter_data_{$wr}"; // which language does the users browser prefer $lang=substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2); if ($lang <> "de" && $lang <> "en" && $lang <> "nl" && $lang <> "fr" && $lang <> "es" && $lang <> "it") $lang="en"; $sublang=substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 3, 2); if ($sublang <> "de" && $sublang <> "en" && $sublang <> "nl" && $sublang <> "fr" && $sublang <> "es" && $sublang <> "it" && $sublang <> "us" && $sublang <> "ru" && $sublang <> "ch") $sublang = "us"; // include language file include 'lang.php'; // if we want to switch to seperate language files we have to use seperate language files and the following line instead // include 'lang_' . $lang . '.php'; // Which font to use in the graphs // for Windows based servers look at C:/Windows/Fonts for appropriate fonts $fontfile="/usr/share/fonts/truetype/ttf-dejavu/DejaVuSansMono-Oblique.ttf"; // Check other POST vars $period = $_POST['period']; if (!in_array($period, array('day', 'month', 'year'))) $period = 'day'; $day = $_POST['day']; if (empty($day)) $day = date('j'); $month = $_POST['month']; if (empty($month)) $month = date('n'); $year = $_POST['year']; if (empty($year)) $year = date('Y'); if (!preg_match("/[0-9]?[0-9]\.[0-9]?[0-9]\.[0-9][0-9][0-9][0-9]/", "$day.$month.$year")) die(${error1.$lang} . "($day, $month, $year)"); if (!checkdate($month, $day, $year)) die(${error2.$lang} . "$day.$month.$year"); // include daily predictions include 'solarertrag_day_predictions.php'; // Connect to mysql database @mysql_connect('localhost', 'solarpowerlog', '!!!!!passsword!!!!!') or die(mysql_error()); @mysql_select_db('solarpowerlog') or die(mysql_error()); // Check which view to use and define start and end limits switch ($period) { case 'day': $start['day'] = $day; $start['month'] = $month; $start['year'] = $year; $end = $start; break; case 'month': $start['day'] = 1; $start['month'] = $month; $start['year'] = $year; $end['day'] = 31; $end['month'] = $month; $end['year'] = $year; break; case 'year': $start['day'] = 1; $start['month'] = 1; $start['year'] = $year; $end['day'] = 31; $end['month'] = 12; $end['year'] = $year; break; } // Make sure we define a valid end date while (!checkdate($end['month'], $end['day'], $end['year'])) $end['day']--; // Set predictions for chosen date $pred_day = ${'d_'.date('m', mktime(0, 0, 0, $start['month'], $start['day'], $start['year']))}; // Include time in start and end delimiters $start = date('Y-m-d H:i:s', mktime(0, 0, 0, $start['month'], $start['day'], $start['year'])); $end = date('Y-m-d H:i:s', mktime(23, 59, 59, $end['month'], $end['day'], $end['year'])); // Remove old image files foreach (glob("img/*.png") as $image_name) unlink($image_name); // Create a filename with appended date to fool browser caches $image_name = 'img/data_' . date('YmdHis') . '.png'; // Check the desired view again and include and call the proper function $input0 = ""; $input4 = "\n
\n

"; switch ($period) { case 'day': include 'drawday.php'; $text = draw_day($start, $end, $pred_day, $image_name, $table, $fontfile, $show_text).$input4; for ($i = 0; $i <= 5; $i++) { $text = $text.$input1.$switch_array[$i].$input2; if (preg_match("/".$switch_array[$i]."/", $show_text)) $text = $text.$input3.${'switch_array'.$lang}[$i]; else $text = $text.">".${switch_array.$lang}[$i]; } for ($i = 6; $i <= 12; $i++) { $text = $text.$input0.$switch_array[$i]."\" "; if (preg_match("/".$switch_array[$i]."/", $show_text)) $text = $text.$input3."\n"; else $text = $text.">\n"; } break; case 'month': include 'drawmonth.php'; $text = draw_month($start, $end, $pred_day, $image_name, $table, $fontfile, $show_text).$input4; for ($i = 6; $i <= 9; $i++) { $text = $text.$input1.$switch_array[$i].$input2; if (preg_match("/".$switch_array[$i]."/", $show_text)) $text = $text.$input3.${'switch_array'.$lang}[$i]; else $text = $text.">".${switch_array.$lang}[$i]; } for ($i = 0; $i <= 5; $i++) { $text = $text.$input0.$switch_array[$i]."\" "; if (preg_match("/".$switch_array[$i]."/", $show_text)) $text = $text.$input3."\n"; else $text = $text.">\n"; } for ($i = 10; $i <= 12; $i++) { $text = $text.$input0.$switch_array[$i]."\" "; if (preg_match("/".$switch_array[$i]."/", $show_text)) $text = $text.$input3."\n"; else $text = $text.">\n"; } break; case 'year': include 'drawyear.php'; $text = draw_year($start, $end, $pred_month, $image_name, $table, $fontfile, $show_text).$input4; for ($i = 10; $i <= 12; $i++) { $text = $text.$input1.$switch_array[$i].$input2; if (preg_match("/".$switch_array[$i]."/", $show_text)) $text = $text.$input3.${'switch_array'.$lang}[$i]; else $text = $text.">".${'switch_array'.$lang}[$i]; } for ($i = 0; $i <= 9; $i++) { $text = $text.$input0.$switch_array[$i]."\" "; if (preg_match("/".$switch_array[$i]."/", $show_text)) $text = $text.$input3."\n"; else $text = $text.">\n"; } break; } ?> Solarmax Watcher

: : :
> > >
', ${'text1'.$lang}, ''."\n"; echo ''."\n"; echo ''."\n"; echo ''."\n"; echo ''."\n"; echo ''."\n"; echo ''."\n"; echo ''."\n"; // echo ''."\n"; // echo ''."\n"; echo ''."\n"; echo '
', mysql_result( $result, 0, 0), ' Watt', ${'text2'.$lang}, '', mysql_result( $result, 0, 1) , ' kWh
', ${'text3'.$lang}, '', mysql_result( $result, 0, 5), ' °C', ${'text4'.$lang}, '', mysql_result( $result, 0, 2), ' kWh
', ${'text5'.$lang}, '', round( mysql_result( $result, 0, 3) * 0.683 / 1000, 3), ' to', ${'text6'.$lang}, '', mysql_result( $result, 0, 3), ' kWh
', ${'text7'.$lang}, '', round( mysql_result( $result, 0, 4) * 0.683 / 1000, 3), ' to', ${'text8'.$lang}, '', mysql_result( $result, 0, 4), ' kWh
', ${'text9'.$lang}, '', ${'_'.mysql_result( $result, 0, 6).$lang}, '', ${'text10'.$lang}, '', round( mysql_result( $result, 0, 4) * 0.3405 * ${curr.$sublang}, 2), ' ', ${currtxt.$sublang}, '', ${'text10'.$lang}, '', round( mysql_result( $result, 0, 4) * 0.3405, 2), ' EUR
'."\n"; echo $text; echo "
\n"; ?> Sonneneinstrahlungsdiagramm
solarpowerlog-solarpowerlog-0.26/tools/solarmaxwatcher/web/solarertrag_day_predictions.php000066400000000000000000000006661444065341000326650ustar00rootroot00000000000000 solarpowerlog-solarpowerlog-0.26/tools/solarmaxwatcher/web/solarertrag_month_predictions.php000066400000000000000000000006451444065341000332320ustar00rootroot00000000000000 solarpowerlog-solarpowerlog-0.26/tools/solarpowerlog.default000066400000000000000000000011401444065341000246370ustar00rootroot00000000000000# Defaults for solarpowerlog initscript # License: GPL3 or later # Author: Tobias Frost # # This is a POSIX shell fragment # # Where to cd to before executing solarpowerlog RUNDIR=/etc/solarpowerlog # Run it as this user. defaults to "nobody" # Note: When you change this, you'll need to chown the logdir to the new user. #USER=nobody # Run it as this group. defaults to "nogroup" #GROUP=nogroup # Options to pass to solarpowerlog. For example, where is the config file: # The options to setup logging and launch the daemon are automatically added DAEMON_ARGS="-c /etc/solarpowerlog/solarpowerlog.conf" solarpowerlog-solarpowerlog-0.26/tools/solarpowerlog.init000077500000000000000000000100061444065341000241620ustar00rootroot00000000000000#! /bin/sh ### BEGIN INIT INFO # Provides: solarpowerlog # Required-Start: $remote_fs $syslog $network # Required-Stop: $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Solarpowerlog startup # Description: Solarpowerlog queries solarpower inverters and logs their data ### END INIT INFO # Author: Tobias Frost # License GPL3 or later # Do NOT "set -e" # PATH should only include /usr/* if it runs after the mountnfs.sh script PATH=/sbin:/usr/sbin:/bin:/usr/bin # Preset options. USER=nobody GROUP=nogroup RUNDIR="/" DESC="solarpowerlog" NAME=solarpowerlog DAEMON=/usr/bin/$NAME PIDDIR=/var/run/$NAME PIDFILE=$PIDDIR/$NAME.pid LOGDIR=/var/log/$NAME LOGSTD=$LOGDIR/$NAME.log LOGERR=$LOGDIR/$NAME.err SCRIPTNAME=/etc/init.d/$NAME # Exit if the package is not installed [ -x "$DAEMON" ] || exit 0 # Read configuration variable file if it is present [ -r /etc/default/$NAME ] && . /etc/default/$NAME # Load the VERBOSE setting and other rcS variables . /lib/init/vars.sh # Define LSB log_* functions. # Depend on lsb-base (>= 3.2-14) to ensure that this file is present # and status_of_proc is working. . /lib/lsb/init-functions # # Function that starts the daemon/service # do_start() { # As we want to run as non-privilegded user, we need to create a helper directory [ ! -e $PIDDIR ] && mkdir -p $PIDDIR && chown $USER $PIDDIR # The same applies to the programm logs. We need to ensure that solarpowerlog can # access it. [ ! -e $LOGDIR ] && mkdir -p $LOGDIR && chown $USER $LOGDIR # Return # 0 if daemon has been started # 1 if daemon was already running # 2 if daemon could not be started start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ || return 1 start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON \ -u $USER -c $USER -g $GROUP -d $RUNDIR -- $DAEMON_ARGS -b \ --pidfile $PIDFILE --stdout $LOGSTD --stderr $LOGERR \ --chdir $RUNDIR \ || return 2 } # # Function that stops the daemon/service # do_stop() { # Return # 0 if daemon has been stopped # 1 if daemon was already stopped # 2 if daemon could not be stopped # other if a failure occurred start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME RETVAL="$?" [ "$RETVAL" = 2 ] && return 2 # Wait for children to finish too if this is a daemon that forks # and if the daemon is only ever run from this initscript. # If the above conditions are not satisfied then add some other code # that waits for the process to drop all resources that could be # needed by services started subsequently. A last resort is to # sleep for some time. start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON [ "$?" = 2 ] && return 2 # Delete PIDDIR. rm -rf $PIDDIR return "$RETVAL" } # # Function that sends a SIGHUP to the daemon/service # do_reload() { # # If the daemon can reload its configuration without # restarting (for example, when it is sent a SIGHUP), # then implement that here. # start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME return 0 } case "$1" in start) [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" do_start case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; stop) [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" do_stop case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; status) status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? ;; restart|force-reload) log_daemon_msg "Restarting $DESC" "$NAME" do_stop case "$?" in 0|1) do_start case "$?" in 0) log_end_msg 0 ;; 1) log_end_msg 1 ;; # Old process is still running *) log_end_msg 1 ;; # Failed to start esac ;; *) # Failed to stop log_end_msg 1 ;; esac ;; *) echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 exit 3 ;; esac : solarpowerlog-solarpowerlog-0.26/tools/solarpowerlog.logrotate000066400000000000000000000004411444065341000252160ustar00rootroot00000000000000/var/log/solarpowerlog/solarpowerlog.log /var/log/solarpowerlog/solarpowerlog.err { monthly rotate 12 compress missingok notifempty sharedscripts postrotate [ -e /var/run/solarpowerlog/solarpowerlog.pid ] && kill -USR1 `cat /var/run/solarpowerlog/solarpowerlog.pid` endscript size 256M }