pax_global_header00006660000000000000000000000064121347211710014511gustar00rootroot0000000000000052 comment=a08faa3f7b7347208cfec142e8aa5f29236c94c3 wfrog-0.8.2+svn953/000077500000000000000000000000001213472117100137075ustar00rootroot00000000000000wfrog-0.8.2+svn953/LICENSE.txt000066400000000000000000001045131213472117100155360ustar00rootroot00000000000000 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 . wfrog-0.8.2+svn953/bin/000077500000000000000000000000001213472117100144575ustar00rootroot00000000000000wfrog-0.8.2+svn953/bin/wfrog000077500000000000000000000125601213472117100155350ustar00rootroot00000000000000#!/usr/bin/python ## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . ''' Bootstrap script for running wfrog components from one place. ''' # This makes sure that users don't have to set up their environment # specially in order to run these programs from bin/. import sys, os, string, os.path WFROG_HOME='.' this_script=os.path.abspath(os.path.realpath(sys.argv[0])) if sys.platform != 'win32' and string.find(this_script, os.sep+'wfrog') != -1: WFROG_HOME=os.path.normpath(os.path.join(this_script, os.pardir, os.pardir)) sys.path.insert(0, WFROG_HOME) if sys.platform == 'win32': WFROG_HOME=os.path.normpath(os.path.join(this_script, os.pardir)) if hasattr(os, "getuid") and os.getuid() != 0: sys.path.insert(0, os.path.abspath(os.getcwd())) import optparse import wfcommon.customize import wflogger.setup import wflogger.wflogger import wfrender.wfrender import logging import getpass SETTINGS_FILE = 'settings.yaml' GLOBAL_CONF_DIR = '/etc/wfrog/' SETTINGS_DEF=os.path.normpath(WFROG_HOME+'/wfcommon/config/settings-definition.yaml') if sys.platform == 'win32': import _winreg HOME_WFROG_DIR = _winreg.ExpandEnvironmentStrings(u'%APPDATA%\\Wfrog\\') else: HOME_WFROG_DIR = os.path.expanduser("~"+getpass.getuser())+'/.wfrog/' settings = None # detect settings if os.path.exists(HOME_WFROG_DIR + SETTINGS_FILE): settings = HOME_WFROG_DIR + SETTINGS_FILE else: if os.path.exists(GLOBAL_CONF_DIR + SETTINGS_FILE): settings = GLOBAL_CONF_DIR + SETTINGS_FILE else: if os.path.exists('./'+SETTINGS_FILE): settings = os.path.abspath('./'+SETTINGS_FILE) opt_parser = optparse.OptionParser(conflict_handler='resolve') opt_parser.add_option("-B", "--backend", action="store_true", dest="backend", help="Starts the logger and the driver only.") opt_parser.add_option("-R", "--renderer", action="store_true", dest="renderer", help="Starts the renderer only.") opt_parser.add_option("-C", "--customize", action="store_true", dest="customize", help="Prepare the config files for customizing wfrog. Safe operation, it does not overwrite an existing custom config.") opt_parser.add_option("-S", "--setup", action="store_true", dest="setup", help="Define the settings interactively.") opt_parser.add_option('-w', '--cwd', action='store_true', dest='cwd', help='Use the current working directory for data instead of the default one.') opt_parser.add_option("-f", "--config", dest="config_file", help="Configuration file (in yaml)", metavar="CONFIG_FILE") opt_parser.add_option("-s", "--settings", dest="settings", help="Settings file (in yaml)", metavar="SETTINGS_FILE") opt_parser.add_option("-m", "--mute", action="store_true", dest="mute", help="Skip the setup of user settings. Do not issues any questions but fails if settings are missing.") candidate_logger = wflogger.wflogger.Logger(opt_parser) candidate_renderer = wfrender.wfrender.RenderEngine(opt_parser) (options, args) = opt_parser.parse_args() if options.settings: settings = os.path.abspath(options.settings) component = candidate_logger if options.backend: if not options.config_file: config_file = 'wflogger/config/wflogger.yaml' else: if options.renderer: component = candidate_renderer if not options.config_file: config_file = 'wfrender/config/wfrender.yaml' else: if not options.config_file: config_file = 'wflogger/config/wfrog.yaml' # detect configuration if os.path.exists(HOME_WFROG_DIR + config_file): config_dir = HOME_WFROG_DIR else: if os.path.exists(GLOBAL_CONF_DIR + config_file): config_dir = GLOBAL_CONF_DIR else: if os.path.exists('./'+config_file): config_dir = os.path.abspath('.') else: config_dir = WFROG_HOME config_file = config_dir + '/'+config_file user = getpass.getuser() if not options.cwd and not user=='root': try: os.makedirs(HOME_WFROG_DIR) except: pass os.chdir(HOME_WFROG_DIR) if user == 'root': customize_dir = GLOBAL_CONF_DIR settings_file = GLOBAL_CONF_DIR+SETTINGS_FILE else: customize_dir = HOME_WFROG_DIR settings_file = HOME_WFROG_DIR+SETTINGS_FILE if options.customize: wfcommon.customize.Customizer().customize(WFROG_HOME+'/', customize_dir, ['wfcommon', 'wfdriver', 'wflogger', 'wfrender']) sys.exit(0) if options.setup or settings is None: if not options.mute: settings = wflogger.setup.SetupClient().setup_settings(SETTINGS_DEF, settings, settings_file) if options.setup: sys.exit(0) else: print "Now starting wfrog. Standard config serves on http://localhost:7680/." component.run(config_file, settings) wfrog-0.8.2+svn953/database/000077500000000000000000000000001213472117100154535ustar00rootroot00000000000000wfrog-0.8.2+svn953/database/conversion_script-firebird-0.1-0.2.sql000066400000000000000000000055241213472117100243300ustar00rootroot00000000000000-- Before the changes it is good to do a backup export of the db ... just in case :-) -- (see below for restore command) -- -- gbak -v -t -user SYSDBA -password "masterkey" localhost:/var/lib/firebird/2.1/data/wfrog.db wfrog.fbk -- Drop unnecessary fields DELETE FROM RDB$DEPENDENCIES WHERE RDB$DEPENDED_ON_NAME='METEO'; ALTER TABLE METEO DROP TEMP_MIN, DROP TEMP_MIN_TIME, DROP TEMP_MAX, DROP TEMP_MAX_TIME, DROP WIND_GUST_TIME, DROP RAIN_RATE_TIME; ALTER TABLE METEO ADD TMP1 NUMERIC(4,1), ADD TMP2 NUMERIC(4,1); COMMIT; -- Reducing field precision from int to smallint for RAIN and RAIN_RATE fields -- requires creating temporal fields first. UPDATE METEO SET TMP1 = RAIN, TMP2 = RAIN_RATE; ALTER TABLE METEO DROP RAIN, DROP RAIN_RATE; ALTER TABLE METEO ADD RAIN NUMERIC(4,1), ADD RAIN_RATE NUMERIC(4,1); COMMIT; UPDATE METEO SET RAIN = TMP1, RAIN_RATE = TMP2; ALTER TABLE METEO DROP TMP1, DROP TMP2; COMMIT; -- Add field comments. Useful when viewing table properties in Flamerobin UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'TEMPERATURE (C)' where RDB$FIELD_NAME = 'TEMP' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = '% RELATIVE HUMIDITY' where RDB$FIELD_NAME = 'HUM' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'WIND AVERAGE SPEED (m/s)' where RDB$FIELD_NAME = 'WIND' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'WIND PREDOMINANT DIRECTION (0-359)' where RDB$FIELD_NAME = 'WIND_DIR' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'WIND GUST SPEED (m/s)' where RDB$FIELD_NAME = 'WIND_GUST' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'WIND GUST DIRECTION (0-359)' where RDB$FIELD_NAME = 'WIND_GUST_DIR' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'DEWPOINT TEMPERATURE (C)' where RDB$FIELD_NAME = 'DEW_POINT' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'RAIN FALL (mm)' where RDB$FIELD_NAME = 'RAIN' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'RAIN RATE (mm/hr)' where RDB$FIELD_NAME = 'RAIN_RATE' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'ATMOSFERIC PRESSURE (mb)' where RDB$FIELD_NAME = 'PRESSURE' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'UV INDEX' where RDB$FIELD_NAME = 'UV_INDEX' and RDB$RELATION_NAME = 'METEO'; COMMIT; -- Export and import to improve database structure and performance: -- -- gbak -v -t -user SYSDBA -password "masterkey" localhost:/var/lib/firebird/2.1/data/wfrog.db wfrog.fbk -- gbak -v -rep -use_all_space -user SYSDBA -password "masterkey" wfrog.fbk localhost:/var/lib/firebird/2.1/data/wfrog.db wfrog-0.8.2+svn953/database/csv2sql000077500000000000000000000051111213472117100167740ustar00rootroot00000000000000#!/usr/bin/python ## Copyright 2013 A Mennucc1 ## ## This file is part of wfrog ## It converts CSV recordings of meteo data to Sqlite3 ## ## ## wfrog 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 . import csv, time, string, os, sys, sqlite3 import os.path from datetime import datetime if len(sys.argv) != 3: print """ Usage: csv2sql input.csv output.sql This program reads CSV recordings of meteo data, and adds them to a Sqlite3 database. The database must be initialized to contain the table 'METEO', see database/sqlite3.sql in the source code. Note that the SQL table it may contain more fields per record than the CSV; this script will selfadapt. """ sys.exit(0) columns = [ 'timestamp', 'localtime', 'temp', 'hum', 'wind', 'wind_dir', 'wind_gust', 'wind_gust_dir', 'dew_point', 'rain', 'rain_rate', 'pressure', 'uv_index' ] def _get_table_fields(db, tablename='METEO'): sql = "PRAGMA table_info(%s);" % tablename fields = [] c=db.cursor() c.execute(sql) for row in c: fields.append(row[1].lower()) return fields reader = csv.reader(open(sys.argv[1])) writer = sqlite3.connect(sys.argv[2], detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES) table_fields = _get_table_fields(writer) # Verify Mandatory fields assert 'timestamp_utc' in table_fields assert 'timestamp_local' in table_fields inserter='?,'*len(table_fields) inserter="insert into METEO values ( %s ) ;" % inserter[:-1] themap=[] for i in range(len(table_fields)): try: j=columns.index(table_fields[i]) themap.append(j) except ValueError: themap.append(None) themap[0]=1 themap[1]=1 c=writer.cursor() for l in reader: if not l: continue w=[] for i in range(len(table_fields)): if themap[i] != None: v=l[themap[i]] if len(v)==0: w.append(None) else: w.append(v) else: w.append(None) c.execute(inserter, w) writer.commit() c.close() wfrog-0.8.2+svn953/database/db-firebird-0.1.sql000066400000000000000000000026471213472117100206520ustar00rootroot00000000000000-- Copyright 2009 Jordi Puigsegur (jordi.puigsegur@gmail.com) -- -- This file is part of WFrog -- -- WFrog 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 . CREATE TABLE METEO( TIMESTAMP_UTC Timestamp NOT NULL, TIMESTAMP_LOCAL Timestamp NOT NULL, TEMP Numeric(3,1), TEMP_MIN Numeric(3,1), TEMP_MIN_TIME Timestamp, TEMP_MAX Numeric(3,1), TEMP_MAX_TIME Timestamp, HUM Numeric(2,1), WIND Numeric(4,1), WIND_DIR Smallint, WIND_GUST Numeric(4,1), WIND_GUST_DIR Smallint, WIND_GUST_TIME Timestamp, DEW_POINT Numeric(3,1), RAIN Numeric(5,1), RAIN_RATE Numeric(5,1), RAIN_RATE_TIME Timestamp, PRESSURE Numeric(5,1), UV_INDEX Smallint, CONSTRAINT PK_METEO PRIMARY KEY (TIMESTAMP_UTC) ); -- To add UV col to existing table: ALTER TABLE METEO ADD UV SMALLINT; CREATE DESCENDING INDEX IDX_METEO1 ON METEO (TIMESTAMP_LOCAL); wfrog-0.8.2+svn953/database/db-firebird-0.2.sql000066400000000000000000000053021213472117100206420ustar00rootroot00000000000000-- Copyright 2009 Jordi Puigsegur (jordi.puigsegur@gmail.com) -- -- This file is part of WFrog -- -- WFrog 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 . CREATE TABLE METEO ( TIMESTAMP_UTC Timestamp NOT NULL, TIMESTAMP_LOCAL Timestamp NOT NULL, TEMP Numeric(3,1), HUM Numeric(3,1), WIND Numeric(4,1), WIND_DIR Smallint, WIND_GUST Numeric(4,1), WIND_GUST_DIR Smallint, DEW_POINT Numeric(3,1), RAIN Numeric(4,1), RAIN_RATE Numeric(4,1), PRESSURE Numeric(5,1), UV_INDEX Smallint, CONSTRAINT PK_METEO PRIMARY KEY (TIMESTAMP_UTC) ); UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'TEMPERATURE (C)' where RDB$FIELD_NAME = 'TEMP' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = '% RELATIVE HUMIDITY' where RDB$FIELD_NAME = 'HUM' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'WIND AVERAGE SPEED (m/s)' where RDB$FIELD_NAME = 'WIND' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'WIND PREDOMINANT DIRECTION (0-359)' where RDB$FIELD_NAME = 'WIND_DIR' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'WIND GUST SPEED (m/s)' where RDB$FIELD_NAME = 'WIND_GUST' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'WIND GUST DIRECTION (0-359)' where RDB$FIELD_NAME = 'WIND_GUST_DIR' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'DEWPOINT TEMPERATURE (C)' where RDB$FIELD_NAME = 'DEW_POINT' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'RAIN FALL (mm)' where RDB$FIELD_NAME = 'RAIN' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'RAIN RATE (mm/hr)' where RDB$FIELD_NAME = 'RAIN_RATE' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'ATMOSFERIC PRESSURE (mb)' where RDB$FIELD_NAME = 'PRESSURE' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'UV INDEX' where RDB$FIELD_NAME = 'UV_INDEX' and RDB$RELATION_NAME = 'METEO'; CREATE DESCENDING INDEX IDX_METEO1 ON METEO (TIMESTAMP_LOCAL); wfrog-0.8.2+svn953/database/db-firebird-0.9.sql000066400000000000000000000071031213472117100206520ustar00rootroot00000000000000-- Copyright 2011 Jordi Puigsegur (jordi.puigsegur@gmail.com) -- -- This file is part of WFrog -- -- WFrog 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 . -- wfrog supports additional temperature & humidity sensors when using -- database storages. Just uncomment the relevant lines and the sensor -- values will be recorded. CREATE TABLE METEO ( TIMESTAMP_UTC Timestamp NOT NULL, TIMESTAMP_LOCAL Timestamp NOT NULL, -- Uncomment to record sensor 0, interior TEMP/HUM -- TEMPINT Numeric(3,1), -- HUMINT Numeric(3,1), -- Main TEMP/HUM sensor is sensor number 1 TEMP Numeric(3,1), HUM Numeric(3,1), WIND Numeric(4,1), WIND_DIR Smallint, WIND_GUST Numeric(4,1), WIND_GUST_DIR Smallint, DEW_POINT Numeric(3,1), RAIN Numeric(4,1), RAIN_RATE Numeric(4,1), PRESSURE Numeric(5,1), -- Uncomment to record UV Index -- UV_INDEX Smallint, -- Uncomment to record Solar Radiation sensor -- SOLAR_RAD Numeric(5,1) -- Uncomment to record additional TEMP/HUM sensors -- TEMP2 Numeric(3,1), -- HUM2 Numeric(3,1), -- TEMP3 Numeric(3,1), -- HUM3 Numeric(3,1), -- TEMP4 Numeric(3,1), -- HUM4 Numeric(3,1), -- TEMP5 Numeric(3,1), -- HUM5 Numeric(3,1), -- TEMP6 Numeric(3,1), -- HUM6 Numeric(3,1), -- TEMP7 Numeric(3,1), -- HUM7 Numeric(3,1), -- TEMP8 Numeric(3,1), -- HUM8 Numeric(3,1), -- TEMP9 Numeric(3,1), -- HUM9 Numeric(3,1), CONSTRAINT PK_METEO PRIMARY KEY (TIMESTAMP_UTC) ); UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'TEMPERATURE (C)' where RDB$FIELD_NAME LIKE 'TEMP%' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = '% RELATIVE HUMIDITY' where RDB$FIELD_NAME LIKE 'HUM%' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'WIND AVERAGE SPEED (m/s)' where RDB$FIELD_NAME = 'WIND' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'WIND PREDOMINANT DIRECTION (0-359)' where RDB$FIELD_NAME = 'WIND_DIR' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'WIND GUST SPEED (m/s)' where RDB$FIELD_NAME = 'WIND_GUST' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'WIND GUST DIRECTION (0-359)' where RDB$FIELD_NAME = 'WIND_GUST_DIR' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'DEWPOINT TEMPERATURE (C)' where RDB$FIELD_NAME = 'DEW_POINT' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'RAIN FALL (mm)' where RDB$FIELD_NAME = 'RAIN' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'RAIN RATE (mm/hr)' where RDB$FIELD_NAME = 'RAIN_RATE' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'ATMOSFERIC PRESSURE (mb)' where RDB$FIELD_NAME = 'PRESSURE' and RDB$RELATION_NAME = 'METEO'; UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'UV INDEX' where RDB$FIELD_NAME = 'UV_INDEX' and RDB$RELATION_NAME = 'METEO'; CREATE DESCENDING INDEX IDX_METEO1 ON METEO (TIMESTAMP_LOCAL); wfrog-0.8.2+svn953/database/db-mysql-0.3.sql000066400000000000000000000006361213472117100202270ustar00rootroot00000000000000CREATE TABLE METEO ( TIMESTAMP_UTC Timestamp NOT NULL, TIMESTAMP_LOCAL Timestamp NOT NULL, TEMP Numeric(3,1), HUM Numeric(3,1), WIND Numeric(4,1), WIND_DIR Smallint, WIND_GUST Numeric(4,1), WIND_GUST_DIR Smallint, DEW_POINT Numeric(3,1), RAIN Numeric(4,1), RAIN_RATE Numeric(4,1), PRESSURE Numeric(5,1), UV_INDEX Smallint, PRIMARY KEY (TIMESTAMP_UTC), KEY METEO_IDX (TIMESTAMP_LOCAL)); wfrog-0.8.2+svn953/database/db-mysql-0.9.sql000066400000000000000000000040051213472117100202270ustar00rootroot00000000000000-- Copyright 2011 Jordi Puigsegur (jordi.puigsegur@gmail.com) -- -- This file is part of WFrog -- -- WFrog 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 . -- wfrog supports additional temperature & humidity sensors when using -- database storages. Just uncomment the relevant lines and the sensor -- values will be recorded. CREATE TABLE METEO ( TIMESTAMP_UTC Timestamp NOT NULL, TIMESTAMP_LOCAL Timestamp NOT NULL, -- Uncomment to record sensor 0, interior TEMP/HUM -- TEMPINT Numeric(3,1), -- HUMINT Numeric(3,1), -- Main TEMP/HUM sensor is sensor number 1 TEMP Numeric(3,1), HUM Numeric(3,1), WIND Numeric(4,1), WIND_DIR Smallint, WIND_GUST Numeric(4,1), WIND_GUST_DIR Smallint, DEW_POINT Numeric(3,1), RAIN Numeric(4,1), RAIN_RATE Numeric(4,1), PRESSURE Numeric(5,1), -- Uncomment to record UV Index -- UV_INDEX Smallint, -- Uncomment to record Solar Radiation sensor -- SOLAR_RAD Numeric(5,1) -- Uncomment to record additional TEMP/HUM sensors -- TEMP2 Numeric(3,1), -- HUM2 Numeric(3,1), -- TEMP3 Numeric(3,1), -- HUM3 Numeric(3,1), -- TEMP4 Numeric(3,1), -- HUM4 Numeric(3,1), -- TEMP5 Numeric(3,1), -- HUM5 Numeric(3,1), -- TEMP6 Numeric(3,1), -- HUM6 Numeric(3,1), -- TEMP7 Numeric(3,1), -- HUM7 Numeric(3,1), -- TEMP8 Numeric(3,1), -- HUM8 Numeric(3,1), -- TEMP9 Numeric(3,1), -- HUM9 Numeric(3,1), PRIMARY KEY (TIMESTAMP_UTC), KEY METEO_IDX (TIMESTAMP_LOCAL)); wfrog-0.8.2+svn953/database/sqlite3.sql000066400000000000000000000034141213472117100175620ustar00rootroot00000000000000-- Copyright 2012 A Mennucc1 -- -- This file is part of WFrog -- -- WFrog 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 . -- wfrog supports additional temperature & humidity sensors when using -- database storages. Just uncomment the relevant lines and the sensor -- values will be recorded. CREATE TABLE METEO( TIMESTAMP_UTC TIMESTAMP NOT NULL PRIMARY KEY, TIMESTAMP_LOCAL TIMESTAMP NOT NULL, -- Uncomment to record sensor 0, interior TEMP/HUM -- TEMPINT REAL, -- HUMINT REAL, -- Main TEMP/HUM sensor is sensor number 1 TEMP REAL, HUM REAL, WIND REAL, WIND_DIR Smallint, WIND_GUST REAL, WIND_GUST_DIR Smallint, DEW_POINT REAL, RAIN REAL, RAIN_RATE REAL, PRESSURE Numeric -- Uncomment to record UV Index -- UV_INDEX Smallint, -- Uncomment to record Solar Radiation sensor -- SOLAR_RAD Numeric(5,1) -- Uncomment to record additional TEMP/HUM sensors -- TEMP2 REAL, -- HUM2 REAL, -- TEMP3 REAL, -- HUM3 REAL, -- TEMP4 REAL, -- HUM4 REAL, -- TEMP5 REAL, -- HUM5 REAL, -- TEMP6 REAL, -- HUM6 REAL, -- TEMP7 REAL, -- HUM7 REAL, -- TEMP8 REAL, -- HUM8 REAL, -- TEMP9 REAL, -- HUM9 REAL, ); CREATE INDEX METEO_IDX ON METEO(TIMESTAMP_LOCAL); wfrog-0.8.2+svn953/debian/000077500000000000000000000000001213472117100151315ustar00rootroot00000000000000wfrog-0.8.2+svn953/debian/changelog000066400000000000000000000025351213472117100170100ustar00rootroot00000000000000wfrog (0.8.2-1) maverick; urgency=low * * New template with date picker and tabs for charts and numerical data. Support for several TH sensors. Configuration files improved. Several fixes in station drivers and many small bugs fixed. * See http://code.google.com/p/wfrog/wiki/ReleaseNotes -- Jordi Puigsegur Figueras Sat, 18 Feb 2012 10:57:58 +0100 wfrog (0.8.1-1) unstable; urgency=low * Dramatically simplified the configuration by choosing the station in startup settings. Some fixes. * See http://code.google.com/p/wfrog/wiki/ReleaseNotes -- Laurent Bovet Tue, 22 Mar 2011 20:47:28 +0100 wfrog (0.8-1) unstable; urgency=low * Many fixes and improvements, added upload to wunderground, numerical data summary and improved support of WH1081-like and VantagePro stations. * See http://code.google.com/p/wfrog/wiki/ReleaseNotes -- Laurent Bovet Fri, 04 Mar 2011 21:47:06 +0100 wfrog (0.7-1) unstable; urgency=low * WMR200 driver, .deb packaging, better configurability, many small fixes and improvements. * See http://code.google.com/p/wfrog/wiki/ReleaseNotes -- Laurent Bovet Wed, 25 Aug 2010 21:57:37 +0200 wfrog (0.6-1) unstable; urgency=low * Initial release. -- Laurent Bovet Tue, 27 Jul 2010 23:28:30 +0200 wfrog-0.8.2+svn953/debian/compat000066400000000000000000000000021213472117100163270ustar00rootroot000000000000007 wfrog-0.8.2+svn953/debian/control000066400000000000000000000024151213472117100165360ustar00rootroot00000000000000Source: wfrog Section: science Priority: optional Maintainer: Laurent Bovet Build-Depends: debhelper (>= 7) Standards-Version: 3.8.3 Homepage: htp://www.wfrog.org Package: wfrog Architecture: all Depends: python (>=2.5), python-yaml (>=3.0), python-cheetah (>=2.0), python-pygooglechart (>=0.2.0), python-lxml (>=1.1.1), python-usb (>=0.4), python-serial (>=2.3) Description: Web-based customizable weather station software wfrog is a software for logging weather station data and statistics, viewing them graphically on the web and sending them to a remote FTP site. The layout and behaviour is fully customizable through an advanced configuration system. It is written in python with an extensible architecture allowing new station drivers to be written very easily. wfrog supports many weather stations and is compliant with the WESTEP protocol. Supported stations: * Ambient Weather WS1080 * Davis VantagePro, VantagePro2 * Elecsa AstroTouch 6975 * Fine Offset Electronics WH1080, WH1081, WH1090, WH1091, WH2080, WH2081 * Freetec PX1117 * LaCrosse 2300 series * Oregon Scientific WMR100N, WMR200, WMRS200, WMR928X * PCE FWS20 * Scientific Sales Pro Touch Screen Weather Station * Topcom National Geographic 265NE * Watson W8681 wfrog-0.8.2+svn953/debian/copyright000066400000000000000000000071551213472117100170740ustar00rootroot00000000000000This work was packaged for Debian by: Laurent Bovet on Tue, 27 Jul 2010 23:28:30 +0200 It was downloaded from: http://www.wfrog.org Upstream Author(s): Laurent Bovet Jordi Puigsegur Copyright: Copyright (C) 2010 Laurent Bovet Copyright (C) 2010 Jordi Puigsegur License: 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 package 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 . On Debian systems, the complete text of the GNU General Public License version 3 can be found in `/usr/share/common-licenses/GPL-3'. The Debian packaging is: Copyright (C) 2010 Laurent Bovet and is licensed under the GPL version 3, see above. This work contains code from: webcolors (http://bitbucket.org/ubernostrum/webcolors/overview/) Copyright (c) 2008-2009, James Bennett All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the author nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. This work contains python code ported from: uWxUtils (http://www.softwx.com/weather/uwxutils.html) This source code may be freely used, including for commercial purposes Steve Hatchett, SoftWx, Inc. http://www.softwx.com/ This work was inspired from the documentation from: http://www.ejeklint.se/development/wmr100n-driver-for-mac-os-x/wmr100n-usb-protocol/ http://wmrx00.sourceforge.net/ (WMR100 weather logger project) http://www.netsky.org/WMR/Protocol.htm http://www.cs.stir.ac.uk/~kjt/software/comms/wmr928.html http://www.castro.aus.net/~maurice/weather/ Thanks to all of them. wfrog-0.8.2+svn953/debian/docs000066400000000000000000000000141213472117100157770ustar00rootroot00000000000000LICENSE.txt wfrog-0.8.2+svn953/debian/rules000077500000000000000000000022271213472117100162140ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 build: build-stamp build-stamp: dh_testdir touch build-stamp clean: dh_testdir dh_testroot rm -f build-stamp configure-stamp dh_clean install: build dh_testdir dh_testroot dh_clean -k dh_installdirs # Install wfrog executables and default config mkdir -p $(CURDIR)/debian/wfrog/usr/lib/wfrog mkdir -p $(CURDIR)/debian/wfrog/usr/bin mkdir -p $(CURDIR)/debian/wfrog/etc/init.d cp -r bin/ wfcommon/ wfdriver/ wflogger/ wfrender/ init.d/ $(CURDIR)/debian/wfrog/usr/lib/wfrog ln -s /usr/lib/wfrog/bin/wfrog $(CURDIR)/debian/wfrog/usr/bin/wfrog ln -s /usr/lib/wfrog/init.d/wflogger $(CURDIR)/debian/wfrog/etc/init.d/wflogger ln -s /usr/lib/wfrog/init.d/wfrender $(CURDIR)/debian/wfrog/etc/init.d/wfrender # Prepare data directory mkdir -p $(CURDIR)/debian/wfrog/var/lib/wfrog binary-indep: build install dh_testdir dh_testroot dh_installchangelogs dh_fixperms dh_installdeb dh_gencontrol dh_md5sums dh_builddeb binary-arch: build install binary: binary-indep binary-arch .PHONY: build clean binary-indep binary-arch binary install configure wfrog-0.8.2+svn953/init.d/000077500000000000000000000000001213472117100150745ustar00rootroot00000000000000wfrog-0.8.2+svn953/init.d/wflogger000077500000000000000000000062641213472117100166460ustar00rootroot00000000000000#! /bin/sh ### BEGIN INIT INFO # Provides: wflogger # Required-Start: $local_fs $remote_fs # Required-Stop: $local_fs $remote_fs # Default-Start: 2 3 4 5 # Default-Stop: S 0 1 6 # Short-Description: wfrog logger # Description: wfrog - Weather Station Software ### END INIT INFO # Author: Laurent bovet # Do NOT "set -e" # PATH should only include /usr/* if it runs after the mountnfs.sh script PATH=/usr/sbin:/usr/bin:/sbin:/bin NAME=wfrog DESC="wfrog logger - Weather Station Software " LOGGER_DIR=/usr/lib/wfrog/bin LOGGER=wfrog LOGGER_ARGS="--backend -m" PIDFILE_LOGGER=/var/run/wflogger.pid SCRIPTNAME=/etc/init.d/wflogger USER=root # Exit if the package is not installed [ -x "$LOGGER_DIR/$LOGGER" ] || 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 [ -f /etc/default/rcS ] && . /etc/default/rcS # Define LSB log_* functions. # Depend on lsb-base (>= 3.0-6) to ensure that this file is present. . /lib/lsb/init-functions # # Function that starts the daemon/service # do_start_logger() { # 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 --background --make-pidfile --pidfile $PIDFILE_LOGGER -d $LOGGER_DIR --exec $LOGGER_DIR/$LOGGER --test > /dev/null \ || return 1 start-stop-daemon --start --verbose --background --make-pidfile --pidfile $PIDFILE_LOGGER -d $LOGGER_DIR --exec $LOGGER_DIR/$LOGGER -- $LOGGER_ARGS \ || return 2 } # # Function that stops the daemon/service # do_stop_logger() { # 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_LOGGER --name $LOGGER RETVAL="$?" [ "$RETVAL" = 2 ] && return 2 rm -f $PIDFILE return "$RETVAL" } case "$1" in start) [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" do_start_logger 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_logger case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; restart|force-reload) # # If the "reload" option is implemented then remove the # 'force-reload' alias # log_daemon_msg "Restarting $DESC" "$NAME" do_stop_logger case "$?" in 0|1) do_start_logger 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|restart|reload|force-reload}" >&2 echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2 exit 3 ;; esac : wfrog-0.8.2+svn953/init.d/wfrender000077500000000000000000000063431213472117100166440ustar00rootroot00000000000000#! /bin/sh ### BEGIN INIT INFO # Provides: wfrender # Required-Start: $local_fs $remote_fs # Required-Stop: $local_fs $remote_fs # Default-Start: 2 3 4 5 # Default-Stop: S 0 1 6 # Short-Description: wfrog renderer # Description: wfrog - Weather Station Software ### END INIT INFO # Author: Laurent bovet # Do NOT "set -e" # PATH should only include /usr/* if it runs after the mountnfs.sh script PATH=/usr/sbin:/usr/bin:/sbin:/bin NAME=wfrender DESC="wfrog renderer - Weather Station Software " RENDERER_DIR=/usr/lib/wfrog/bin RENDERER=wfrog RENDERER_ARGS="--renderer -m" PIDFILE_RENDERER=/var/run/wfrender.pid SCRIPTNAME=/etc/init.d/wfrender USER=root # Exit if the package is not installed [ -x "$RENDERER_DIR/$RENDERER" ] || 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 [ -f /etc/default/rcS ] && . /etc/default/rcS # Define LSB log_* functions. # Depend on lsb-base (>= 3.0-6) to ensure that this file is present. . /lib/lsb/init-functions # # Function that starts the daemon/service # do_start_renderer() { # 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 --background --make-pidfile --pidfile $PIDFILE_RENDERER -d $RENDERER_DIR --exec $RENDERER_DIR/$RENDERER --test > /dev/null \ || return 1 start-stop-daemon --start --background --make-pidfile --pidfile $PIDFILE_RENDERER -d $RENDERER_DIR --exec $RENDERER_DIR/$RENDERER -- $RENDERER_ARGS \ || return 2 } # # Function that stops the daemon/service # do_stop_renderer() { # 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_RENDERER --name $RENDERER RETVAL="$?" [ "$RETVAL" = 2 ] && return 2 rm -f $PIDFILE return "$RETVAL" } case "$1" in start) [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" do_start_renderer 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_renderer case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; restart|force-reload) # # If the "reload" option is implemented then remove the # 'force-reload' alias # log_daemon_msg "Restarting $DESC" "$NAME" do_stop_renderer case "$?" in 0|1) do_start_renderer 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|restart|reload|force-reload}" >&2 echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2 exit 3 ;; esac : wfrog-0.8.2+svn953/wfcommon/000077500000000000000000000000001213472117100155345ustar00rootroot00000000000000wfrog-0.8.2+svn953/wfcommon/__init__.py000066400000000000000000000014521213472117100176470ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . wfrog-0.8.2+svn953/wfcommon/config.py000066400000000000000000000156561213472117100173700ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import logging import log import yaml import inspect import sys import os.path import copy from Cheetah.Template import Template wfrog_version = "0.8.2-svn" class Configurer(object): default_filename = None module_map = None log_configurer = log.LogConfigurer() logger = logging.getLogger('config') def __init__(self, module_map): self.module_map = module_map self.extensions = {} def add_options(self, opt_parser): opt_parser.add_option("-f", "--config", dest="config", help="Configuration file (in yaml)", metavar="CONFIG_FILE") opt_parser.add_option("-s", "--settings", dest="settings", help="Settings file (in yaml)", metavar="SETTINGS_FILE") opt_parser.add_option("-H", action="store_true", dest="help_list", help="Gives help on the configuration file and the list of possible config !elements in the yaml config file") opt_parser.add_option("-E", dest="help_element", metavar="ELEMENT", help="Gives help about a config !element") opt_parser.add_option("-e", "--extensions", dest="extension_names", metavar="MODULE1,MODULE2,...", help="Comma-separated list of modules containing custom configuration elements") self.log_configurer.add_options(opt_parser) def configure(self, options, component, config_file, settings_file=None, embedded=False): self.config_file = config_file self.settings_file = settings_file if options.extension_names: for ext in options.extension_names.split(","): self.logger.debug("Loading extension module '"+ext+"'") self.extensions[ext]=__import__(ext) if options.help_list: if component.__doc__ is not None: print component.__doc__ for (k,v) in self.module_map: print k print "-"*len(k) +"\n" self.print_help(v) if options.extension_names: print "Extensions" print "----------\n" for ext in self.extensions: print "[" + ext + "]" print self.print_help(self.extensions[ext]) # Adds logger documentation print self.log_configurer.__doc__ print " Use option -H ELEMENT for help on a particular !element" sys.exit() if options.help_element: element = options.help_element if element[0] is not '!': element = '!' + element desc = {} for(k,v) in self.module_map: desc.update(self.get_help_desc(v)) if len(desc) == 0: for ext in self.extensions: desc.update(self.get_help_desc(self.extensions[ext])) if desc.has_key(element): print print element + " [" + desc[element][1] +"]" print " " + desc[element][0] print else: print "Element "+element+" not found or not documented" sys.exit() if not embedded and options.config: self.config_file = options.config settings_warning=False if self.settings_file is None: if options.settings is not None: self.settings_file = options.settings else: settings_warning=True self.settings_file = os.path.dirname(self.config_file)+'/../../wfcommon/config/default-settings.yaml' settings = yaml.load( file(self.settings_file, 'r') ) variables = {} variables['settings']=settings config = yaml.load( str(Template(file=file(self.config_file, "r"), searchList=[variables]))) if settings is not None: context = copy.deepcopy(settings) else: context = {} context['_yaml_config_file'] = self.config_file context['os']=sys.platform if not embedded: self.log_configurer.configure(options, config, context) self.logger.info("Starting wfrog " + wfrog_version) if settings_warning: self.logger.warn('User settings are missing. Loading default ones. Run \'wfrog -S\' for user settings setup.') self.logger.info("Loaded settings file " + os.path.normpath(self.settings_file)) self.logger.debug('Loaded settings %s', repr(settings)) self.logger.debug("Loaded config file " + os.path.normpath(self.config_file)) if config.has_key('init'): for k,v in config['init'].iteritems(): self.logger.debug("Initializing "+k) try: v.init(context=context) except AttributeError: pass # In case the element has not init method return ( config, context ) def print_help(self, module): desc = self.get_help_desc(module, summary=True) sorted = desc.keys() sorted.sort() for k in sorted: print k print " " + desc[k][0] print def get_help_desc(self, module, summary=False): self.logger.debug("Getting info on module '"+module.__name__+"'") elements = inspect.getmembers(module, lambda l : inspect.isclass(l) and yaml.YAMLObject in inspect.getmro(l)) desc={} for element in elements: self.logger.debug("Getting doc of "+element[0]) # Gets the documentation of the first superclass superclass = inspect.getmro(element[1])[1] fulldoc=superclass.__doc__ # Add the doc of the super-super-class if _element_doc is if hasattr(inspect.getmro(superclass)[1], "_element_doc") and inspect.getmro(superclass)[1].__doc__ is not None: fulldoc = fulldoc + inspect.getmro(superclass)[1].__doc__ firstline=fulldoc.split(".")[0] self.logger.debug(firstline) module_name = module.__name__.split('.')[-1] if summary: desc[element[1].yaml_tag] = [ firstline, module_name ] else: desc[element[1].yaml_tag] = [ fulldoc, module_name ] return desc wfrog-0.8.2+svn953/wfcommon/config/000077500000000000000000000000001213472117100170015ustar00rootroot00000000000000wfrog-0.8.2+svn953/wfcommon/config/default-settings.yaml000066400000000000000000000001441213472117100231460ustar00rootroot00000000000000station: driver: auto altitude: 0 units: temp: C wind: km/h rain: mm press: hPa wfrog-0.8.2+svn953/wfcommon/config/fileloghandler.yaml000066400000000000000000000002321213472117100226410ustar00rootroot00000000000000handler: !!python/object/new:logging.handlers.RotatingFileHandler kwds: filename: ${filename} maxBytes: 262144 backupCount: 3 wfrog-0.8.2+svn953/wfcommon/config/loghandler.yaml000066400000000000000000000005161213472117100220060ustar00rootroot00000000000000handler: !user choices: root: !include path: ../../wfcommon/config/fileloghandler.yaml variables: filename: /var/log/${process}.log default: !include path: ../../wfcommon/config/fileloghandler.yaml variables: filename: ${process}.log wfrog-0.8.2+svn953/wfcommon/config/mailloghandler.yaml000066400000000000000000000005241213472117100226500ustar00rootroot00000000000000handler: !!python/object/new:wfcommon.maillog.mySMTPHandler kwds: mailhost: !!python/tuple [smtp.gmail.com, 587] fromaddr: myAccount@gmail.com toaddrs: [myAccount@gmail.com, myOtherAccount@gmail.com] subject: Critical wfrog message credentials: !!python/tuple [myAccount@gmail.com, myPassword] wfrog-0.8.2+svn953/wfcommon/config/settings-definition.yaml000066400000000000000000000013501213472117100236520ustar00rootroot00000000000000- name: altitude description: the altitude of your station (meters from sea level) type: number default: 0 - name: units description: Measurement units type: dict children: - name: temp description: the temperature unit type: choice choices: [ C, F] - name: wind description: the wind speed unit type: choice choices: [ km/h, mph, m/s, kt, bft ] - name: rain description: the absolute rain fall unit type: choice choices: [ mm, in ] - name: press description: the atmospheric pressure unit type: choice choices: [ hPa, mmHg, inHg ] wfrog-0.8.2+svn953/wfcommon/config/storage.yaml000066400000000000000000000016371213472117100213400ustar00rootroot00000000000000storage: !user choices: root: !csv path: /var/lib/wfrog/wfrog.csv default: !csv path: data/wfrog.csv ## you may use sqlite3 instead of csv , ## to this end delete the above lines and undelete below, ## install pysqlite2 and dependencies, ## and create the database and its tables ## using # sqlite3 /var/lib/wfrog/wfrog.sql < database/sqlite3.sql ## (in case, use the database/csv2sql script to transfer meteo data) #storage: !user # choices: # root: !sqlite3 # database: /var/lib/wfrog/wfrog.sql # default: !sqlite3 # database: data/wfrog.sql #storage: !firebird { database: 'localhost:/var/lib/firebird/2.0/data/wfrog.db', # user: sysdba, # password: masterkey } #storage: !mysql { database: wfrog, # host: localhost, # user: root, # password: root } wfrog-0.8.2+svn953/wfcommon/config/storagecopy.yaml000066400000000000000000000005551213472117100222310ustar00rootroot00000000000000from: !csv path: /tmp/wfrog.csv to: !mysql { database: wfrog, host: localhost, user: root, password: root } logging: level: info handlers: default: level: debug handler: !!python/object/new:logging.FileHandler kwds: filename: storagecopy.log wfrog-0.8.2+svn953/wfcommon/customize.py000066400000000000000000000034321213472117100201320ustar00rootroot00000000000000#!/usr/bin/python ## Copyright 2010 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import os.path import sys import shutil class Customizer(object): '''Prepares the user directory for customization. Does not override existing custom config.''' config_subdirs = [ '/config/', '/templates/' ] def customize(self, source_dir, target_dir, modules, output=sys.stdout): for module in modules: for subdir in self.config_subdirs: source_config_dir = source_dir+module+subdir target_config_dir = target_dir+module+subdir if os.path.exists(source_config_dir): if os.path.exists(target_config_dir): output.write('Config for '+module+' was already customized in '+target_config_dir+'. Skipping') else: output.write('Copying config of '+module+' for customization in '+target_config_dir+'.') shutil.copytree(source_config_dir, target_config_dir) output.write('\n') wfrog-0.8.2+svn953/wfcommon/database.py000066400000000000000000000204611213472117100176550ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of WFrog ## ## WFrog 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 . import datetime import decimal try: import kinterbasdb if __name__ == '__main__': print "firebird driver present" except ImportError: kinterbasdb = None if kinterbasdb: try: kinterbasdb.init(type_conv=199) # Set type conversion (datetime / floats) if __name__ == '__main__': print "firebird conversion 199" #kinterbasdb.init(type_conv=200) # Set type conversion (datetime / decimal) except: try: kinterbasdb.init(type_conv=0) # Set type conversion (time tuple / floats) old python 2.5 if __name__ == '__main__': print "firebird conversion 0" except: pass try: import MySQLdb if __name__ == '__main__': print "mysql driver present" except ImportError: MySQLdb = None # Converts time tuples (old Firebird format) to datetime # and Decimal into floats. # Working with Decimal numbers needs to be studied ... def adjust(obj): try: if isinstance(obj, tuple): if len(obj) == 7 or len(obj) == 6: return datetime.datetime(*obj) elif isinstance(obj, decimal.Decimal): return float(obj) else: return obj except: return obj class DB(): dbObject = None def __init__(self): raise Exception("Method cannot be called") def connect(self): raise Exception("Method cannot be called") def select(self, sql): if self.dbObject == None: raise Exception("Not connected to a Database") cursor = self.dbObject.cursor() cursor.execute(sql) try: while True: row = cursor.fetchone() if row is not None: yield tuple(map(adjust, row)) else: break finally: cursor.close() self.dbObject.commit() def execute(self, sql): if self.dbObject == None: raise Exception("Not connected to a Database") cursor = self.dbObject.cursor() cursor.execute(sql) cursor.close() self.dbObject.commit() def disconnect(self): try: self.dbObject.close() self.dbObject = None except: pass ## Firebird database driver class FirebirdDB(DB): def __init__(self, db, user='sysdba', password='masterkey', charset='ISO8859_1'): self.db = db self.user = user self.password = str(password) self.charset = charset def connect(self): if self.dbObject != None: raise Exception("Firebird: already connected to %s" % self.db) self.dbObject = kinterbasdb.connect(dsn=self.db, user=self.user, password=self.password, charset=self.charset) ## MySQL database driver class MySQLDB(DB): def __init__(self, db, host, port=3306, user='root', password='root'): self.host = host self.port = port self.db = db self.user = user self.password = str(password) def connect(self): if self.dbObject != None: raise Exception("MySQL: already connected to %s" % self.db) self.dbObject = MySQLdb.connect(host=self.host, port=self.port, user=self.user, passwd=self.password, db=self.db) ## sqlite3 database driver try: import sqlite3 if __name__ == '__main__': print "sqlite driver present" except ImportError: sqlite3 = None class Sqlite3(DB): def __init__(self, filename): self.filename = filename def connect(self): if self.dbObject != None: raise Exception("MySQL: already connected to %s" % self.filename) #http://stackoverflow.com/questions/1829872/read-datetime-back-from-sqlite-as-a-datetime-in-python self.dbObject = sqlite3.connect(self.filename, detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES) ## Database driver factory ## def DBFactory(configuration): """ Expects a python dictionary with the database configuration and returns its corresponding database object. Two types of database are available: 1) firebird {'type' : 'firebird', 'database' : 'localhost:/var/lib/firebird/2.0/data/wfrog.db', 'user' : 'sysdba', 'password' : 'masterkey'} 2) mysql {'type' : 'mysql', 'database' : 'wfrog', 'host' : 'localhost', 'port' : 3306, 'user' : 'root', 'password' : 'root'} """ if 'type' not in configuration: raise(Exception('DBFactory: database type not specified')) type = configuration['type'].lower() if type == 'firebird': if not kinterbasdb: raise(Exception("DBFactory: kinterbasdb (Firebirds python's database driver) is not installed")) if 'database' not in configuration: raise(Exception('DBFactory: Firebird database connection string not specified')) database = configuration['database'] if 'user' not in configuration: user = 'SYSDBA' else: user = configuration['user'] if 'password' not in configuration: password = 'masterkey' else: password = configuration['password'] if 'charset' not in configuration: charset = 'ISO8859_1' else: charset = configuration['charset'] return FirebirdDB(database, user, password, charset) elif type == 'mysql': if not MySQLdb: raise(Exception("DBFactory: MySQLdb (mysql python's database driver) is not installed")) if 'database' not in configuration: raise(Exception('DBFactory: MySql database name not specified')) database = configuration['database'] if 'host' not in configuration: raise(Exception('DBFactory: MySql database name not specified')) host = configuration['host'] if 'port' not in configuration: port = 3306 else: port = int(configuration['port']) if 'user' not in configuration: user = 'root' else: user = configuration['user'] if 'password' not in configuration: password = 'root' else: password = configuration['password'] return MySQLDB(database, host, port, user, password) else: raise(Exception('database type %s not supported' % configuration)) if __name__ == '__main__': if kinterbasdb: firebird_test = {'type' : 'firebird', 'database' : 'localhost:/var/lib/firebird/2.0/data/wfrog.db', 'user' : 'sysdba', 'password' : 'masterkey'} db = DBFactory(firebird_test) db.connect() print db.select("SELECT COUNT(*) FROM METEO") db.disconnect() else: print "kinterbasdb (Firebird python's driver) is not installed" if MySQLdb: mysql_test = {'type' : 'mysql', 'database' : 'wfrog', 'host' : 'localhost', 'port' : 3306, 'user' : 'root', 'password' : 'root'} db = DBFactory(mysql_test) db.connect() print db.select("SELECT COUNT(*) FROM METEO") db.disconnect() else: print "MySQLdb (mysql python's driver) is not installed" wfrog-0.8.2+svn953/wfcommon/dict.py000066400000000000000000000024511213472117100170330ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . def merge(dst, src): stack = [(dst, src)] while stack: current_dst, current_src = stack.pop() for key in current_src: if key not in current_dst: current_dst[key] = current_src[key] else: if isinstance(current_src[key], dict) and isinstance(current_dst[key], dict) : stack.append((current_dst[key], current_src[key])) else: current_dst[key] = current_src[key] return dst wfrog-0.8.2+svn953/wfcommon/formula/000077500000000000000000000000001213472117100172015ustar00rootroot00000000000000wfrog-0.8.2+svn953/wfcommon/formula/__init__.py000066400000000000000000000041041213472117100213110ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import yaml import base import wind import temp class YamlCountFormula(base.CountFormula, yaml.YAMLObject): yaml_tag = u'!count' class YamlAverageFormula(base.AverageFormula, yaml.YAMLObject): yaml_tag = u'!avg' class YamlLastFormula(base.LastFormula, yaml.YAMLObject): yaml_tag = u'!last' class YamlMinFormula(base.MinFormula, yaml.YAMLObject): yaml_tag = u'!min' class YamlMaxFormula(base.MaxFormula, yaml.YAMLObject): yaml_tag = u'!max' class YamlSumFormula(base.SumFormula, yaml.YAMLObject): yaml_tag = u'!sum' class YamlPredominantWindFormula(wind.PredominantWindFormula, yaml.YAMLObject): yaml_tag = u'!predominant' class YamlWindSectorAverageFormula(wind.WindSectorAverageFormula, yaml.YAMLObject): yaml_tag = u'!sector-avg' class YamlWindSectorMaxFormula(wind.WindSectorMaxFormula, yaml.YAMLObject): yaml_tag = u'!sector-max' class YamlWindSectorFrequencyFormula(wind.WindSectorFrequencyFormula, yaml.YAMLObject): yaml_tag = u'!sector-freq' class YamlHeatIndexMaxFormula(temp.HeatIndexMaxFormula, yaml.YAMLObject): yaml_tag = u'!heatindex' class YamlHumidexMaxFormula(temp.HumidexMaxFormula, yaml.YAMLObject): yaml_tag = u'!humidex' class YamlWindChillMinFormula(temp.WindChillMinFormula, yaml.YAMLObject): yaml_tag = u'!windchill' wfrog-0.8.2+svn953/wfcommon/formula/base.py000066400000000000000000000063021213472117100204660ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import sys class CountFormula(object): ''' Counts number of measures. ''' def __init__(self, index): self.index = index index = None count = 0 def append(self, sample): value = sample[self.index] if value is not None: self.count = self.count + 1 def value(self): return self.count class AverageFormula(object): ''' Average. ''' def __init__(self, index): self.index = index index = None sum = 0 count = 0 def append(self, sample): value = sample[self.index] if value is not None: self.sum = self.sum + value self.count = self.count + 1 def value(self): if self.count==0: return None else: return float(self.sum) / float(self.count) class LastFormula(object): ''' Keeps last sample value. ''' def __init__(self, index): self.index = index index = None last = None def append(self, sample): value = sample[self.index] if value is not None: self.last = value def value(self): return self.last class MinFormula(object): ''' Minimum. ''' def __init__(self, index): self.index = index index = None min = sys.maxint def append(self, sample): value = sample[self.index] if value is not None: self.min = min(self.min, value) def value(self): if self.min == sys.maxint: return None else: return self.min class MaxFormula(object): ''' Maximum. ''' def __init__(self, index): self.index = index index = None max = -sys.maxint def append(self, sample): value = sample[self.index] if value is not None: self.max = max(self.max, value) def value(self): if self.max == -sys.maxint: return None else: return self.max class SumFormula(object): ''' Sum. ''' def __init__(self, index): self.index = index index = None sum = 0 empty = True def append(self, sample): value = sample[self.index] if value is not None: self.empty = False self.sum = self.sum + value def value(self): if self.empty: return None else: return self.sum wfrog-0.8.2+svn953/wfcommon/formula/temp.py000066400000000000000000000057121213472117100205250ustar00rootroot00000000000000## Copyright 2010 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import sys from wfcommon import meteo class WindChillMinFormula(object): ''' Minimum WindChill temperature. Requires temperature and wind speed in Km/h. ''' def __init__(self, index): self.index = index index = None min_windchill = None def append(self, sample): value_temp = sample[self.index[0]] value_wind = sample[self.index[1]] if value_temp is not None and value_wind is not None : sample_windchill = meteo.WindChill(value_temp, meteo.msToKmh(value_wind)) if sample_windchill is not None: if self.min_windchill is None or self.min_windchill > sample_windchill: self.min_windchill = sample_windchill def value(self): return self.min_windchill class HeatIndexMaxFormula(object): ''' Maximum Heat Index temperature. Requires temperature and humidity. ''' def __init__(self, index): self.index = index index = None max_heatindex = None def append(self, sample): value_temp = sample[self.index[0]] value_hum = sample[self.index[1]] if value_temp is not None and value_hum is not None : sample_heatindex = meteo.HeatIndex(value_temp, value_hum) if sample_heatindex is not None: if self.max_heatindex is None or self.max_heatindex < sample_heatindex: self.max_heatindex = sample_heatindex def value(self): return self.max_heatindex class HumidexMaxFormula(object): ''' Maximum Humidex temperature. Requires temperature and humidity. ''' def __init__(self, index): self.index = index index = None max_humidex = None def append(self, sample): value_temp = sample[self.index[0]] value_hum = sample[self.index[1]] if value_temp is not None and value_hum is not None : sample_humidex = meteo.Humidex(value_temp, value_hum) if sample_humidex is not None: if self.max_humidex is None or self.max_humidex < sample_humidex: self.max_humidex = sample_humidex def value(self): return self.max_humidex wfrog-0.8.2+svn953/wfcommon/formula/wind.py000066400000000000000000000102651213472117100205200ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . from base import AverageFormula from wfcommon import meteo class PredominantWindFormula(object): ''' Returns the direction in degrees of predominant wind. ''' def __init__(self, index): self.index = index index = None sumX = 0 sumY = 0 count = 0 def append(self, sample): speed_index = self.index dir_index = self.index+1 speed = sample[speed_index] dir = sample[dir_index] if speed is not None and dir is not None: x = meteo.WindX(speed, dir) y = meteo.WindY(speed, dir) self.sumX = self.sumX + x self.sumY = self.sumY + y self.count = self.count + 1 def value(self): if self.count==0: return (None, None) else: avgX = self.sumX / self.count avgY = self.sumY / self.count dir = meteo.WindDir(avgX, avgY) return ( dir, meteo.WindDirTxt(dir) ) class WindSectorAverageFormula(object): ''' Returns wind average histogram per sector as a sequence. ''' def __init__(self, index): self.index = index index = None sums = None counts = [0]*16 def append(self, sample): if self.sums is None: self.sums = [0]*16 self.counts = [0]*16 speed_index = self.index dir_index = self.index+1 speed = sample[speed_index] dir = sample[dir_index] if speed is not None and speed > 0 and dir is not None: i = int(round(dir/22.5)) % 16 self.sums[i] = self.sums[i] + speed self.counts[i] = self.counts[i]+1 def value(self): averages = [0]*16 for i in range(16): if self.counts[i] > 0: averages[i] = float(self.sums[i]) / self.counts[i] return averages class WindSectorMaxFormula(object): ''' Returns wind maximum histogram per sector as a sequence. ''' def __init__(self, index): self.index = index index = None values = None def append(self, sample): if self.values is None: self.values = [0]*16 speed_index = self.index dir_index = self.index+1 speed = sample[speed_index] dir = sample[dir_index] if speed is not None and speed > 0 and dir is not None: i = int(round(dir/22.5)) % 16 if speed > self.values[i]: self.values[i] = speed def value(self): if self.values is not None: return self.values else: return [0]*16 class WindSectorFrequencyFormula(object): ''' Returns wind frequency histogram per sector as a sequence. ''' def __init__(self, index): self.index = index index = None sums = None count = 0 def append(self, sample): if self.sums is None: self.sums = [0]*16 self.count = 0 speed_index = self.index dir_index = self.index+1 speed = sample[speed_index] dir = sample[dir_index] if speed is not None and speed > 0 and dir is not None: i = int(round(dir/22.5)) % 16 self.sums[i] = self.sums[i] + 1.0 self.count = self.count + 1 def value(self): freqs = [0]*16 if self.count > 0: for i in range(16): freqs[i] = self.sums[i] / self.count return freqs wfrog-0.8.2+svn953/wfcommon/gendoc/000077500000000000000000000000001213472117100167735ustar00rootroot00000000000000wfrog-0.8.2+svn953/wfcommon/gendoc/gendoc.sh000077500000000000000000000024511213472117100205730ustar00rootroot00000000000000 #!/bin/sh WFROG_HOME=../../ version=$(grep wfrog_version $WFROG_HOME/wfcommon/config.py | cut -f2 -d'"') date=$(date +%F) footer="

wfrog $version - $date" DOC_DIR=doc mkdir -p $DOC_DIR function gen_module() { module=$1 echo "--- $module ---" mkdir -p $DOC_DIR/$module python $WFROG_HOME/$module/$module.py -H | python helpformat.py -H "wfrog > $module

Configuration of $module

" -F "$footer" > $DOC_DIR/$module/index.html for i in $(python $WFROG_HOME/$module/$module.py -H | grep "^\!.*" | cut -b2-); do echo $i; python $WFROG_HOME/$module/$module.py -E $i | \ python helpformat.py \ -H "wfrog > $module > "'!'"$i" -F "$footer" -t > $DOC_DIR/$module/$i.html; done } for i in wfdriver wflogger wfrender; do gen_module $i done cat << EOF > $DOC_DIR/index.html wfrog - configuration wfrog

Configuration of wfrog

$footer EOF wfrog-0.8.2+svn953/wfcommon/gendoc/helpformat.py000066400000000000000000000127471213472117100215210ustar00rootroot00000000000000import optparse import sys, StringIO import re TITLE1=1 TITLE2=2 TITLE3=3 SPACE=4 PROP=5 BULLET=6 HIDDEN=7 prop_re = re.compile('^ *[a-z]+.*\[.*\].*:$') hidden_re = re.compile('^ [^ ]+.*$') inline_element = re.compile('(![a-z-]*)') def parse_mark(line): if line.strip().startswith("---"): return TITLE2 elif line.strip() == '': return SPACE elif line.strip().startswith('[') and line.strip().endswith(']'): return TITLE3 elif prop_re.match(line): return PROP elif line.strip().startswith('- '): return BULLET elif hidden_re.match(line): return HIDDEN else: return 0 def format_text(line): formatted ='' parts = line.split("'") if len(parts) > 1: open = True count = len(parts) for part in parts: formatted = formatted + part count = count - 1 if count > 0: if open: formatted = formatted + '' else: formatted = formatted + '' open = not open line = formatted tokens = inline_element.split(line) if len(tokens) > 1: for i in range(0, len(tokens)-1): if inline_element.match(tokens[i]): tokens[i] = ''+tokens[i]+'' line = ''.join(tokens) return line def format(line, context={}): if parse_mark(line) == TITLE3: token = line.split('[')[1].strip().split(']')[0] return token elif line.strip().startswith('!') and not context['title-line']: token = line.strip()[1:] return ''+line.strip()+'' elif line.strip().startswith('>'): return '' elif parse_mark(line) == PROP: if line.find('(optional)') > 0: result = ''+line.replace('[', '[').replace('(optional)', '') else: result = line result = result.strip()[:-1] result = result.replace('[', '[') result = result.replace(']', ']') return '' + result + '
' elif parse_mark(line) == BULLET: if line.find(':') > 0: line = line.replace(' - ', ' - ') line = line.replace(':', ':') return '
  • '+format_text(line.replace(' - ', '')) elif parse_mark(line) == HIDDEN: return '' else: return format_text(line) def to_content(buffer, line, context): mark = parse_mark(line) if mark == TITLE2: if False and context.has_key('start_title2'): #disabled prefix = '
    ' else: prefix='' context['start_title2']=True return (buffer, prefix+'

    ', '

    ', True) if mark == TITLE3: buffer.write(format(line)) return (buffer, '

    ', '

    ', True) if mark == SPACE: if hasattr(buffer, 'bullet'): buffer.write('') return (buffer, '', '', True) else: if not buffer.getvalue().strip() == '': return (buffer, '', '

    ', True) else: return (buffer, '', '', True) if mark == BULLET: if not hasattr(buffer, 'bullet'): buffer.write('
      ') buffer.bullet = True buffer.write(format(line)) return (buffer, '', '', False) else: if context['title-line']: buffer.write('

      ') if line.strip().find('[') > 0: line = line.replace('[', '

      [') buffer.write(line) buffer.write('

      ') else: buffer.write(line) buffer.write('') context['title-line'] = False return (buffer, '', '', True) else: buffer.write(format(line, context)) return (buffer, '', '', False) def treat_line(buffer, line, output, context): if buffer == None: buffer = StringIO.StringIO() ( buffer, prefix, suffix, end ) = to_content(buffer, line, context) if end: output.write(prefix+buffer.getvalue()+suffix) output.write('\n') buffer = None return buffer def write_header(output): output.write('wfrog - configuration') def write_footer(output): output.write('') # TODO: footer # Lines to hide: space instead of ' > ' def process(input, output, header='', footer='', first_line_title=False): line = 1 buffer = None write_header(output) output.write(header) context = { 'title-line' : first_line_title } while line: line = input.readline() buffer = treat_line(buffer, line, output, context) treat_line(buffer, '', output, context) output.write(footer) write_footer(output) def main(): opt_parser = optparse.OptionParser() opt_parser.add_option("-H", "--header", dest="header", default='', help="Header on generated page", metavar="HTML") opt_parser.add_option("-F", "--footer", dest="footer", default='', help="Footer on generated page", metavar="HTML") opt_parser.add_option("-t", "--first-line-title", action="store_true", dest="first_line_title", help="Makes the first non empty line a main title.") (options, args) = opt_parser.parse_args() process(sys.stdin, sys.stdout, header=options.header, footer=options.footer, first_line_title=options.first_line_title) if __name__ == "__main__": main() wfrog-0.8.2+svn953/wfcommon/generic/000077500000000000000000000000001213472117100171505ustar00rootroot00000000000000wfrog-0.8.2+svn953/wfcommon/generic/__init__.py000066400000000000000000000025541213472117100212670ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import yaml import include import multi import service import stopwatch import user # YAML mappings class YamlIncludeElement(include.IncludeElement, yaml.YAMLObject): yaml_tag = u'!include' class YamlMultiElement(multi.MultiElement, yaml.YAMLObject): yaml_tag = u'!multi' class YamlServiceElement(service.ServiceElement, yaml.YAMLObject): yaml_tag = u'!service' class YamlUserChoiceElement(user.UserChoiceElement, yaml.YAMLObject): yaml_tag = u'!user' class YamlStopWatchElement(stopwatch.StopWatchElement, yaml.YAMLObject): yaml_tag = u'!stopwatch' wfrog-0.8.2+svn953/wfcommon/generic/include.py000066400000000000000000000047171213472117100211560ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import yaml import logging import wrapper import copy from os import path from Cheetah.Template import Template class IncludeElement(wrapper.ElementWrapper): """ Includes another yaml configuration file. The included file must define only one root element. [ Properties ] path [string]: A path to the file to include. Relative to the main config file. """ path = None target = None variables = None abs_path = None logger = logging.getLogger("generic.include") def _init(self, context=None): if not self.target: if context: config_file = context['_yaml_config_file'] else: raise Exception('Context not passed to !include element') dir_name = path.dirname(config_file) self.abs_path=path.join(dir_name, self.path) if not self.variables: self.variables={} if context: self.variables['settings']=context conf_str = str(Template(file=file(self.abs_path, "r"), searchList=[self.variables])) config = yaml.load(conf_str) self.target = config.values()[0] return self.target def _call(self, attr, *args, **keywords): if keywords.has_key('context'): self._init(keywords['context']) context = copy.copy(keywords['context']) context['_yaml_config_file'] = self.abs_path keywords['context'] = context else: self._init() self.logger.debug('Calling '+attr+' on ' + str(self.target)) return self.target.__getattribute__(attr).__call__(*args, **keywords) wfrog-0.8.2+svn953/wfcommon/generic/multi.py000066400000000000000000000055241213472117100206620ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import logging import wrapper import time from threading import Thread class MultiElement(wrapper.ElementWrapper): """ Wraps a list of children elements and delegates the method calls to them. The result of a method call is a dictionary containing the result of call to the method on each child indexed by its name. [ Properties ] children [dict]: A dictionary in which keys are names and values are the children objects the method calls are delegated to. parallel [true|false] (optional): True if the children must be called in parallel i.e. each in a separate thread. Useful when using blocking childrens like schedulers or other active components. When true, this element returns nothing, it just launches the threads for children method calls and returns. """ children={} threads = [] parallel = False logger = logging.getLogger('generic.multi') def _call(self, attr, *args, **keywords): result = {} for name, r in self.children.iteritems(): self.logger.debug("Calling "+attr+" on child "+name) if self.parallel: thread = Thread( target=r.__getattribute__(attr).__call__, args=args, kwargs=keywords) self.threads.append(thread) thread.start() else: result[name] = r.__getattribute__(attr).__call__(*args, **keywords) if self.parallel: try: while True: time.sleep(2) except KeyboardInterrupt: self.logger.debug("^C received, closing childrens") self.close() raise for thread in self.threads: self.logger.debug("Main thread waiting for thread "+str(thread)+" to finish") thread.join() else: return result def close(self): for name, r in self.children.iteritems(): try: r.close() except: pass wfrog-0.8.2+svn953/wfcommon/generic/service.py000066400000000000000000000042311213472117100211620ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import logging import wrapper # The global service registry services = {} class ServiceElement(wrapper.ElementWrapper): """ Provides a dictionary of objects global to the python process. Objects are registered under a name. Method calls to this element are forwarded to the registered object if an instance has already been registered. The call does nothing, otherwise. Calls never fail. [ Properties ] name [string]: Name under which an object is registered. instance [object] (optional): Object to register as a service. """ name = None instance = None logger = logging.getLogger("generic.service") def _call(self, attr, *args, **keywords): assert self.name is not None, "'service.name' must be set" global services if self.instance: if not services.__contains__(self.name): self.logger.debug('Registering service '+str(self.instance)+" under '" + self.name +"'") services[self.name] = self.instance if services.__contains__(self.name): self.logger.debug('Calling '+attr+' on ' + str(services[self.name])) return services[self.name].__getattribute__(attr).__call__(*args, **keywords) else: self.logger.debug("No service registered under '" + self.name +"'. Ignoring call to '" +attr+"'") wfrog-0.8.2+svn953/wfcommon/generic/stopwatch.py000066400000000000000000000042331213472117100215400ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import wrapper import logging import time class StopWatchElement(wrapper.ElementWrapper): ''' Element wrapper measuring duration of calls to the wrapped object. The measures in seconds are output using the log system in level INFO. [ Properties ] target [object]: The wrapped object. ''' measures = None logger = logging.getLogger("generic.stopwatch") target = None def _call(self, attr, *args, **keywords): if self.measures is None: self.measures = {} start = time.clock() result = self.target.__getattribute__(attr).__call__(*args, **keywords) duration = time.clock() - start if self.measures.has_key(attr): measure = self.measures.get(attr) measure = (duration, measure[1]+duration, measure[2]+1) else: measure = (duration, duration, 1) self.measures[attr] = measure obj_id = '{%x}' % id(self.target) if hasattr(self.target, "yaml_tag"): name = self.target.yaml_tag + obj_id elif hasattr(self.target, "__class__"): name = self.target.__class__.__name__ + obj_id else: name = str(self.target) self.logger.info(name+"."+attr+": call="+str(measure[0])+", sum="+str(measure[1])+", count="+str(measure[2])) return result wfrog-0.8.2+svn953/wfcommon/generic/user.py000066400000000000000000000040161213472117100205010ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import logging import wrapper import os import copy import getpass class UserChoiceElement(wrapper.ElementWrapper): """ Wrap an element by picking it from a dictionary according to the current user, owner of the process. [ Properties ] choices [dict]: The candidate elements keyed by username. The key 'default' is chosen if none matches. """ choices = None logger = logging.getLogger("generic.user") target = None def _init(self, context=None): if not self.target: user = getpass.getuser() if not self.choices.has_key(user): user = 'default' self.logger.debug('Current user:'+user) self.target = self.choices[user] return self.target def _call(self, attr, *args, **keywords): if keywords.has_key('context'): self._init(keywords['context']) context = copy.copy(keywords['context']) context['_yaml_config_file'] = self.abs_path keywords['context'] = context else: self._init() self.logger.debug('Calling '+attr+' on ' + str(self.target)) return self.target.__getattribute__(attr).__call__(*args, **keywords) wfrog-0.8.2+svn953/wfcommon/generic/wrapper.py000066400000000000000000000023651213472117100212100ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import yaml import logging from os import path from functools import partial from Cheetah.Template import Template class ElementWrapper(object): def __getattribute__(self, attr): try: return object.__getattribute__(self, attr) except AttributeError: if attr.startswith('__'): raise AttributeError() else: return partial(object.__getattribute__(self, '_call'), attr) wfrog-0.8.2+svn953/wfcommon/log.py000066400000000000000000000103121213472117100166640ustar00rootroot00000000000000#!/usr/bin/python ## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import optparse import logging import logging.handlers levels = {'debug': logging.DEBUG, 'info': logging.INFO, 'warning': logging.WARNING, 'error': logging.ERROR, 'critical': logging.CRITICAL} class LogConfigurer(object): '''Logging Configuration --------------------- level [debug|info|error|critical]: The root level of logging handlers [dict]: Dictionary configuring handlers. Keys are free names, values are: - handler: A python loghandler object, the actual log destination. - level: Optional log level for this handler. ''' def add_options(self, opt_parser): opt_parser.add_option("-d", "--debug", action="store_true", dest="debug", help="Issues all debug messages on the console.") opt_parser.add_option("-v", "--verbose", action="store_true", dest="verbose", help="Issues errors on the console.") def configure(self, options, config, context): formatter = logging.Formatter("%(asctime)s %(levelname)s [%(name)s] %(message)s") logger = logging.getLogger() # root logger level = logging.INFO if config.has_key('logging'): logging_config = config['logging'] if logging_config.has_key('level'): level = levels[logging_config['level']] if logging_config.has_key('format'): formatter = logging.Formatter(logging_config['format']) if logging_config.has_key('handlers'): handlers_config = logging_config['handlers'] for handler_config in handlers_config.values(): handler = handler_config['handler'] # Hack to bypass !include element because log handlers are not class instances # (they don't provide __getattribute__ # do it recursively if hasattr(handler, '_init'): while hasattr(handler, '_init'): handler = handler._init(context) if handler_config.has_key('level'): handler.setLevel(levels[handler_config['level']]) handler.setFormatter(formatter) logger.addHandler(handler) # If no handler is specified, by default a RotatingFileHandler with a # {$process.log} filename (see issue 85) else: filename = logging_config['filename'] if logging_config.has_key('filename') else 'wfrog' # Bypass !user elements if hasattr(filename, '_init'): while hasattr(filename, '_init'): filename = filename._init(context) handler = logging.handlers.RotatingFileHandler(filename = filename, maxBytes = 262144, backupCount = 3) handler.setFormatter(formatter) logger.addHandler(handler) if options.debug or options.verbose: if options.debug: level=logging.DEBUG else: level=logging.ERROR console_handler = logging.StreamHandler() console_handler.setFormatter(formatter) console_handler.setFormatter(formatter) logger.addHandler(console_handler) logger.setLevel(level) wfrog-0.8.2+svn953/wfcommon/maillog.py000066400000000000000000000073361213472117100175430ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of WFrog ## ## WFrog 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 . import smtplib import logging import logging.handlers import string import types class mySMTPHandler(logging.handlers.SMTPHandler): """ A customized handler class which sends an SMTP email for each logging event, and supports TLS smtp servers, like gmail """ def __init__(self, mailhost, fromaddr, toaddrs, subject, credentials=None): """ Initialize the handler. Initialize the instance with the from and to addresses and subject line of the email. To specify a non-standard SMTP port, use the (host, port) tuple format for the mailhost argument. To specify authentication credentials, supply a (username, password) tuple for the credentials argument. If TLS is required (gmail) it will be activated automatically """ logging.handlers.SMTPHandler.__init__(self, mailhost, fromaddr, toaddrs, subject) # Python 2.5 SMTPHAndler object does not admit login credentials if type(credentials) == types.TupleType: self.username, self.password = credentials else: self.username = None def emit(self, record): """ Emit a record. Format the record and send it to the specified addressees. """ try: try: from email.utils import formatdate except ImportError: formatdate = self.date_time port = self.mailport if not port: port = smtplib.SMTP_PORT smtp = smtplib.SMTP(self.mailhost, port) msg = self.format(record) msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\nDate: %s\r\n\r\n%s" % ( self.fromaddr, string.join(self.toaddrs, ","), self.getSubject(record), formatdate(), msg) smtp.ehlo() if smtp.has_extn('STARTTLS'): smtp.starttls() if self.username: smtp.login(self.username, self.password) smtp.sendmail(self.fromaddr, self.toaddrs, msg) smtp.quit() except (KeyboardInterrupt, SystemExit): raise except: self.handleError(record) # Test mySMTPHandler if __name__ == '__main__': MY_ACCOUNT = 'myAccount@gmail.com' MY_PASSWD = 'myPassword' handler = mySMTPHandler(('smtp.gmail.com', 587), MY_ACCOUNT, [MY_ACCOUNT], 'Testing wfrog critical mail messages', (MY_ACCOUNT, MY_PASSWD)) logger = logging.getLogger('wfrog_test') handler.setLevel(logging.CRITICAL) formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") handler.setFormatter(formatter) logger.addHandler(handler) print "Testing critical e-mail" logger.critical('This is a test ... you should be reading this inside a new email') wfrog-0.8.2+svn953/wfcommon/meteo.py000066400000000000000000000505031213472117100172220ustar00rootroot00000000000000## Copyright 2009 Jordi Puigsegur ## Laurent Bovet ## ## This file is part of wfrog ## ## wfrog 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 . ## TODO: Test exhaustively all functions from WxUtils. ## So far only the ones used by WFROG have been tested. import math ########################################################################################### ## 1) This first part of this file is a translation to Python of ## uWxUtils (http://www.softwx.com/weather/uwxutils.html) ########################################################################################### ## ---------------------------------------------------------------------------------------- ## This source code may be freely used, including for commercial purposes ## Steve Hatchett, SoftWx, Inc. ## http://www.softwx.com/ ## ## ## This file contains functions for performing various weather related calculations. ## ## Notes about pressure ## Sensor Pressure raw pressure indicated by the barometer instrument ## Station Pressure Sensor Pressure adjusted for any difference between sensor elevation and official station elevation ## Field Pressure (QFE) Usually the same as Station Pressure ## Altimeter Setting (QNH) Station Pressure adjusted for elevation (assumes standard atmosphere) ## Sea Level Pressure (QFF) Station Pressure adjusted for elevation, temperature and humidity ## ## Notes about input parameters: ## currentTemp - current instantaneous station temperature ## meanTemp - average of current temp and the temperature 12 hours in ## the past. If the 12 hour temp is not known, simply pass ## the same value as currentTemp for the mean temp. ## humidity - Value should be 0 to 100. For the pressure conversion ## functions, pass a value of zero if you do not want to ## the algorithm to include the humidity correction factor ## in the calculation. If you provide a humidity value ## > 0, then humidity effect will be included in the ## calculation. ## elevation - This should be the geometric altitude of the station ## (this is the elevation provided by surveys and normally ## used by people when they speak of elevation). Some ## algorithms will convert the elevation internally into ## a geopotential altitude. ## sensorElevation - This should be the geometric altitude of the actual ## barometric sensor (which could be different than the ## official station elevation). ## ## Notes about Sensor Pressure vs. Station Pressure: ## SensorToStationPressure and StationToSensorPressure functions are based ## on an ASOS algorithm. It corrects for a difference in elevation between ## the official station location and the location of the barometetric sensor. ## It turns out that if the elevation difference is under 30 ft, then the ## algorithm will give the same result (a 0 to .01 inHg adjustment) regardless ## of temperature. In that case, the difference can be covered using a simple ## fixed offset. If the difference is 30 ft or greater, there is some effect ## from temperature, though it is small. For example, at a 100ft difference, ## the adjustment will be .13 inHg at -30F and .10 at 100F. The bottom line ## is that while ASOS stations may do this calculation, it is likely unneeded ## for home weather stations, and the station pressure and the sensor pressure ## can be treated as equivalent. ## Formulas / Algorithms ## Sea Level Pressure reduction algorithms TSLPAlgorithm = [ 'paDavisVP', ## algorithm closely approximates SLP calculation used inside Davis Vantage Pro weather equipment console (http:##www.davisnet.com/weather/) 'paUnivie', ## http:##www.univie.ac.at/IMG-Wien/daquamap/Parametergencom.html 'paManBar' ## from Manual of Barometry (1963) ] ## Altimeter algorithms TAltimeterAlgorithm = [ 'aaASOS', ## formula described in the ASOS training docs 'aaASOS2', ## metric formula that was likely used to derive the aaASOS formula 'aaMADIS', ## apparently the formula used by the MADIS system 'aaNOAA', ## essentially the same as aaSMT with any result differences caused by unit conversion rounding error and geometric vs. geopotential elevation 'aaWOB', ## Weather Observation Handbook (algorithm similar to aaASOS & aaASOS2 - main differences being precision of constants used) 'aaSMT' ## Smithsonian Meteorological Tables (1963) ] TVapAlgorithm = [ 'vaDavisVp', ## algorithm closely approximates calculation used by Davis Vantage Pro weather stations and software 'vaBuck', ## this and the remaining algorithms described at http:##cires.colorado.edu/~voemel/vp.html 'vaBuck81', 'vaBolton', 'vaTetenNWS', 'vaTetenMurray', 'vaTeten'] from math import exp, pow, log DefaultSLPAlgorithm = 'paManBar'; DefaultAltimeterAlgorithm = 'aaMADIS'; DefaultVapAlgorithm = 'vaBolton'; ## U.S. Standard Atmosphere (1976) constants gravity = 9.80665 ## g at sea level at latitude 45.5 degrees in m/sec^2 uGC = 8.31432 ## universal gas constant in J/mole-K moleAir = 0.0289644 ## mean molecular mass of air in kg/mole moleWater = 0.01801528 ## molecular weight of water in kg/mole gasConstantAir = uGC/moleAir ## (287.053) gas constant for air in J/kgK standardSLP = 1013.25 ## standard sea level pressure in hPa standardSlpInHg = 29.921 ## standard sea level pressure in inHg standardTempK = 288.15 ## standard sea level temperature in Kelvin earthRadius45 = 6356.766 ## radius of the earth at latitude 45.5 degrees in km standardLapseRate = 0.0065 ## standard lapse rate (6.5C/1000m i.e. 6.5K/1000m) standardLapseRateFt = standardLapseRate * 0.3048 ## (0.0019812) standard lapse rate per foot (1.98C/1000ft) vpLapseRateUS = 0.00275 ## lapse rate used by Davis VantagePro (2.75F/1000ft) manBarLapseRate = 0.0117 ## lapse rate from Manual of Barometry (11.7F/1000m, which = 6.5C/1000m) def StationToSensorPressure(pressureHPa, sensorElevationM, stationElevationM, currentTempC): ## from ASOS formula specified in US units return InToHPa(HPaToIn(pressureHPa) / pow(10, (0.00813 * MToFt(sensorElevationM - stationElevationM) / FToR(CToF(currentTempC))))) def StationToAltimeter(PressureHPa, elevationM, algorithm = DefaultAltimeterAlgorithm): if algorithm == 'aaASOS': ## see ASOS training at http:##www.nwstc.noaa.gov ## see also http:##wahiduddin.net/calc/density_altitude.htm return InToHPa(Power(Power(HPaToIn(pressureHPa), 0.1903) + (1.313E-5 * MToFt(elevationM)), 5.255)) elif algorithm == 'aaASOS2': geopEl = GeopotentialAltitude(elevationM) k1 = standardLapseRate * gasConstantAir / gravity ## approx. 0.190263 k2 = 8.41728638E-5 ## (standardLapseRate / standardTempK) * (Power(standardSLP, k1) return Power(Power(pressureHPa, k1) + (k2 * geopEl), 1/k1) elif algorithm == 'aaMADIS': ## from MADIS API by NOAA Forecast Systems Lab, see http://madis.noaa.gov/madis_api.html k1 = 0.190284; ## discrepency with calculated k1 probably because Smithsonian used less precise gas constant and gravity values k2 = 8.4184960528E-5; ## (standardLapseRate / standardTempK) * (Power(standardSLP, k1) return Power(Power(pressureHPa - 0.3, k1) + (k2 * elevationM), 1/k1) elif algorithm == 'aaNOAA': ## see http://www.srh.noaa.gov/elp/wxcalc/formulas/altimeterSetting.html k1 = 0.190284 ## discrepency with k1 probably because Smithsonian used less precise gas constant and gravity values k2 = 8.42288069E-5 ## (standardLapseRate / 288) * (Power(standardSLP, k1SMT); return (pressureHPa - 0.3) * Power(1 + (k2 * (elevationM / Power(pressureHPa - 0.3, k1))), 1/k1) elif algorithm == 'aaWOB': ## see http://www.wxqa.com/archive/obsman.pdf k1 = standardLapseRate * gasConstantAir / gravity ## approx. 0.190263 k2 = 1.312603E-5 ##(standardLapseRateFt / standardTempK) * Power(standardSlpInHg, k1); return InToHPa(Power(Power(HPaToIn(pressureHPa), k1) + (k2 * MToFt(elevationM)), 1/k1)) elif algorithm == 'aaSMT': ## see WMO Instruments and Observing Methods Report No.19 at http://www.wmo.int/pages/prog/www/IMOP/publications/IOM-19-Synoptic-AWS.pdf k1 = 0.190284; ## discrepency with calculated value probably because Smithsonian used less precise gas constant and gravity values k2 = 4.30899E-5; ## (standardLapseRate / 288) * (Power(standardSlpInHg, k1SMT)); geopEl = GeopotentialAltitude(elevationM) return InToHPa((HPaToIn(pressureHPa) - 0.01) * Power(1 + (k2 * (geopEl / Power(HPaToIn(pressureHPa) - 0.01, k1))), 1/k1)); else: raise Exception('unknown algorithm') def StationToSeaLevelPressure(pressureHPa, elevationM, currentTempC, meanTempC, humidity, algorithm = DefaultSLPAlgorithm): return pressureHPa * PressureReductionRatio(pressureHPa, elevationM, currentTempC, meanTempC, humidity, algorithm) def SensorToStationPressure(pressureHPa, sensorElevationM, stationElevationM, currentTempC): ## see ASOS training at http://www.nwstc.noaa.gov ## from US units ASOS formula return InToHPa(HPaToIn(pressureHPa) * (10 * (0.00813 * MToFt(sensorElevationM - stationElevationM)/ FToR(CToF(currentTempC))))) def SeaLevelToStationPressure(pressureHPa, elevationM, currentTempC, meanTempC, humidity, algorithm = DefaultSLPAlgorithm): return pressureHPa / PressureReductionRatio(pressureHPa, elevationM, currentTempC, meanTempC, humidity, algorithm); def PressureReductionRatio(pressureHPa, elevationM, currentTempC, meanTempC, humidity, algorithm = DefaultSLPAlgorithm): if algorithm == 'paUnivie': ## see http://www.univie.ac.at/IMG-Wien/daquamap/Parametergencom.html geopElevationM = GeopotentialAltitude(elevationM) return exp(((gravity/gasConstantAir) * geopElevationM) / (VirtualTempK(pressureHPa, meanTempC, humidity) + (geopElevationM * standardLapseRate/2))) elif algorithm == 'paDavisVP': ## see http://www.exploratorium.edu/weather/barometer.html if (humidity > 0): hcorr = (9/5) * HumidityCorrection(currentTempC, elevationM, humidity, 'vaDavisVP') else: hcorr = 0 ## in the case of davisvp, take the constant values literally. return pow(10, (MToFt(elevationM) / (122.8943111 * (CToF(meanTempC) + 460 + (MToFt(elevationM) * vpLapseRateUS/2) + hcorr)))) elif algorithm == 'paManBar': ## see WMO Instruments and Observing Methods Report No.19 at http://www.wmo.int/pages/prog/www/IMOP/publications/IOM-19-Synoptic-AWS.pdf if (humidity > 0): hCorr = (9/5) * HumidityCorrection(currentTempC, elevationM, humidity, 'vaBuck') else: hCorr = 0 geopElevationM = GeopotentialAltitude(elevationM); return exp(geopElevationM * 6.1454E-2 / (CToF(meanTempC) + 459.7 + (geopElevationM * manBarLapseRate / 2) + hCorr)) else: raise Exception('Unknown algorithm') def ActualVaporPressure(tempC, humidity, algorithm = DefaultVapAlgorithm): return (humidity * SaturationVaporPressure(tempC, algorithm)) / 100 def SaturationVaporPressure(tempC, algorithm = DefaultVapAlgorithm): ## see http://cires.colorado.edu/~voemel/vp.html comparison of vapor pressure algorithms ## see (for DavisVP) http://www.exploratorium.edu/weather/dewpoint.html if algorithm == 'vaDavisVP': return 6.112 * exp((17.62 * tempC)/(243.12 + tempC)) ## Davis Calculations Doc elif algorithm == 'vaBuck': return 6.1121 * exp((18.678 - (tempC/234.5)) * tempC / (257.14 + tempC)) ## Buck(1996) elif algorithm == 'vaBuck81': return 6.1121 * exp((17.502 * tempC)/(240.97 + tempC)) ## Buck(1981) elif algorithm == 'vaBolton': return 6.112 * exp(17.67 * tempC / (tempC + 243.5)) ## Bolton(1980) elif algorithm == 'vaTetenNWS': return 6.112 * pow(10,(7.5 * tempC / (tempC + 237.7))) ## Magnus Teten see www.srh.weather.gov/elp/wxcalc/formulas/vaporPressure.html elif algorithm == 'vaTetenMurray': return 10 *+ ((7.5 * tempC / (237.5 + tempC)) + 0.7858) ## Magnus Teten (Murray 1967) elif algorithm == 'vaTeten': return 6.1078 * pow(10, (7.5 * tempC / (tempC + 237.3))) ## Magnus Teten see www.vivoscuola.it/US/RSIGPP3202/umidita/attivita/relhumONA.htm else: raise Exception('Unknown algorithm') def MixingRatio(pressureHPa, tempC, humidity): ## see http://www.wxqa.com/archive/obsman.pdf ## see also http://www.vivoscuola.it/US/RSIGPP3202/umidita/attivita/relhumONA.htm k1 = moleWater / moleAir; ## 0.62198 vapPres = ActualVaporPressure(tempC, humidity, vaBuck) return 1000 * ((k1 * vapPres) / (pressureHPa - vapPres)) def VirtualTempK(pressureHPa, tempC, humidity): ## see http://www.univie.ac.at/IMG-Wien/daquamap/Parametergencom.html ## see also http://www.vivoscuola.it/US/RSIGPP3202/umidita/attivita/relhumONA.htm ## see also http://wahiduddin.net/calc/density_altitude.htm epsilon = 1 - (moleWater / moleAir) ## 0.37802 vapPres = ActualVaporPressure(tempC, humidity, vaBuck) return (CtoK(tempC)) / (1-(epsilon * (vapPres/pressureHPa))) def HumidityCorrection(tempC, elevationM, humidity, algorithm = DefaultVapAlgorithm): vapPress = ActualVaporPressure(tempC, humidity, algorithm) return (vapPress * ((2.8322E-9 * (elevationM ** 2)) + (2.225E-5 * elevationM) + 0.10743)) def DewPoint(tempC, humidity, algorithm = DefaultVapAlgorithm): LnVapor = log(ActualVaporPressure(tempC, humidity, algorithm)) if algorithm == 'vaDavisVP': return ((243.12 * LnVapor) - 440.1) / (19.43 - LnVapor) else: return ((237.7 * LnVapor) - 430.22) / (19.08 - LnVapor) def WindChill(tempC, windSpeedKmph): """ Wind Chill Params: - tempC - windSpeedKmph Wind Chill algorithm is only valid for temperatures below 10C and wind speeds above 4,8 Km/h. Outside this range a None value is returned. see American Meteorological Society Journal see http://www.msc.ec.gc.ca/education/windchill/science_equations_e.cfm see http://www.weather.gov/os/windchill/index.shtml """ if ((tempC >= 10.0) or (windSpeedKmph <= 4.8)): return None else: windPow = pow(windSpeedKmph, 0.16) Result = min([tempC, 13.12 + (0.6215 * tempC) - (11.37 * windPow) + (0.3965 * tempC * windPow)]) return Result if Result < tempC else tempC def HeatIndex(tempC, humidity): """ Heat Index Params: - tempC - humidity Heat index algorithm is only valid for temps above 26.7C / 80F. Outside this range a None value is returned. see http://www.hpc.ncep.noaa.gov/heat_index/hi_equation.html """ tempF = CToF(tempC) if (tempF < 80): return None else: tSqrd = tempF **2 hSqrd = humidity **2 Result = (0.0 - 42.379 + (2.04901523 * tempF) + (10.14333127 * humidity) - (0.22475541 * tempF * humidity) - (0.00683783 * tSqrd) - (0.05481717 * hSqrd) + (0.00122874 * tSqrd * humidity) + (0.00085282 * tempF * hSqrd) - (0.00000199 * tSqrd * hSqrd)) ## Rothfusz adjustments if ((humidity < 13) and (tempF >= 80) and (tempF <= 112)): Result -= ((13.0 - humidity)/4.0) * Sqrt((17.0 - Abs(tempf - 95.0))/17.0) elif ((humidity > 85) and (tempF >= 80) and (tempF <= 87)): Result += ((humidity - 85.0)/10.0) * ((87.0 - tempF)/5.0) return FToC(Result) if Result > tempF else tempC def Humidex(tempC, humidity): if tempC <= 20.0: return None else: humidex = tempC + ((5.0/9.0) * (ActualVaporPressure(tempC, humidity, 'vaTetenNWS') - 10.0)) if humidex > tempC: return humidex else: return None def GeopotentialAltitude(geometricAltitudeM): return (earthRadius45 * 1000 * geometricAltitudeM) / ((earthRadius45 * 1000) + geometricAltitudeM) ########################################################################################### ## 2) Unit conversion functions, translated to Python from ## uWxUtils (http://www.softwx.com/weather/uwxutils.html) ########################################################################################### def FToC(value): return (((value * 1.0) - 32.0) * 5.0) / 9.0 def CToF(value): return ((value * 9.0) / 5.0) + 32.0 def CToK(value): return 273.15 + value def KToC(value): return value - 273.15 def FToR(value): return value + 459.67 def RToF(value): return value - 459.67 def InToHPa(value): return value / 0.02953 def HPaToIn(value): return value * 0.02953 def FtToM(value): return value * 0.3048 def MToFt(value): return value / 0.3048 def InToMm(value): return value * 25.4 def MmToIn(value): return value / 25.4 def MToKm(value): # Miles ! return value * 1.609344 def KmToM(value): # Miles ! return value / 1.609344 def msToKmh(value): return value * 3.6 ########################################################################################### ## 3) Wind calculations ########################################################################################### WIND_DIRECTIONS = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW', 'N'] # http://www.procdev.com/downloads/windcalcs.zip # http://www.webmet.com/met_monitoring/621.html def WindX(v,d): """ Extract X component of wind speed (X and Y are standard rectangular coordinates, ie X = East, Y = North) """ if d == None: return 0 else: return v * math.cos(2.0 * math.pi * (90.0 - d) / 360.0) def WindY(v,d): """ Extract Y component of wind speed (X and Y are standard rectangular coordinates, ie X = East, Y = North) """ if d == None: return 0 else: return v * math.sin(2.0 * math.pi * (90.0 - d) / 360.0) def WindSpeed(x,y): """ Obtains composite wind speed from x and y speeds """ return math.sqrt(x**2 + y**2) def WindDir(x,y): """ Obtains composite wind direction from x and y speeds """ # Calculate polar coordinate angle if x == 0: if y > 0: rAlpha = 90 elif y < 0: rAlpha = -90 else: rAlpha = 0 else: if x > 0 and y > 0: rAlpha = 180 / math.pi * math.atan(y/x) elif x < 0 and y < 0: rAlpha = 180 + 180 / math.pi * math.atan(y/x) elif x > 0 and y < 0: rAlpha = 180 / math.pi * math.atan(y/x) else: rAlpha = -180 + 180 / math.pi * math.atan(y/x) # Convert to compass bearing if rAlpha < 90: return 90 - rAlpha else: return 450 - rAlpha def WindDirTxt(d): """ Obtains textual representation of wind direction from a degree value (0 = 360 = N) """ if d == None: return '' else: return WIND_DIRECTIONS[int(round(d / 22.5))] def WindPredominantDirection(l): """ Given a list of [(speed, dir)] returns predominant wind direction obtained by wind speed vectors composition. """ # Step 1: (speed, dir) -> (speed, speedX, speedY) l1 = map(lambda (v,d): (WindX(v,d), WindY(v,d)), l) # Step 2: Calculate averages of speed, speedX, and speedY [avg_x, avg_y] = map(lambda L: sum(L)/len(L), zip(* l1)) # Step 3: return average speed, composite average speed and composite wind direction return WindDir(avg_x, avg_y) wfrog-0.8.2+svn953/wfcommon/storage/000077500000000000000000000000001213472117100172005ustar00rootroot00000000000000wfrog-0.8.2+svn953/wfcommon/storage/__init__.py000066400000000000000000000025601213472117100213140ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import yaml import csvfile import firebird import mysql import sqlite3 import simulator # YAML mappings class YamlCsvStorage(csvfile.CsvStorage, yaml.YAMLObject): yaml_tag = u'!csv' class YamlFirebirdStorage(firebird.FirebirdStorage, yaml.YAMLObject): yaml_tag = u'!firebird' class YamlMysqlStorage(mysql.MysqlStorage, yaml.YAMLObject): yaml_tag = u'!mysql' class YamlSqlite3Storage(sqlite3.Sqlite3Storage, yaml.YAMLObject): yaml_tag = u'!sqlite3' class YamlSimulatorStorage(simulator.SimulatorStorage, yaml.YAMLObject): yaml_tag = u'!simulator-storage' wfrog-0.8.2+svn953/wfcommon/storage/base.py000066400000000000000000000072461213472117100204750ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import time from datetime import datetime class DatabaseStorage(object): ''' Base class for database storages. ''' time_format = '%Y-%m-%d %H:%M:%S' tablename = 'METEO' mandatory_storage_fields = ['TEMP', 'HUM', 'DEW_POINT', 'WIND', 'WIND_DIR', 'WIND_GUST', 'WIND_GUST_DIR', 'RAIN', 'RAIN_RATE', 'PRESSURE'] optional_storage_fields = ['UV_INDEX', 'SOLAR_RAD', 'TEMPINT', 'HUMINT', 'TEMP2', 'HUM2', 'TEMP3', 'HUM3', 'TEMP4', 'HUM4', 'TEMP5', 'HUM5', 'TEMP6', 'HUM6', 'TEMP7', 'HUM7', 'TEMP8', 'HUM8', 'TEMP9', 'HUM9'] # Database storages should rewrite the storage_fields variable with the actual available fields storage_fields = mandatory_storage_fields def write_sample(self, sample, context={}): timestamp = time.mktime(sample['localtime'].timetuple()) utc_time = datetime.utcfromtimestamp(timestamp) sql = "INSERT INTO %s (TIMESTAMP_UTC, TIMESTAMP_LOCAL, %s) VALUES (%s, %s, %s)" % ( self.tablename, ', '.join(self.storage_fields), "'%s'" % utc_time.strftime(self.time_format), "'%s'" % sample['localtime'].strftime(self.time_format), ', '.join(map(lambda x: self.format(sample[x.lower()] if x.lower() in sample else None), self.storage_fields))) try: self.db.connect() self.db.execute(sql) self.logger.debug("SQL executed: %s", sql) except: self.logger.exception("Error writting current data to database") finally: self.db.disconnect() def keys(self, context={}): return ['utctime', 'localtime'] + map(str.lower ,self.storage_fields) def samples(self, from_time=datetime.fromtimestamp(0), to_time=datetime.now(), context={}): self.logger.debug("Getting samples for range: %s to %s", from_time, to_time) sql = ( "SELECT TIMESTAMP_UTC, TIMESTAMP_LOCAL, %s FROM %s " + \ " WHERE TIMESTAMP_LOCAL >= '%s' AND TIMESTAMP_LOCAL < '%s' "+ \ " ORDER BY TIMESTAMP_LOCAL ASC" ) % ( ', '.join(self.storage_fields), self.tablename, from_time.strftime(self.time_format), to_time.strftime(self.time_format)) try: self.db.connect() for row in self.db.select(sql): if not isinstance(row[0], datetime): row = list(row) row[0] = datetime.strptime(row[0], self.time_format) yield row finally: self.db.disconnect() def format(self, value): if value is None: return 'NULL' else: return str(value) # rounds up values to 1 decimal, which is OK. wfrog-0.8.2+svn953/wfcommon/storage/csvfile.py000066400000000000000000000131571213472117100212140ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import logging import os import os.path import csv import time from datetime import datetime import sys class CsvStorage(object): ''' Stores samples in a CSV file. Only supports one TH sensor (number 1). [ Properties ] path [string]: The path to the CSV file. ''' path = None columns = [ 'timestamp', 'localtime', 'temp', 'hum', 'wind', 'wind_dir', 'wind_gust', 'wind_gust_dir', 'dew_point', 'rain', 'rain_rate', 'pressure', 'uv_index' ] logger = logging.getLogger('storage.csv') def write_sample(self, sample, context={}): self.logger.debug(sample) if os.path.exists(self.path): file = open(self.path, 'a') writer = csv.writer(file) else: dir = os.path.realpath(os.path.dirname(self.path)) if not os.path.exists(dir): os.makedirs(dir) file = open(self.path, 'w') writer = csv.writer(file) writer.writerow(self.columns) file.flush() sample_row = [] now = sample['localtime'] sample_row.append(int(time.mktime(now.timetuple()))) # timestamp sample_row.append(now.strftime('%Y-%m-%d %H:%M:%S')) # localtime for key in self.columns[2:]: sample_row.append(sample[key]) writer.writerow(sample_row) self.logger.debug("Writing row: %s", sample_row) file.close() def keys(self, context={}): return ['localtime', 'temp', 'hum', 'wind', 'wind_dir', 'wind_gust', 'wind_gust_dir', 'dew_point', 'rain', 'rain_rate', 'pressure', 'uv_index', 'utctime'] def samples(self, from_time=datetime.fromtimestamp(0), to_time=datetime.now(), context={}): if not os.path.exists(self.path): self.logger.warning("File '"+self.path+"' not found") raise StopIteration from_timestamp = int(time.mktime(from_time.timetuple())) file = self._position_cursor(from_timestamp) to_timestamp = time.mktime(to_time.timetuple()) reader = csv.reader(file) counter=0 try: for line in reader: counter=counter+1 if len(line) == 0 or line[0].strip() == '': self.logger.warn('Encountered empty line after '+str(counter)+' lines') continue ts = int(line[0]) if ts < from_timestamp: continue if ts >= to_timestamp: raise StopIteration sample = line[1:] sample[0] = datetime.fromtimestamp(ts) length = len(sample) for i in range(1,length): if sample[i] != '' and sample[i] != None: sample[i] = float(sample[i]) else: sample[i] = None sample.append(datetime.utcfromtimestamp(ts)) yield sample finally: file.close() def _position_cursor(self, timestamp): size = os.path.getsize(self.path) step = offset = size / 2 file = open(self.path, 'r') while abs(step) > 1: last_pos = file.tell() if last_pos + offset < 0: break file.seek(offset, os.SEEK_CUR) (current_timestamp, shift) = self._get_timestamp(file) if current_timestamp is None: file.seek(last_pos) break pos = file.tell() if current_timestamp == timestamp: break if current_timestamp < timestamp: step = abs(step) / 2 else: step = - abs(step) / 2 offset = step - shift return file timestamp_length = 10 def _get_timestamp(self, file): skip = file.readline() shift = len(skip) + len('\n') timestamp_string = file.read(self.timestamp_length) if(timestamp_string.strip() == ''): # End of file return (None, 0) file.seek(-self.timestamp_length, os.SEEK_CUR) return ( int(timestamp_string), shift) def dump(value, context): print repr(value) if __name__ == '__main__': s = CsvStorage() s.path = '/tmp/wfrog.csv' timestamp = int(sys.argv[1]) f = s._position_cursor(timestamp) if f is not None: print f.readline() f.close() s = CsvStorage() s.path = '/tmp/wfrog.csv' format = "%Y-%m-%d %H:%M:%S" samples = s.samples(from_time=datetime.strptime('2010-03-22 22:58:33', format) , to_time=datetime.strptime('2010-03-22 23:00:35', format)) for i in samples: print repr(i) wfrog-0.8.2+svn953/wfcommon/storage/firebird.py000066400000000000000000000054051213472117100213440ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import logging import base import wfcommon.database class FirebirdStorage(base.DatabaseStorage): ''' Stores sample data in a Firebird database table. [ Properties ] database [string] (optional): Path to the database. Defaults to 'localhost:/var/lib/firebird/2.0/data/wfrog.db' user [string] (optional): Database user. Defaults to 'sysdba'. password [string] (optional): Database user password. Defaults to 'masterkey'. charset [string] (optional): Character encoding in the database. Defaults to 'ISO8859_1'. tablename [string] (optional): Table name. Defaults to 'METEO'. ''' database = 'localhost:/var/lib/firebird/2.0/data/wfrog.db' user = 'sysdba' password = 'masterkey' charset = 'ISO8859_1' logger = logging.getLogger('storage.firebird') def init(self, context=None): self.db = wfcommon.database.FirebirdDB(self.database, self.user, self.password, self.charset) table_fields = self._get_table_fields() # Verify Mandatory fields assert 'TIMESTAMP_UTC' in table_fields assert 'TIMESTAMP_LOCAL' in table_fields for field in self.mandatory_storage_fields: assert field in table_fields # Obtain actual storage fields self.storage_fields = self.mandatory_storage_fields + \ [field for field in self.optional_storage_fields if field in table_fields] self.logger.info("Table %s detected with fields: %s" % (self.tablename, ', '.join(self.storage_fields))) def _get_table_fields(self): sql = "SELECT RDB$FIELD_NAME FROM RDB$RELATION_FIELDS WHERE RDB$RELATION_NAME = '%s'" % self.tablename fields = [] try: self.db.connect() for row in self.db.select(sql): fields.append(str(row[0].strip())) finally: self.db.disconnect() return fields wfrog-0.8.2+svn953/wfcommon/storage/mysql.py000066400000000000000000000052141213472117100207210ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import logging import base import wfcommon.database class MysqlStorage(base.DatabaseStorage): ''' Stores sample data in a MySQL database table. [ Properties ] database [string] (optional): Name of the database. defaults to 'wfrog'. host [string] (optional): Database host. Defaults to 'localhost'. port [string] (optional): Database TCP port. user [string] (optional): Database usename. Defaults to 'root'. password [string] (optional): Database user password. Defaults to 'root'. tablename [string] (optional): Table name. Defaults to 'METEO'. ''' database = 'wfrog' host = 'localhost' port = 3306 user = 'root' password = 'root' logger = logging.getLogger('storage.mysql') def init(self, context=None): self.db = wfcommon.database.MySQLDB(self.database, self.host, self.port, self.user, self.password) table_fields = self._get_table_fields() # Verify Mandatory fields assert 'TIMESTAMP_UTC' in table_fields assert 'TIMESTAMP_LOCAL' in table_fields for field in self.mandatory_storage_fields: assert field in table_fields # Obtain actual storage fields self.storage_fields = self.mandatory_storage_fields + \ [field for field in self.optional_storage_fields if field in table_fields] self.logger.info("Table %s detected with fields: %s" % (self.tablename, ', '.join(self.storage_fields))) def _get_table_fields(self): sql = "show columns from %s;" % self.tablename fields = [] try: self.db.connect() for row in self.db.select(sql): fields.append(row[0]) finally: self.db.disconnect() return fields wfrog-0.8.2+svn953/wfcommon/storage/simulator.py000066400000000000000000000066251213472117100216020ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import logging import os import os.path import time import wfcommon.meteo import copy import random import datetime import sys class SimulatorStorage(object): ''' Returns random generated samples. [ Properties ] seed (optional): To change the generated data. Default to 0. period (optional): Sampling period in seconds. ''' seed=0 period=300 keylist = ['localtime', 'temp', 'hum', 'wind', 'wind_dir', 'wind_gust', 'wind_gust_dir', 'dew_point', 'rain', 'rain_rate', 'pressure', 'uv_index'] base = [ 0, 20, 65, 2, 45, 0, 0, 0, 0, 2, 1020, 2 ] logger = logging.getLogger('storage.random') def write_sample(self, sample, context={}): pass def keys(self): return self.keylist def samples(self, from_time=datetime.datetime.fromtimestamp(0), to_time=datetime.datetime.now(), context={}): from_timestamp = int(time.mktime(from_time.timetuple())) to_timestamp = time.mktime(to_time.timetuple()) sample = copy.copy(self.base) t = from_time while t < to_time: gen = random.Random() minutes = int(time.mktime(t.timetuple())/60) gen.seed(self.seed+minutes) sample[0] = t #temp sample[1] = self.variate(sample[1], gen, 1, -5, 28) #hum sample[2] = self.variate(sample[2], gen, 3, 10, 98) #wind avg sample[3] = self.variate(sample[3], gen, 0.5, 0, 10) #avg dir sample[4] = self.variate(sample[4], gen, 60, 0, 359) #wind gust sample[5] = self.variate(sample[3]+4, gen, 2, 0, 15) #gust dir sample[6] = self.variate(sample[4], gen , 22, 0, 359) #rain rate sample[9] = self.variate(sample[9], gen, 1, 0, 20) #rain fall sample[8] = sample[8]+sample[9]*(self.period/3600.0) #pressure sample[10] = self.variate(sample[10], gen, 5, 980, 1030) #uv-index sample[11] = round(self.variate(sample[11], gen, 2, 0, 12)) #dew point sample[7] = wfcommon.meteo.DewPoint(sample[1], sample[2]) yield sample t = t+datetime.timedelta(0,self.period) def variate(self, value, gen, ratio, vmin, vmax): delta = ratio * ( gen.random() - 0.5 ) if value + delta < vmin or value + delta > vmax: delta = -delta return value + delta wfrog-0.8.2+svn953/wfcommon/storage/sqlite3.py000066400000000000000000000043011213472117100211340ustar00rootroot00000000000000## Copyright 2013 A Mennucc ## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import logging import base import wfcommon.database class Sqlite3Storage(base.DatabaseStorage): ''' Stores sample data in a Sqlite3 database table. [ Properties ] database [string] (optional): File holding the database. tablename [string] (optional): Table name. Defaults to 'METEO'. ''' database = None logger = logging.getLogger('storage.sqlite3') def init(self, context=None): self.db = wfcommon.database.Sqlite3(self.database) table_fields = self._get_table_fields() # Verify Mandatory fields assert 'TIMESTAMP_UTC' in table_fields assert 'TIMESTAMP_LOCAL' in table_fields for field in self.mandatory_storage_fields: assert field in table_fields # Obtain actual storage fields self.storage_fields = self.mandatory_storage_fields + \ [field for field in self.optional_storage_fields if field in table_fields] self.logger.info("Table %s detected with fields: %s" % (self.tablename, ', '.join(self.storage_fields))) def _get_table_fields(self): sql = "PRAGMA table_info(%s);" % self.tablename fields = [] try: self.db.connect() for row in self.db.select(sql): fields.append(row[1]) finally: self.db.disconnect() return fields wfrog-0.8.2+svn953/wfcommon/storagecopy.py000066400000000000000000000055161213472117100204540ustar00rootroot00000000000000#!/usr/bin/python ## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . # Before loading other modules add wfrog directory to sys.path to be able to use wfcommon import os.path import sys if __name__ == "__main__": sys.path.append(os.path.abspath(sys.path[0] + '/..')) import wfcommon.storage import optparse import logging import wfcommon.config class StorageCopy(object): logger = logging.getLogger('storagecopy') def __init__(self, config_file=None): # Prepare the configurer module_map = ( ( "Storages", wfcommon.storage) ) if config_file is None: config_file = "config/storagecopy.yaml" configurer = wfcommon.config.Configurer(module_map) # Initialize the option parser opt_parser = optparse.OptionParser() configurer.add_options(opt_parser) # Parse the options and create object trees from configuration (options, args) = opt_parser.parse_args() (config, context) = configurer.configure(options, self, config_file) self.from_storage = config['from'] self.to_storage = config['to'] try: self.from_storage.init(context=context) except AttributeError: pass # In case the element has not init method try: self.to_storage.init(context=context) except AttributeError: pass # In case the element has not init method def run(self): keys = self.from_storage.keys() n = 0 for sample in self.from_storage.samples(): if n % 500 == 0: self.logger.info("Processed %d samples" % n) sample_to_write = {} for i in range(len(keys)): sample_to_write[keys[i]] = sample[i] self.to_storage.write_sample(sample_to_write) n += 1 if __name__ == "__main__": driver = StorageCopy() driver.logger.debug("Started main()") try: driver.run() except: driver.logger.exception("An unexpected error has ocurred while copying storages:") driver.logger.debug("Finished main()") wfrog-0.8.2+svn953/wfcommon/test/000077500000000000000000000000001213472117100165135ustar00rootroot00000000000000wfrog-0.8.2+svn953/wfcommon/test/csv.yaml000066400000000000000000000000721213472117100201710ustar00rootroot00000000000000storage: !csv path: wfrog.csv #storage: !firebird {} wfrog-0.8.2+svn953/wfcommon/units.py000066400000000000000000000107711213472117100172560ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import re from math import pow, sqrt reference = { "temp": "C", "press": "hPa", "wind": "m/s", "rain": "mm", } def identity(value): return value def FToC(value): return (((value * 1.0) - 32.0) * 5.0) / 9.0 if value != None else None def CToF(value): return ((value * 9.0) / 5.0) + 32.0 if value != None else None def InHgToHPa(value): return value / 0.02953 if value != None else None def HPaToInHg(value): return value * 0.02953 if value != None else None def HPaToMmHg(value): return value * 0.750062 if value != None else None def MmHgToHPa(value): return value / 0.750062 if value != None else None def InToMm(value): return value * 25.4 if value != None else None def MmToIn(value): return value / 25.4 if value != None else None def MpsToKt(value): return value / 0.514 if value != None else None def KtToMps(value): return value * 0.514 if value != None else None def MpsToKmh(value): return value * 3.6 if value != None else None def KmhToMps(value): return value / 3.6 if value != None else None def MphToMps(value): return value / 2.2445 if value != None else None def MpsToMph(value): return value * 2.2445 if value != None else None def MpsToBft(value): return pow((pow((value*3.6),2))/9, 1.0/3.0) if value != None else None def BftToMps(value): return sqrt(pow(value, 3)*9)/3.6 if value != None else None conversions = { "temp" : { "C" : [ identity, identity ], "F" : [ CToF, FToC ], }, "press" : { "hPa" : [ identity, identity ], "mmHg" : [ HPaToMmHg, MmHgToHPa ], "inHg" : [ HPaToInHg, InHgToHPa ], }, "wind" : { "m/s" : [ identity, identity ], "km/h": [ MpsToKmh, KmhToMps ], "mph": [ MpsToMph, MphToMps ], "kt": [ MpsToKt, KtToMps ], "bft": [MpsToBft, BftToMps ] }, "rain" : { "mm" : [identity, identity ], "in" : [MmToIn, InToMm ], } } def convert(measure, value, to_units, from_units=None): try: val = value if from_units: # convert to reference units val = conversions[measure][from_units][1](val) if not to_units: return val return conversions[measure][to_units][0](val) except: return val class Converter(object): units = reference def __init__(self, units): self.units.update(units) def convert(self, measure, value): raw_measure = re.split("[0-9]|int", measure)[0] # strip trailing numbers if not self.units.has_key(raw_measure): return value return convert(raw_measure, value, self.units[raw_measure]) def convert_back(self, measure, value): raw_measure = re.split("[0-9]|int", measure)[0] # strip trailing numbers if not self.units.has_key(raw_measure): return value return convert(raw_measure, value, None, self.units[raw_measure]) def temp(self, value): return self.convert("temp", value) def press(self, value): return self.convert("press", value) def wind(self, value): return self.convert("wind", value) def rain(self, value): return self.convert("rain", value) def unit_roll(measure, unit): """Gives the next unit alternative, wrapping""" k=conversions[measure].keys() i = k.index(unit) if i==len(k)-1: return k[0] else: return k[i+1] if __name__ == "__main__": target = { "temp": "F", "press": "mmHg", "wind": "kt", "rain": "in", } c = Converter( target ) print c.temp(20) print c.wind(1) print c.rain(254) print c.press(1000) wfrog-0.8.2+svn953/wfcommon/utils.py000066400000000000000000000046111213472117100172500ustar00rootroot00000000000000## Copyright 2010 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of WFrog ## ## WFrog 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 . from xml.etree import ElementTree from time import struct_time, strftime from datetime import datetime from decimal import Decimal def format(obj, time_format): if isinstance(obj, int): return str(obj) elif isinstance(obj, float): return str(round(obj,1)) elif isinstance(obj, Decimal): return str(obj) elif isinstance(obj, struct_time): return strftime(time_format, obj) elif isinstance(obj, (str, unicode)): return obj.strip() elif isinstance(obj, datetime): return obj.strftime(time_format) else: return "unknown" def extract_units(tag): i = tag.find('(') if i == -1: return (tag, None) j = tag.find(')') if j == -1: return (tag[:i], tag[i+1:j]) return (tag[:i], tag[i+1:j]) def write2xml(dictionary, root_tag, filename, time_format='%Y-%m-%d %H:%M:%S'): root = ElementTree.Element(root_tag) doc = ElementTree.ElementTree(root) if dictionary != None: ks = dictionary.keys() ks.sort() for k in ks: ele = root for (t,u) in map(extract_units, k.split('.')): aux = ele.find(t) if aux == None: if u == None: aux = ElementTree.Element(t) else: aux = ElementTree.Element(t, {'units': u}) ele.append(aux) ele = aux else: ele = aux ele.text = format(dictionary[k], time_format) f = open(filename, 'w') doc.write(f) f.close() wfrog-0.8.2+svn953/wfdriver/000077500000000000000000000000001213472117100155375ustar00rootroot00000000000000wfrog-0.8.2+svn953/wfdriver/__init__.py000066400000000000000000000000001213472117100176360ustar00rootroot00000000000000wfrog-0.8.2+svn953/wfdriver/config/000077500000000000000000000000001213472117100170045ustar00rootroot00000000000000wfrog-0.8.2+svn953/wfdriver/config/embedded.yaml000066400000000000000000000002411213472117100214160ustar00rootroot00000000000000station: !${settings.station.driver} { } #station: !${settings.station.driver} # port: /dev/ttyS1 # pressure_cal: -8 output: !service { name: events } wfrog-0.8.2+svn953/wfdriver/config/wfdriver.yaml000066400000000000000000000016541213472117100215260ustar00rootroot00000000000000station: !${settings.station.driver} { } #station: !${settings.station.driver} # port: /dev/ttyS1 # pressure_cal: -8 #output: !http-out { url: 'http://localhost:8888/' } output: !stdio-out {} logging: level: debug filename: !user choices: root: /var/log/wfdriver.log default: wfdriver.log ## By default wfrog uses a Rotating file handler. To set up different handlers ## uncomment the following section. (Warning: does not work under python > 2.7) #handlers: # default: # level: debug # # handler: !include # path: ../../wfcommon/config/loghandler.yaml # variables: # process: wfrender # # ## Uncomment to receive mail on critical events # #mail: # # level: critical # # handler: !include # # path: ../../wfcommon/config/mailloghandler.yaml wfrog-0.8.2+svn953/wfdriver/event.py000066400000000000000000000040531213472117100172340ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import StringIO class Element(object): def __init__(self): self.__dict__['children'] = [] def create_child(self, name): child = Element() self._add(name, child) return child def __setattr__(self, name, value): self._add(name, value) def _add(self, name, value): if not self.children.__contains__(name): self.children.append(name) self.__dict__[name] = value def __str__(self): # Trivial XML serialization. If the WESTEP protocol is extended, # consider using real XML construction. result = StringIO.StringIO() if self.__dict__.keys().__contains__('_type'): result.write('<' + self._type + '>') for child in self.children: if not child == '_type': result.write('<' + child + '>' + str(self.__dict__[child]) + '') if self.__dict__.keys().__contains__('_type'): result.write('') return result.getvalue() class Event(Element): ''' Class for events generated by the station implementations. ''' def __init__(self, type): Element.__init__(self) self._type = type wfrog-0.8.2+svn953/wfdriver/output/000077500000000000000000000000001213472117100170775ustar00rootroot00000000000000wfrog-0.8.2+svn953/wfdriver/output/__init__.py000066400000000000000000000020221213472117100212040ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import yaml import stdio import http # YAML Mappings class YamlStdioOutput(stdio.StdioOutput, yaml.YAMLObject): yaml_tag = '!stdio-out' class YamlHttpOutput(http.HttpOutput, yaml.YAMLObject): yaml_tag = '!http-out' wfrog-0.8.2+svn953/wfdriver/output/http.py000066400000000000000000000035251213472117100204350ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import httplib import urlparse import logging class HttpOutput(object): ''' Sends the events using HTTP post according to WESTEP HTTP transport. [ Properties ] url [string]: Endpoint the events are sent to. ''' connection = None logger = logging.getLogger('output.http') def send_event(self, event): if self.connection == None: parts = urlparse.urlsplit(self.url) self.connection = httplib.HTTPConnection(parts.netloc) self.path = parts.path if parts.query: self.path = self.path + '?' + parts.query try: self.connection.request('POST', self.url, str(event)) response = self.connection.getresponse() response.read() except Exception, e: self.connection = None self.logger.critical(self.url+": "+str(e)) return if response.status != 200: self.logger.critical('HTTP '+response.status+' '+response.reason) self.connection = None wfrog-0.8.2+svn953/wfdriver/output/stdio.py000066400000000000000000000020401213472117100205670ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import sys class StdioOutput(object): ''' Send events to the standard output according to WESTEP STDIO transport. ''' def send_event(self, event): sys.stdout.write(str(event)+"\n\n") sys.stdout.flush() wfrog-0.8.2+svn953/wfdriver/station/000077500000000000000000000000001213472117100172205ustar00rootroot00000000000000wfrog-0.8.2+svn953/wfdriver/station/__init__.py000066400000000000000000000043351213472117100213360ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import yaml import simulator import wmrs200 import wmr928nx import wmr200 import vantagepro import vantagepro2 import wh1080 import wh3080 import ws23xx import ws28xx import auto # YAML mappings and registration for auto-detect class YamlAutoDetectStation(auto.AutoDetectStation, yaml.YAMLObject): yaml_tag = u'!auto' class YamlWMR200Station(wmr200.WMR200Station, yaml.YAMLObject): yaml_tag = u'!wmr200' auto.stations.append(wmr200) class YamlWMRS200Station(wmrs200.WMRS200Station, yaml.YAMLObject): yaml_tag = u'!wmrs200' auto.stations.append(wmrs200) class YamlWMR928NXStation(wmr928nx.WMR928NXStation, yaml.YAMLObject): yaml_tag = u'!wmr928nx' auto.stations.append(wmr928nx) class YamlVantageProStation(vantagepro.VantageProStation, yaml.YAMLObject): yaml_tag = u'!vantagepro' class YamlVantageProStation(vantagepro2.VantageProStation, yaml.YAMLObject): yaml_tag = u'!vantagepro2' class YamlWH1080Station(wh1080.WH1080Station, yaml.YAMLObject): yaml_tag = u'!wh1080' class YamlWH3080Station(wh3080.WH3080Station, yaml.YAMLObject): yaml_tag = u'!wh3080' class YamlWS2300Station(ws23xx.WS2300Station, yaml.YAMLObject): yaml_tag = u'!ws2300' class YamlWS28xxStation(ws28xx.WS28xxStation, yaml.YAMLObject): yaml_tag = u'!ws28xx' auto.stations.append(ws28xx) class YamlRandomSimulator(simulator.RandomSimulator, yaml.YAMLObject): yaml_tag = u'!random-simulator' auto.stations.append(simulator) wfrog-0.8.2+svn953/wfdriver/station/auto.py000066400000000000000000000053141213472117100205450ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import sys import logging stations = [] class AutoDetectStation(object): ''' Auto detect station. Station driver class must implement the unbound boolean method 'detect' and must be register in the 'stations' list in order to be detected. [ Properties ] debug [true|false] (optional): If true also detects debug stations like simulators. Defaults to 'true'. Set it to 'false' in productive installation to ensure having only meaningful data even if the station fails. ''' logger = logging.getLogger('station.auto') debug = True def run(self, generate_event, send_event): detected_station = None self.logger.info("Discovering stations...") for station in stations: if hasattr(station, 'name'): name = station.name else: name = '' if hasattr(station, 'detect'): try: detected_result = station.detect() if self.detected(detected_result): self.logger.info("Detected station "+name) detected_station = detected_result break except Exception, e: self.logger.warn("Could not probe station "+name+": %s", e) if detected_station is None: self.logger.error("Could not detect any station connected to this computer") sys.exit(1) detected_station.run(generate_event, send_event) def detected(self, detected_result): # If level is debug and debug station are not excluded if detected_result and \ hasattr(detected_result, 'debug_station') and \ detected_result.debug_station and \ not (self.logger.isEnabledFor(logging.DEBUG) and \ self.debug): return False else: return detected_result wfrog-0.8.2+svn953/wfdriver/station/base.py000066400000000000000000000051371213472117100205120ustar00rootroot00000000000000## Copyright 2009 Jordi Puigsegur ## Laurent Bovet ## ## This file is part of WFrog ## ## WFrog 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 . class BaseStation(object): ''' Convenience class used for migrating station code. New station support should work directly with events. ''' def _report_rain(self, total, rate, timestamp=None): event = self.generate_event('rain') event.total = total event.rate = rate if timestamp: event.timestamp = timestamp self.send_event(event) def _report_wind(self, dirDeg, avgSpeed, gustSpeed, timestamp=None): event = self.generate_event('wind') event.create_child('mean') event.mean.dir = dirDeg event.mean.speed = avgSpeed event.create_child('gust') event.gust.dir = dirDeg event.gust.speed = gustSpeed if timestamp: event.timestamp = timestamp self.send_event(event) def _report_barometer_absolute(self, pressure, timestamp=None): event = self.generate_event('press') event.value = pressure if timestamp: event.timestamp = timestamp self.send_event(event) def _report_temperature(self, temp, humidity, sensor, timestamp=None): event = self.generate_event('temp') event.sensor = sensor event.value = temp if timestamp: event.timestamp = timestamp self.send_event(event) if humidity != None: event = self.generate_event('hum') event.sensor = sensor event.value = humidity if timestamp: event.timestamp = timestamp self.send_event(event) def _report_uv(self, uv_index, timestamp=None): event = self.generate_event('uv') event.value = uv_index if timestamp: event.timestamp = timestamp self.send_event(event) wfrog-0.8.2+svn953/wfdriver/station/simulator.py000066400000000000000000000076721213472117100216250ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import time import datetime import random import copy import logging def detect(): return RandomSimulator() class RandomSimulator(object): ''' Simulates a station. Issues events randomly with random variations. [Properties] past [number] (optional): Will send events "from the past" at startup. Default to 0. th_sensors [number] (optional): Number of TH sensors to be simulated. Values allowed are from 2 to 10. Defaults to 5. ''' debug_station = True past = 0 th_sensors = 5 types = [ 'press', 'rain', 'wind', 'uv', 'rad' ] + ['temp', 'hum'] * th_sensors init_values = [ 1020, 10, [ 3, 180], 1, 2 ] + [10, 65] * th_sensors range = [100, 20, [6, 360], 5, 4 ] + [30, 40] * th_sensors rain_total = 55 logger = logging.getLogger('station.simulator') name = "Station Simulator" def new_value(self, current, init, range): step = random.random()*(range/8.0) - range/16.0 dev = current-init # deviation from init delta = round(step-dev/16.0,1) # encourage keeping around init new = current + delta # keep in range if new < init -range/2.0: new = init - range/2.0 if new > init + range/2.0: new = init + range/2.0 return new def run(self, generate_event, send_event): current_values = copy.copy(self.init_values) past_counter = self.past while True: t = random.randint(0,len(self.types)-1) type = self.types[t] e = generate_event(type) if past_counter > 0: timestamp = datetime.datetime.now() - datetime.timedelta(0, 600) self.logger.debug("Setting timestamp %s to %s event", timestamp, type) e.timestamp = timestamp if type == 'temp' or type=='hum': e.sensor = random.randint(0,self.th_sensors-1) if type == 'wind': current_values[t][0] = self.new_value(current_values[t][0], self.init_values[t][0], self.range[t][0]) current_values[t][1] = self.new_value(current_values[t][1], self.init_values[t][1], self.range[t][1]) e.create_child('mean') e.mean.speed=current_values[t][0] e.mean.dir=current_values[t][1] e.create_child('gust') e.gust.speed=current_values[t][0] + random.randint(0,2) e.gust.dir=current_values[t][1] else: current_values[t] = self.new_value(current_values[t], self.init_values[t], self.range[t]) if type == 'rain': e.rate = current_values[t] e.total = self.rain_total self.rain_total = self.rain_total + random.randint(0,2) elif type == 'uv': e.value = abs(int(current_values[t])) else: e.value = current_values[t] send_event(e) if past_counter > 0: time.sleep(0.1) past_counter = past_counter - 1 else: time.sleep(2) name = RandomSimulator.name wfrog-0.8.2+svn953/wfdriver/station/vantagepro.py000066400000000000000000000066711213472117100217520ustar00rootroot00000000000000## Copyright 2010 Laurent Bovet ## derived from PyWeather by Patrick C. McGinty ## ## This file is part of wfrog ## ## wfrog 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 . import time import logging from wfcommon import units class VantageProStation(object): ''' Station driver for the Davis VantagePro. It is a wrapper around PyWeather, thus need this package installed on your system (sudo easy_install weather). Deprecated - USE vantagepro2 (native wfrog driver) [Properties] port [string] (optional): Serial port tty to use. Defaults to /dev/ttyS0. period [numeric] (optional): Polling interval in seconds. Defaults to 60. ''' ARCHIVE_INTERVAL=10 port='/dev/ttyS0' period=60 logger = logging.getLogger('station.vantagepro') name = 'Davis VantagePro' def run(self, generate_event, send_event, context={}): import weather.stations.davis station = weather.stations.davis.VantagePro(self.port, self.ARCHIVE_INTERVAL) while True: try: station.parse() e = generate_event('press') e.value = units.InHgToHPa(station.fields['Pressure']) send_event(e) e = generate_event('temp') e.sensor = 0 e.value = units.FToC(station.fields['TempIn']) send_event(e) e = generate_event('hum') e.sensor = 0 e.value = station.fields['HumIn'] send_event(e) e = generate_event('temp') e.sensor = 1 e.value = units.FToC(station.fields['TempOut']) send_event(e) e = generate_event('hum') e.sensor = 1 e.value = station.fields['HumOut'] send_event(e) e = generate_event('rain') e.rate = units.InToMm(station.fields['RainRate']) e.total = units.InToMm(station.fields['RainYear']) send_event(e) e = generate_event('wind') e.create_child('mean') e.mean.speed = units.MphToMps(station.fields['WindSpeed']) e.mean.dir = station.fields['WindDir'] rec = station.fields['Archive'] if rec: e.create_child('gust') e.gust.speed = units.MphToMps(rec['WindHi']) e.gust.dir = rec['WindHiDir'] send_event(e) except Exception, e: self.logger.error(e) # pause until next update time next_update = self.period - (time.time() % self.period) time.sleep(next_update) wfrog-0.8.2+svn953/wfdriver/station/vantagepro2.py000066400000000000000000000471071213472117100220330ustar00rootroot00000000000000## Copyright 2011 Jordi Puigsegur ## derived from PyWeather by Patrick C. McGinty ## ## This file is part of wfrog ## ## wfrog 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 . import time import logging from wfcommon import units import struct import array class VantageProStation(object): ''' Station driver for the Davis VantagePro and VantagePro2. [Properties] port [string] (optional): Serial port tty to use. Defaults to /dev/ttyS0. It accepts: - serial port number - serial port device name i.e. /dev/ttyUSB0 (Linux) - url for a raw or rfc2217 device: - rfc2217://: - socket://: baud [integer] (optional): Serial port speed. Possible values are 19200, 9600, 4800, 2400 and 1200. Defaults to 19200 rain_bucket ['us','eu'] (optional): Type of rain bucket. Defaults to 'eu'. - us: 0.01 inches each bucket tip. - eu: 2 mm. each bucket tip. pressure_cal [numeric] (optional): Pressure calibration offset in mb. Defaults to 0. ''' port='/dev/ttyS0' baud=19200 loops=25 rain_bucket = 'eu' pressure_cal = 0 logger = logging.getLogger('station.vantagepro') name = 'Davis VantagePro' # device reply commands WAKE_ACK = '\n\r' ACK = '\x06' ESC = '\x1b' OK = '\n\rOK\n\r' def run(self, generate_event, send_event, context={}): import serial _LoopStruct = LoopStruct(self.rain_bucket) assert self.rain_bucket in ['eu', 'us'] assert self.baud in [19200, 9600, 4800, 2400, 1200] while True: self.logger.info("Opening serial port") ## Open Serial port #self._port = serial.Serial(self.port, self.baud, timeout=10) try: self._port = serial.serial_for_url(self.port, self.baud, timeout=10) except AttributeError: # happens when the installed pyserial is older than 2.5. use the # Serial class directly then. self._port = serial.Serial(self.port, self.baud, timeout=10) try: bad_CRC = 0 self._wakeup() self._cmd( 'LOOP', self.loops) for x in xrange(self.loops): raw = self._port.read( _LoopStruct.size ) # read data self.logger.debug('read: ' + raw.encode('hex')) crc_ok = VProCRC.verify( raw ) if crc_ok: bad_CRC = 0 self.logger.debug("CRC OK") fields = _LoopStruct.unpack(raw) # Pressure e = generate_event('press') e.value = fields['Pressure'] + self.pressure_cal e.code = 'QFF' # Davis sends QFF calculated pressure (same as on console) send_event(e) log_txt = "DATA PACKET Press:%.1fmb " % (fields['Pressure'] + self.pressure_cal) # Inside Temp & Hum sensor e = generate_event('temp') e.sensor = 0 e.value = fields['TempIn'] send_event(e) e = generate_event('hum') e.sensor = 0 e.value = fields['HumIn'] send_event(e) log_txt += "TempIn:%.1fC HumIn:%d%% " % (fields['TempIn'], fields['HumIn']) # Outside main Temp & Hum sensor e = generate_event('temp') e.sensor = 1 e.value = fields['TempOut'] send_event(e) e = generate_event('hum') e.sensor = 1 e.value = fields['HumOut'] send_event(e) log_txt += "TempOut:%.1fC HumOut:%d%% " % (fields['TempOut'], fields['HumOut']) # Rain bucket e = generate_event('rain') e.rate = fields['RainRate'] e.total = fields['RainYear'] send_event(e) log_txt += "Rain:%.2fmm %.2fmm/h " % (fields['RainYear'], fields['RainRate']) # Wind sensor e = generate_event('wind') e.create_child('mean') e.mean.speed = fields['WindSpeed'] e.mean.dir = fields['WindDir'] e.create_child('gust') e.gust.speed = fields['WindSpeed'] e.gust.dir = fields['WindDir'] send_event(e) # We ignore WindSpeed10min data. Davis stations send wind # speed & direction every 2 secs. wfrog will calculate means log_txt += "Wind:%.3fm/s %d " % (fields['WindSpeed'], fields['WindDir']) # Extra Temp & Hum sensors for i in xrange(7): extra_temp = 'ExtraTemp%d' % (i+1) extra_hum = 'ExtraHum%d' % (i+1) if not fields[extra_temp] is None: e = generate_event('temp') e.sensor = i + 2 e.value = fields[extra_temp] send_event(e) log_txt += "Temp%d:%.1fC " % (i+1, fields[extra_temp]) if not fields[extra_hum] is None: e = generate_event('hum') e.sensor = i + 2 e.value = fields[extra_hum] send_event(e) log_txt += "Hum%d:%d%% " % (i+1, fields[extra_hum]) # UV sensor if not fields['UV'] is None: e = generate_event('uv') e.value = fields['UV'] send_event(e) log_txt += "UV:%.1f " % fields['UV'] # Solar radiation sensor if not fields['SolarRad'] is None: e = generate_event('rad') e.value = fields['SolarRad'] send_event(e) log_txt += "Rad:%.2fW/m2 " % fields['SolarRad'] self.logger.info(log_txt) else: self.logger.info("CRC Bad") bad_CRC += 1 # Two consecutive CRC errors => exception & abort LOOP command if bad_CRC > 1: raise Exception("CRC error") time.sleep(2) except Exception, e: self.logger.error(e) time.sleep(self.loops * 2) finally: self._port.close() self._port = None def _wakeup(self): ''' issue wakeup command to device to take out of standby mode. ''' self.logger.debug("send: WAKEUP") for i in xrange(3): self._port.write('\n') # wakeup device ack = self._port.read(len(self.WAKE_ACK)) # read wakeup string self.logger.debug('read: ' + ack.encode('hex')) if ack == self.WAKE_ACK: return raise Exception('Cannot access weather station (WAKEUP)') def _cmd(self,cmd,*args,**kw): ''' write a single command, with variable number of arguments. after the command, the device must return ACK ''' ok = kw.setdefault('ok',False) if args: cmd = "%s %s" % (cmd, ' '.join(str(a) for a in args)) self.logger.debug('send: ' + cmd) for i in xrange(3): self._port.write( cmd + '\n') if ok: ack = self._port.read(len(self.OK)) # read OK self.logger.debug('read: ' + ack.encode('hex')) if ack == self.OK: return else: ack = self._port.read(len(self.ACK)) # read ACK self.logger.debug('read: ' + ack.encode('hex')) if ack == self.ACK: return raise Exception('Cannot access weather station (%s)' % cmd) # --------------------------------------------------------------------------- # class NoDeviceException(Exception): pass # --------------------------------------------------------------------------- # class VProCRC(object): ''' Implements CRC algorithm, necessary for encoding and verifying data from the Davis Vantage Pro unit. ''' CRC_TABLE = ( 0x0, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0xa50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0xc60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0xe70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0xa1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x2b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x8e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0xaf1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0xcc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0xed1, 0x1ef0, ) @staticmethod def get(data): ''' return CRC calc value from raw serial data ''' crc = 0 for byte in array.array('B',data): crc = (VProCRC.CRC_TABLE[ (crc>>8) ^ byte ] ^ ((crc&0xFF) << 8)) return crc @staticmethod def verify(data): ''' perform CRC check on raw serial data, return true if valid. a valid CRC == 0. ''' if len(data) == 0: return False crc = VProCRC.get(data) return not crc # --------------------------------------------------------------------------- # class myStruct( struct.Struct ): ''' Implements a reusable class for working with a binary data structure. It provides a named fields interface, similiar to C structures. Usage: 1) subclass and extend _post_unpack method 2) instantiate directly, if no 'post unpack' processing needed Arguments: See `struct.Struct` class defintion. ''' def __init__(self, fmt, order='@'): self.fields, fmt_t = zip(*fmt) fmt_s = order + ''.join(fmt_t) super(myStruct,self).__init__( fmt_s ) def unpack(self, buf): ''' see unpack_from() ''' return self.unpack_from( buf, offset=0 ) def unpack_from(self, buf, offset=0 ): ''' unpacks data from 'buf' and returns a dication of named fields. the fields can be post-processed by extending the _post_unpack() method. ''' data = super(myStruct,self).unpack_from( buf, offset) items = dict(zip(self.fields,data)) return self._post_unpack(items) def _post_unpack(self,items): ''' perform data modification of any values, after unpacking from a buffer. ''' return items class LoopStruct( myStruct ): ''' For unpacking data structure returned by the 'LOOP' command. this structure contains all of the real-time data that can be read from the Davis Vantage Pro. ''' FMT = ( ('LOO', '3s'), ('BarTrend', 'B'), ('PacketType', 'B'), ('NextRec', 'H'), ('Pressure', 'H'), ('TempIn', 'H'), ('HumIn', 'B'), ('TempOut', 'H'), ('WindSpeed', 'B'), ('WindSpeed10Min','B'),('WindDir', 'H'), ('ExtraTemp1', 'B'), ('ExtraTemp2', 'B'), ('ExtraTemp3', 'B'), ('ExtraTemp4', 'B'), ('ExtraTemp5', 'B'), ('ExtraTemp6', 'B'), ('ExtraTemp7', 'B'), ('SoilTemps', '4s'), ('LeafTemps', '4s'), ('HumOut', 'B'), ('ExtraHum1', 'B'), ('ExtraHum2', 'B'), ('ExtraHum3', 'B'), ('ExtraHum4', 'B'), ('ExtraHum5', 'B'), ('ExtraHum6', 'B'), ('ExtraHum7', 'B'), ('RainRate', 'H'), ('UV', 'B'), ('SolarRad', 'H'), ('RainStorm', 'H'), ('StormStartDate','H'), ('RainDay', 'H'), ('RainMonth', 'H'), ('RainYear', 'H'), ('ETDay', 'H'), ('ETMonth', 'H'), ('ETYear', 'H'), ('SoilMoist', '4s'), ('LeafWetness','4s'), ('AlarmIn', 'B'), ('AlarmRain', 'B'), ('AlarmOut' , '2s'), ('AlarmExTempHum','8s'), ('AlarmSoilLeaf','4s'),('BatteryStatus','B'), ('BatteryVolts','H'), ('ForecastIcon','B'), ('ForecastRuleNo','B'),('SunRise', 'H'), ('SunSet', 'H'), ('EOL', '2s'), ('CRC', 'H'), ) def __init__(self, rain_bucket): super(LoopStruct,self).__init__(self.FMT,'=') self.rain_bucket = rain_bucket def _post_unpack(self,items): # Pressure items['Pressure'] = units.InHgToHPa(items['Pressure'] / 1000.0) # Temperature items['TempIn'] = units.FToC(items['TempIn'] / 10.0) items['TempOut'] = units.FToC(items['TempOut'] / 10.0) items['ExtraTemp1'] = units.FToC(items['ExtraTemp1'] / 10.0) if items['ExtraTemp1'] != 255 else None items['ExtraTemp2'] = units.FToC(items['ExtraTemp2'] / 10.0) if items['ExtraTemp2'] != 255 else None items['ExtraTemp3'] = units.FToC(items['ExtraTemp3'] / 10.0) if items['ExtraTemp3'] != 255 else None items['ExtraTemp4'] = units.FToC(items['ExtraTemp4'] / 10.0) if items['ExtraTemp4'] != 255 else None items['ExtraTemp5'] = units.FToC(items['ExtraTemp5'] / 10.0) if items['ExtraTemp5'] != 255 else None items['ExtraTemp6'] = units.FToC(items['ExtraTemp6'] / 10.0) if items['ExtraTemp6'] != 255 else None items['ExtraTemp7'] = units.FToC(items['ExtraTemp7'] / 10.0) if items['ExtraTemp7'] != 255 else None # Humidity items['ExtraHum1'] = items['ExtraHum1'] if items['ExtraHum1'] != 255 else None items['ExtraHum2'] = items['ExtraHum2'] if items['ExtraHum2'] != 255 else None items['ExtraHum3'] = items['ExtraHum3'] if items['ExtraHum3'] != 255 else None items['ExtraHum4'] = items['ExtraHum4'] if items['ExtraHum4'] != 255 else None items['ExtraHum5'] = items['ExtraHum5'] if items['ExtraHum5'] != 255 else None items['ExtraHum6'] = items['ExtraHum6'] if items['ExtraHum6'] != 255 else None items['ExtraHum7'] = items['ExtraHum7'] if items['ExtraHum7'] != 255 else None # Wind items['WindSpeed'] = units.MphToMps(items['WindSpeed']) items['WindSpeed10Min'] = units.MphToMps(items['WindSpeed10Min']) # Rain / European version => each bucket tip ~ 0.2mm if self.rain_bucket == 'eu': items['RainRate'] = items['RainRate'] / 5.0 items['RainStorm'] = items['RainStorm'] / 5.0 items['RainDay'] = items['RainDay'] / 5.0 items['RainMonth'] = items['RainMonth'] / 5.0 items['RainYear'] = items['RainYear'] / 5.0 # Rain / US version => each bucket tip ~ 0.01 inches. Conversion to mm needed. elif self.rain_bucket == 'us': items['RainRate'] = units.InToMm(items['RainRate'] / 100.0) items['RainStorm'] = units.InToMm(items['RainStorm'] / 100.0) items['RainDay'] = units.InToMm(items['RainDay'] / 100.0) items['RainMonth'] = units.InToMm(items['RainMonth'] / 100.0) items['RainYear'] = units.InToMm(items['RainYear'] / 100.0) items['StormStartDate'] = self._unpack_storm_date(items['StormStartDate']) # UV items['UV'] = (items['UV'] / 10.0) if items['UV'] != 255 else None # SolarRad items['SolarRad'] = items['SolarRad'] if items['SolarRad'] != 32767 else None # evapotranspiration totals # items['ETDay'] = items['ETDay'] / 1000.0 # items['ETMonth'] = items['ETMonth'] / 100.0 # items['ETYear'] = items['ETYear'] / 100.0 # soil moisture + leaf wetness # items['SoilMoist'] = struct.unpack('4B',items['SoilMoist']) # items['LeafWetness'] = struct.unpack('4B',items['LeafWetness']) # battery statistics items['BatteryVolts'] = items['BatteryVolts'] * 300 / 512.0 / 100.0 # sunrise / sunset # items['SunRise'] = self._unpack_time( items['SunRise'] ) # items['SunSet'] = self._unpack_time( items['SunSet'] ) return items @staticmethod def _unpack_time( val ): ''' given a packed time field, unpack and return "HH:MM" string. ''' # format: HHMM, and space padded on the left.ex: "601" is 6:01 AM return "%02d:%02d" % divmod(val,100) # covert to "06:01" @staticmethod def _unpack_storm_date( date ): ''' given a packed storm date field, unpack and return 'YYYY-MM-DD' string. ''' year = (date & 0x7f) + 2000 # 7 bits day = (date >> 7) & 0x01f # 5 bits month = (date >> 12) & 0x0f # 4 bits return "%s-%s-%s" % (year, month, day) wfrog-0.8.2+svn953/wfdriver/station/wh1080.py000066400000000000000000000073241213472117100205270ustar00rootroot00000000000000## Copyright 2010 Laurent Bovet , ## Jan Commandeur ## derived from pywws by Jim Easterbrook ## ## This file is part of wfrog ## ## wfrog 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 . import time import logging from wfcommon import units class WH1080Station(object): ''' Station driver for Fine Offset WH1080, WH1081, WH1090, WH1091, WH2080, WH2081. This also covers all the re-branded weather stations working with EasyWeather: - Elecsa AstroTouch 6975 - Watson W-8681 - WH-1080PC - Scientific Sales Pro Touch Screen Weather Station - TOPCOM NATIONAL GEOGRAPHIC 265NE - PCE-FWS 20 This driver is a wrapper around pywws, thus needs this package installed on your system. ''' logger = logging.getLogger('station.wh1080') name = 'Fine Offset WH1080 and compatibles' def run(self, generate_event, send_event, context={}): from pywws import WeatherStation station = WeatherStation.weather_station() for data, last_ptr, logged in station.live_data(): if not logged: try: if data['abs_pressure'] is not None: e = generate_event('press') e.value = (10*(4.5+(data['abs_pressure'])))/10 send_event(e) if data['temp_in'] is not None: e = generate_event('temp') e.sensor = 0 e.value = data['temp_in'] send_event(e) if data['hum_in'] is not None: e = generate_event('hum') e.sensor = 0 e.value = data['hum_in'] send_event(e) if data['temp_out'] is not None: e = generate_event('temp') e.sensor = 1 e.value = data['temp_out'] send_event(e) if data['hum_out'] is not None: e = generate_event('hum') e.sensor = 1 e.value = data['hum_out'] send_event(e) if data['rain'] is not None: e = generate_event('rain') e.total = (136*(data['rain']))/100 e.rate = 0 send_event(e) if data['wind_ave'] is not None and data['wind_dir'] < 16: e = generate_event('wind') e.create_child('mean') e.mean.speed = data['wind_ave'] e.mean.dir = 22.5*(data['wind_dir']) if data['wind_gust']: e.create_child('gust') e.gust.speed = data['wind_gust'] e.gust.dir = 22.5*(data['wind_dir']) send_event(e) except Exception, e: self.logger.error(e)wfrog-0.8.2+svn953/wfdriver/station/wh3080.py000066400000000000000000000076411213472117100205330ustar00rootroot00000000000000## Copyright 2010 Laurent Bovet , ## Jan Commandeur ## derived from pywws by Jim Easterbrook ## ## This file is part of wfrog ## ## wfrog 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 . import time import logging from wfcommon import units class WH3080Station(object): ''' Station driver for Fine Offset WH3080 This driver is a wrapper around pywws, thus needs this package installed on your system. ''' logger = logging.getLogger('station.wh3080') name = 'Fine Offset WH3080 and compatibles' def run(self, generate_event, send_event, context={}): from pywws import WeatherStation station = WeatherStation.weather_station() for data, last_ptr, logged in station.live_data(): if not logged: try: if data['abs_pressure'] is not None: e = generate_event('press') e.value = (10*(4.5+230+(data['abs_pressure'])))/10 #e.value = (10*(data['abs_pressure']))/10 send_event(e) if data['temp_in'] is not None: e = generate_event('temp') e.sensor = 0 e.value = data['temp_in'] send_event(e) if data['hum_in'] is not None: e = generate_event('hum') e.sensor = 0 e.value = data['hum_in'] send_event(e) if data['temp_out'] is not None: e = generate_event('temp') e.sensor = 1 e.value = data['temp_out'] send_event(e) if data['hum_out'] is not None: e = generate_event('hum') e.sensor = 1 e.value = data['hum_out'] send_event(e) if data['rain'] is not None: e = generate_event('rain') e.total = (136*(data['rain']))/100 e.rate = 0 send_event(e) if data['wind_ave'] is not None and data['wind_dir'] < 16: e = generate_event('wind') e.create_child('mean') e.mean.speed = data['wind_ave'] e.mean.dir = 22.5*(data['wind_dir']) if data['wind_gust']: e.create_child('gust') e.gust.speed = data['wind_gust'] e.gust.dir = 22.5*(data['wind_dir']) send_event(e) if data['uv'] is not None: e = generate_event('uv') e.sensor = 1 e.value = data['uv'] send_event(e) if data['solar_rad'] is not None: e = generate_event('rad') e.sensor = 1 e.value = (data['solar_rad']) send_event(e) except Exception, e: self.logger.error(e) wfrog-0.8.2+svn953/wfdriver/station/wmr200.py000066400000000000000000000754051213472117100206340ustar00rootroot00000000000000## Copyright 2010, 2011 Chris Schlaeger ## ## This WMR200 driver was modeled after the WMRS200 driver. It contains ## some code fragment from the original WMRS200 file. There portions are ## ## Copyright 2009 Jordi Puigsegur ## Laurent Bovet ## ## This file is part of WFrog ## ## WFrog 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 . # There is no known official documentation for the USB protocol that # the WMR200 uses to exchange weather data with a PC. Too bad that # Oregon Scientific does not understand the benefits of open # protocols. The code in this WMR200 driver is based on a collective # reverse engineering effort. Most of the decoding is probably # accurate, but minor bugs cannot be ruled out. Please submit a bug # report at http://code.google.com/p/wfrog/issues/list in case you see # odd behaviour. The report must include the full debug output of # the logger. # # cd wflogger # ./wflogger -d 2> wmr200.log # # Attach the wmr200.log file and include the corresponding actual # readings from your WMR200 display. # I'd like to thank the folks at # http://aguilmard.com/phpBB3/viewtopic.php?f=2&t=508&st=0&sk=t&sd=a&sid=4f64fc06860272367eb6c9e408acabe1 # for their previous work. Also, the work from Denis Ducret # was very helpful to write this driver. His data # logger can be found at http://www.sdic.ch/innovation/contributions. # Probably the most comprehensive WRM200 protocol description was compiled by # Rainer Finkeldeh and can be found at http://www.bashewa.com/wmr200-protocol.php. # Without the diligent reverse engineering efforts of these folks, this driver # would not have been possible. from base import BaseStation import time import datetime import logging import threading import platform import sys windDirMap = { 0:"N", 1:"NNE", 2:"NE", 3:"ENE", 4:"E", 5:"ESE", 6:"SE", 7:"SSE", 8:"S", 9:"SSW", 10:"SW", 11:"WSW", 12:"W", 13:"WNW", 14:"NW", 15:"NNW" } forecastMap = { 0:"Partly Cloudy", 1:"Rainy", 2:"Cloudy", 3:"Sunny", 4:"Clear Night", 5:"Snowy", 6:"Partly Cloudy Night", 7:"Unknown7" } trends = { 0:"Stable", 1:"Rising", 2:"Falling", 3:"Undefined" } usbWait = 0.5 usbTimeout = 3.0 # The USB vendor and product ID of the WMR200. Unfortunately, Oregon # Scientific is using this combination for several other products as # well. Checking for it, is not good enough to reliable identify the # WMR200. vendor_id = 0xfde product_id = 0xca01 def detect(): station = WMR200Station() station.init() if station.connectDevice(silent_fail=True) is not None: return station class WMR200Error(IOError): "Used to signal an error condition" class WMR200Station(BaseStation): ''' Station driver for the Oregon Scientific WMR200. ''' logger = logging.getLogger('station.wmr200') name = "Oregon Scientific WMR200" def init(self): # The delay between data requests. This value will be adjusted # automatically. self.pollDelay = 2.5 # Initialize some statistic counters. # The total number of packets. self.totalPackets = 0 # The number of correctly received USB packets. self.packets = 0 # The total number of received data frames. self.frames = 0 # The number of corrupted packets. self.badPackets = 0 # The number of corrupted frames self.badFrames = 0 # The number of checksum errors self.checkSumErrors = 0 # The number of sent requests for data frames self.requests = 0 # The number of USB connection resyncs self.resyncs = 0 # The time when the program was started self.start = time.time() # The time of the last resync start or end self.lastResync = time.time() # True if we are (re-)synching with the station self.syncing = True # The accumulated time in logging mode self.loggedTime = 0 # Difference between the PC clock and the station clock in # minutes. self.clockDelta = 0 # The accumulated time in (re-)sync mode self.resyncTime = 0 # Counters for each of the differnt data record types (0xD1 - # 0xD9) self.recordCounters = [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ] self.devh = None def _list2bytes(self, d): return reduce(lambda a, b: a + b, map(lambda a: "%02X " % a, d)) def searchDevice(self, vendorId, productId): try: import usb except Exception, e: self.logger.warning(e) return None busses = usb.busses() for bus in busses: for device in bus.devices: if device.idVendor == vendorId and device.idProduct == productId: self.usbDevice = device self.usbConfiguration = device.configurations[0] self.usbInterface = self.usbConfiguration.interfaces[0][0] return device # After each 0xD0 command, the station will provide a set of data # packets. The first byte of each packet indicates the number of # valid octects in the packet. The length octect is not counted, # so the maximum value for the first octet is 7. The remaining # octets to fill the 8 octests are invalid. The actual weather # data is contained in data frames that may spread over several # packets. If the read times-out, we have received the final # packet of the last frame. def receivePacket(self): import usb while True: try: packet = self.devh.interruptRead(usb.ENDPOINT_IN + 1, 8, int(self.pollDelay * 1000)) self.totalPackets += 1 # Provide some statistics on the USB connection every 1000 # packets. if self.totalPackets > 0 and self.totalPackets % 1000 == 0: self.logStats() if len(packet) != 8: # Valid packets must always have 8 octets. self.badPackets += 1 self.logger.error("Wrong packet size: %s" % self._list2bytes(packet)) elif packet[0] > 7: # The first octet is always the number of valid octets in the # packet. Since a packet can only have 8 bytes, ignore all packets # with a larger size value. It must be corrupted. self.badPackets += 1 self.logger.error("Bad packet: %s" % self._list2bytes(packet)) else: # We've received a new packet. self.packets += 1 self.logger.debug("Packet: %s" % self._list2bytes(packet)) return packet except usb.USBError, e: self.logger.debug("Exception reading interrupt: "+ str(e)) return None def sendPacket(self, packet): import usb try: self.devh.controlMsg(usb.TYPE_CLASS + usb.RECIP_INTERFACE, 0x9, packet, 0x200, timeout = int(usbTimeout * 1000)) except usb.USBError, e: if e.args != ('No error',): self.logger.exception("Can't write request record: "+ str(e)) # The WMR200 is known to support the following commands: # 0xD0: Request next set of data frames. # 0xDA: Check if station is ready. # 0xDB: Clear historic data from station memory. # 0xDF: Not really known. Maybe to signal end of data transfer. def sendCommand(self, command): # All commands are only 1 octect long. self.sendPacket([0x01, command, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) self.logger.debug("Command: %02X" % command) def clearReceiver(self): while True: if self.receivePacket() == None: break def connectDevice(self, silent_fail=False): import usb if silent_fail: self.logger.debug("USB initialization") else: self.logger.info("USB initialization") self.syncMode(True) self.resyncs += 1 try: dev = self.searchDevice(vendor_id, product_id) if dev == None: raise WMR200Error("USB WMR200 not found (%04X %04X)" % (vendor_id, product_id)) self.devh = dev.open() self.logger.info("Oregon Scientific weather station found") self.logger.info("Manufacturer: %s" % dev.iManufacturer) self.logger.info("Product: %s" % dev.iProduct) self.logger.info("Device version: %s" % dev.deviceVersion) self.logger.info("USB version: %s" % dev.usbVersion) try: self.devh.detachKernelDriver(self.usbInterface.interfaceNumber) self.logger.info("Unloaded other driver from interface %d" % self.usbInterface.interfaceNumber) except usb.USBError, e: pass # The following init sequence was adapted from Denis Ducret's # wmr200log program. self.devh.setConfiguration(self.usbConfiguration) self.devh.claimInterface(self.usbInterface) self.devh.setAltInterface(self.usbInterface) self.devh.reset() time.sleep(usbWait) # WMR200 Init sequence self.logger.debug("Sending 0xA message") self.devh.controlMsg(usb.TYPE_CLASS + usb.RECIP_INTERFACE, 0xA, [], 0, 0, int(usbTimeout * 1000)) time.sleep(usbWait) self.devh.getDescriptor(0x22, 0, 0x62) # Ignore any response packets the commands might have generated. self.clearReceiver() self.logger.debug("Sending init message") self.sendPacket([0x20, 0x00, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00]) # Ignore any response packets the commands might have generated. self.clearReceiver() # This command clears the WMR200 history memory. We can use it # if we don't care about old data that has been logged to the # station memory. # self.sendCommand(0xDB) # self.clearReceiver() # This command is supposed to stop the communication between # PC and the station. self.sendCommand(0xDF) # Ignore any response packets the commands might have generated. self.clearReceiver() # This command is a 'hello' command. The station respons with # a 0x01 0xD1 packet. self.sendCommand(0xDA) packet = self.receivePacket() if packet == None: self.logger.error("Station did not respond properly to WMR200 ping") return None elif packet[0] == 0x01 and packet[1] == 0xD1: self.logger.info("Station identified as WMR200") else: self.logger.error("Ping answer doesn't match: %s" % self._list2bytes(packet)) return None self.clearReceiver() self.logger.info("USB connection established") return self.devh except usb.USBError, e: if silent_fail: self.logger.debug("WMR200 connect failed: %s" % str(e)) else: self.logger.exception("WMR200 connect failed: %s" % str(e)) self.disconnectDevice() return None def disconnectDevice(self): import usb if self.devh == None: return try: # Tell console the session is finished. self.sendCommand(0xDF) try: self.devh.releaseInterface() except ValueError: None self.logger.info("USB connection closed") time.sleep(usbTimeout) except usb.USBError, e: self.logger.exception("WMR200 disconnect failed: %s" % str(e)) self.devh = None def increasePollDelay(self): if self.pollDelay < 5.0: self.pollDelay += 0.1 self.logger.debug("Polling delay increased: %.1f" % self.pollDelay) def decreasePollDelay(self): if self.pollDelay > 0.5: self.pollDelay -= 0.1 self.logger.debug("Polling delay decreased: %.1f" % self.pollDelay) def run(self, generate_event, send_event): # Initialize injected functions used by BaseStation self.generate_event = generate_event self.send_event = send_event self.logger.info("Thread started") self.init() while True: try: if self.devh == None: self.connectDevice() self.logData() except WMR200Error, e: self.logger.error("Re-syncing USB connection") self.disconnectDevice() self.logStats() # The more often we resync, the less likely we get back in # sync. To prevent useless retries, we wait longer the more # often we've tried. time.sleep(self.resyncs) # The weather data is contained in frames of variable length. The # first octet of each frame indicates the type of the frame. Valid # types are 0xD1 to 0xD9. The 0xD1 frame is only 1 octet long. It # is sent as a response to a 0xDA command. The 0xD2 - 0xD9 frames # are responses to a 0xD0 command. 0xD8 frames are probably not # used. The meaning of 0xD9 frames is currently unknown. def receiveFrames(self): packets = [] # Collect packets until we get no more data. By then we should have # received one or more frames. while True: packet = self.receivePacket() if packet == None: break # The first octet is the length. Only length octets are valid data. packets += packet[1:packet[0] + 1] frames = [] while True: if len(packets) == 0: # There should be at least one frame. if len(frames) == 0: # If we get empty frames we increase the polling delay a # bit. self.increasePollDelay() return None # We've found all the frames in the packets. break self.frames += 1 if packets[0] < 0xD1 or packets[0] > 0xD9: # All frames must start with 0xD1 - 0xD9. If the first byte is # not within this range, we don't have a proper frame start. # Discard all octets and restart with the next packet. self.logger.error("Bad frame: %s" % self._list2bytes(packets)) self.badFrames += 1 break if packets[0] == 0xD1 and len(packets) == 1: # 0xD1 frames have only 1 octet. frame = packets[0:1] packets = packets[1:len(packets)] frames.append(frame) elif len(packets) < 2 or len(packets) < packets[1]: # 0xD2 - 0xD9 frames use the 2nd octet to specifiy the length of the # frame. The length includes the type and length octet. self.logger.error("Short frame: %s" % self._list2bytes(packets)) self.badFrames += 1 break elif packets[1] < 8: # All valid D2 - D9 frames must have at least a length of 8 self.logger.error("Bad frame length: %d" % packets[1]) self.badFrames += 1 else: # This is for all frames with length byte and checksum. frame = packets[0:packets[1]] packets = packets[packets[1]:len(packets)] # The last 2 octets of D2 - D9 frames are always the low and high byte # of the checksum. We ignore all frames that don't have a matching # checksum. if self.checkSum(frame[0:len(frame)-2], frame[len(frame) - 2] | (frame[len(frame) - 1] << 8)) == False: self.checkSumErrors += 1 break frames.append(frame) if len(frames) > 0: if len(frames) > 2: # If we get more than 2 frames at a time we increase the # polling frequency again. self.decreasePollDelay() return frames else: return None def logData(self): while True: # Requesting the next set of data frames by sending a D0 # command. self.sendCommand(0xD0) self.requests += 1 # Get the frames. frames = self.receiveFrames() if frames == None: # The station does not have any data right now. Just wait a # bit and ask again. time.sleep(usbTimeout) else: # Send the received frames to the decoder. for frame in frames: self.decodeFrame(frame) def decodeFrame(self, record): self.syncMode(False) self.logger.debug("Frame: %s" % self._list2bytes(record)) type = record[0] self.recordCounters[type - 0xD1] += 1 if type == 0xD2: self.logger.info(">>>>> Historic Data Record >>>>>") # We ignore 0xD2 frames for now. They only contain historic data. # Byte 2 - 6 contains the time stamp. timeStamp = self.decodeTimeStamp(record[2:7], '@Time', False) # Bytes 7 - 19 contain rain data rainTotal, rainRate = self.decodeRain(record[7:20]) # Bytes 20 - 26 contain wind data dirDeg, avgSpeed, gustSpeed, windchill = self.decodeWind(record[20:27]) # Byte 27 contains UV data uv = self.decodeUV(record[27]) # Bytes 28 - 32 contain pressure data pressure = self.decodePressure(record[28:32]) # Byte 32: number of external sensors externalSensors = record[32] # Bytes 33 - end contain temperature and humidity data data = self.decodeTempHumid(record[33:33 + (1 + externalSensors) * 7]) self.logger.info("<<<<< End Historic Record <<<<<") # TODO: Find out how "no wind data" is encoded and ignore it. self._report_wind(dirDeg, avgSpeed, gustSpeed, timeStamp) # TODO: Find out how "no rain data" is encoded and ignore it. self._report_rain(rainTotal, rainRate, timeStamp) # If no UV data is present, the value is 0xFF. if uv != 0xFF: self._report_uv(uv, timeStamp) self._report_barometer_absolute(pressure, timeStamp) for d in data: temp, humidity, sensor = d self._report_temperature(temp, humidity, sensor, timeStamp) elif type == 0xD3: # 0xD3 frames contain wind related information. # Byte 2 - 6 contains the time stamp. self.decodeTimeStamp(record[2:7]) dirDeg, avgSpeed, gustSpeed, windchill = self.decodeWind(record[7:15]) self._report_wind(dirDeg, avgSpeed, gustSpeed) elif type == 0xD4: # 0xD4 frames contain rain data # Byte 2 - 6 contains the time stamp. self.decodeTimeStamp(record[2:7]) rainTotal, rainRate = self.decodeRain(record[7:21]) self._report_rain(rainTotal, rainRate) elif type == 0xD5: # 0xD5 frames contain UV data. # Untested. I don't have a UV sensor. # Byte 2 - 6 contains the time stamp. self.decodeTimeStamp(record[2:7]) uv = self.decodeUV(record[7]) self._report_uv(uv) elif type == 0xD6: # 0xD6 frames contain forecast and air pressure data. # Byte 2 - 6 contains the time stamp. self.decodeTimeStamp(record[2:7]) pressure = self.decodePressure(record[7:11]) self._report_barometer_absolute(pressure) elif type == 0xD7: # 0xD7 frames contain humidity and temperature data. # Byte 2 - 6 contains the time stamp. self.decodeTimeStamp(record[2:7]) data = self.decodeTempHumid(record[7:14]) temp, humidity, sensor = data[0] self._report_temperature(temp, humidity, sensor) elif type == 0xD8: # 0xD8 frames have never been observed. self.logger.info("TODO: 0xD8 frame found: %s" % self._list2bytes(record)) elif type == 0xD9: # 0x09 frames contain status information about the devices. The # meaning of several bits is still unknown. Maybe they are not in use. self.decodeStatus(record[2:8]) def decodeTimeStamp(self, record, label = 'Time', check = True): minutes = record[0] hours = record[1] day = record[2] # The WMR200 can sometimes return all 0 bytes. We interpret this as # 2000-01-01 0:00 if day == 0: day = 1 month = record[3] if month == 0: month = 1 year = 2000 + record[4] date = "%04d-%02d-%02d %d:%02d" % (year, month, day, hours, minutes) self.logger.info("%s: %s" % (label, date)) ts = time.mktime((year, month, day, hours, minutes, 0, -1, -1, -1)) if check: self.clockDelta = int(time.time() / 60) - int(ts / 60) return datetime.datetime(year, month, day, hours, minutes) def decodeWind(self, record): # Byte 0: Wind direction in steps of 22.5 degrees. # 0 is N, 1 is NNE and so on. See windDirMap for complete list. dirDeg = (record[0] & 0xF) * 22.5 # Byte 1: Always 0x0C? Maybe high nible is high byte of gust speed. # Byts 2: The low byte of gust speed in 0.1 m/s. gustSpeed = ((((record[1] >> 4) & 0xF) << 8) | record[2]) * 0.1 if record[1] != 0x0C: self.logger.info("TODO: Wind byte 1: %02X" % record[1]) # Byte 3: High nibble seems to be low nibble of average speed. # Byte 4: Maybe low nibble of high byte and high nibble of low byte # of average speed. Value is in 0.1 m/s avgSpeed = ((record[4] << 4) | ((record[3] >> 4) & 0xF)) * 0.1 if (record[3] & 0x0F) != 0: self.logger.info("TODO: Wind byte 3: %02X" % record[3]) # Byte 5 and 6: Low and high byte of windchill temperature. The value is # in 0.1F. If no windchill is available byte 5 is 0 and byte 6 0x20. # Looks like OS hasn't had their Mars Climate Orbiter experience yet. if record[5] != 0 or record[6] != 0x20: windchill = (((record[6] << 8) | record[5]) - 320) * (5.0 / 90.0) else: windchill = None self.logger.info("Wind Dir: %s" % windDirMap[record[0]]) self.logger.info("Gust: %.1f m/s" % gustSpeed) self.logger.info("Wind: %.1f m/s" % avgSpeed) if windchill != None: self.logger.info("Windchill: %.1f C" % windchill) return (dirDeg, avgSpeed, gustSpeed, windchill) def decodeRain(self, record): # Bytes 0 and 1: high and low byte of the current rainfall rate # in 0.1 in/h rainRate = ((record[1] << 8) | record[0]) / 3.9370078 # Bytes 2 and 3: high and low byte of the last hour rainfall in 0.1in rainHour = ((record[3] << 8) | record[2]) / 3.9370078 # Bytes 4 and 5: high and low byte of the last day rainfall in 0.1in rainDay = ((record[5] << 8) | record[4]) / 3.9370078 # Bytes 6 and 7: high and low byte of the total rainfall in 0.1in rainTotal = ((record[7] << 8) | record[6]) / 3.9370078 self.logger.info("Rain Rate: %.1f mm/hr" % rainRate) self.logger.info("Rain Hour: %.1f mm" % rainHour) self.logger.info("Rain 24h: %.1f mm" % rainDay) self.logger.info("Rain Total: %.1f mm" % rainTotal) # Bytes 8 - 12 contain the time stamp since the measurement started. self.decodeTimeStamp(record[8:13], 'Since', False) return (rainTotal, rainRate) def decodeUV(self, uv): self.logger.info("UV Index: %d" % uv) return uv def decodePressure(self, record): # Byte 0: low byte of pressure. Value is in hPa. # Byte 1: high nibble is probably forecast # low nibble is high byte of pressure. pressure = ((record[1] & 0xF) << 8) | record[0] forecast = forecastMap[(record[1] & 0x70) >> 4] # Bytes 2 - 3: Similar to bytes 0 and 1, but altitude corrected # pressure. Upper nibble of byte 3 is still unknown. Seems to # be always 3. altPressure = (record[3] & 0xF) * 256 + record[2] unknownNibble = (record[3] & 0x70) >> 4 self.logger.info("Forecast: %s" % forecast) self.logger.info("Measured Pressure: %d hPa" % pressure) if unknownNibble != 3: self.logger.info("TODO: Pressure unknown nibble: %d" % unknownNibble) self.logger.info("Altitude corrected Pressure: %d hPa" % altPressure) return pressure def decodeTempHumid(self, record): data = [] # The historic data can contain data from multiple sensors. I'm not # sure if the 0xD7 frames can do too. I've never seen a frame with # multiple sensors. But historic data bundles data for multiple # sensors. rSize = 7 for i in xrange(len(record) / rSize): # Byte 0: low nibble contains sensor ID. 0 for base station. sensor = record[i * rSize] & 0xF tempTrend = (record[i * rSize] >> 6) & 0x3 humTrend = (record[i * rSize] >> 4) & 0x3 # Byte 1: probably the high nible contains the sign indicator. # The low nibble is the high byte of the temperature. # Byte 2: The low byte of the temperature. The value is in 1/10 # degrees centigrade. temp = (((record[i * rSize + 2] & 0x0F) << 8) + record[i * rSize + 1]) * 0.1 if record[i * rSize + 2] & 0x80: temp = -temp # Byte 3: The humidity in percent. humidity = record[i * rSize + 3] # Bytes 4 and 5: Like bytes 1 and 2 but for dew point. dewPoint = (((record[i * rSize + 5] & 0x0F) << 8) + record[i * rSize + 4]) * 0.1 if record[i * rSize + 5] & 0x80: dewPoint = -dewPoint # Byte 6: Head index if record[i * rSize + 6] != 0: headIndex = (record[i * rSize + 6] - 32) / 1.8 else: headIndex = None self.logger.info("Temperature %d: %.1f C Trend: %s" % (sensor, temp, trends[tempTrend])) self.logger.info("Humidity %d: %d%% Trend: %s" % (sensor, humidity, trends[humTrend])) self.logger.info("Dew point %d: %.1f C" % (sensor, dewPoint)) if headIndex: self.logger.info("Heat index: %d" % (headIndex)) data.append((temp, humidity, sensor)) return data def decodeStatus(self, record): # Byte 0 if record[0] & int('11111100', 2): self.logger.info("TODO: Unknown bits in D9 frame byte 0: %0x2X" % (record[0])) if record[0] & 0x2: self.logger.warning("Sensor 1 fault (temp/hum outdoor)") if record[0] & 0x1: self.logger.warning("Wind sensor fault") # Byte 1 if record[1] & int('11001111',2): self.logger.info("TODO: Unknown bits in D9 frame byte 1: %02X" % (record[1])) if record[1] & 0x20: self.logger.warning("UV Sensor fault") if record[1] & 0x10: self.logger.warning("Rain sensor fault") # Byte 2 if record[2] & int('01111100', 2): self.logger.info("TODO: Unknown bits in D9 frame byte 2: %02X" % (record[2])) if record[2] & 0x80: self.logger.warning("Weak RF signal. Clock not synched") if record[2] & 0x02: self.logger.warning("Sensor 1: Battery low") if record[2] & 0x01: self.logger.warning("Wind sensor: Battery low") # Byte 3 if record[3] & int('11001111', 2): self.logger.info("TODO: Unknown bits in D9 frame byte 3: %02X" % (record[3])) if record[3] & 0x20: self.logger.warning("UV sensor: Battery low") if record[3] & 0x10: self.logger.warning("Rain sensor: Battery low") def checkSum(self, packet, checkSum): sum = 0 for byte in packet: sum += byte if sum != checkSum: self.logger.error("Checksum error: %d instead of %d" % (sum, checkSum)) return False return True def logStats(self): now = time.time() uptime = now - self.start self.logger.info("Uptime: %s" % self.durationToStr(uptime)) if self.totalPackets > 0: self.logger.info("Total packets: %d" % self.totalPackets) self.logger.info("Good packets: %d (%.1f%%)" % (self.packets, self.packets * 100.0 / self.totalPackets)) self.logger.info("Bad packets: %d (%.1f%%)" % (self.badPackets, self.badPackets * 100.0 / self.totalPackets)) if self.frames > 0: self.logger.info("Frames: %d" % self.frames) self.logger.info("Bad frames: %d (%.1f%%)" % (self.badFrames, self.badFrames * 100.0 / self.frames)) self.logger.info("Checksum errors: %d (%.1f%%)" % (self.checkSumErrors, self.checkSumErrors * 100.0 / self.frames)) self.logger.info("Requests: %d" % self.requests) self.logger.info("Clock delta: %d" % self.clockDelta) # Generate a warning if PC and station clocks are more than 2 # minutes out of sync. if abs(self.clockDelta) > 2: self.logger.warning("PC and station clocks are out of sync") self.logger.info("Polling delay: %.1f" % self.pollDelay) self.logger.info("USB resyncs: %d" % self.resyncs) loggedTime = self.loggedTime resyncTime = self.resyncTime if not self.syncing: loggedTime += now - self.lastResync else: resyncTime += now - self.lastResync self.logger.info("Logged time: %s (%.1f%%)" % (self.durationToStr(loggedTime), loggedTime * 100.0 / uptime)) self.logger.info("Resync time: %s (%.1f%%)" % (self.durationToStr(resyncTime), resyncTime * 100.0 / uptime)) if self.frames > 0: for i in xrange(9): self.logger.info("0x%X records: %8d (%2d%%)" % (0xD1 + i, self.recordCounters[i], self.recordCounters[i] * 100.0 / self.frames)) def durationToStr(self, sec): seconds = sec % 60 minutes = (sec / 60) % 60 hours = (sec / (60 * 60)) % 24 days = (sec / (60 * 60 * 24)) return ("%d days, %d hours, %d minutes, %d seconds" % (days, hours, minutes, seconds)) def syncMode(self, on): now = time.time() if self.syncing: if not on: self.logger.info("*** Switching to log mode ***") # We are in sync mode and need to switch to log mode now. self.resyncTime += now - self.lastResync self.lastResync = now self.syncing = False else: if on: self.logger.info("*** Switching to sync mode ***") # We are in log mode and need to switch to sync mode now. self.loggedTime += now - self.lastResync self.lastResync = now self.syncing = True name = WMR200Station.name wfrog-0.8.2+svn953/wfdriver/station/wmr928nx.py000066400000000000000000000360721213472117100212200ustar00rootroot00000000000000## Copyright 2009 Jordi Puigsegur ## Laurent Bovet ## ## This file is part of WFrog ## ## WFrog 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 . # Protocol information obtained from from: # - http://www.netsky.org/WMR/Protocol.htm # - http://www.cs.stir.ac.uk/~kjt/software/comms/wmr928.html # - http://www.castro.aus.net/~maurice/weather/ import wfcommon.utils import logging import time import threading from base import BaseStation class WMR928NXStation(BaseStation): ''' Station driver for the Oregon Scientific WMR928NX. [ Properties ] port [string] (optional): Serial port where the station is connected. Defaults to 1. It accepts: - serial port number - serial port device name i.e. /dev/ttyUSB0 (Linux) - url for a raw or rfc2217 device: - rfc2217://: - socket://: pressure_cal [numeric] (optional): Pressure calibration offset in mb. Defaults to 0. ''' port = 1 pressure_cal = 0 logger = logging.getLogger('station.wmr928nx') name = 'Oregon Scientific WMR928NX' weatherStatusMap = {0xc: 'Sunny', 0x6: 'Half cloudy', 0x2: 'Cloudy', 0x3: 'rainy'} def _list2bytes(self, d): return reduce(lambda a, b: a + b, map(lambda a: "%02X " % a, d)) def _decode_bcd(self, bcd): return(bcd & 0xf) + ((bcd & 0xf0) >> 4) * 10 def run(self, generate_event, send_event): import serial # Initialize injected functions used by BaseStation self.generate_event = generate_event self.send_event = send_event self.logger.info("Thread started") while True: try: self.logger.info("Opening serial port") ## Open Serial port try: ser = serial.serial_for_url(self.port, 9600, parity=serial.PARITY_NONE, bytesize=serial.EIGHTBITS, stopbits=serial.STOPBITS_ONE, timeout=60) except AttributeError: # happens when the installed pyserial is older than 2.5. use the # Serial class directly then. ser = serial.Serial(self.port, 9600, parity=serial.PARITY_NONE, bytesize=serial.EIGHTBITS, stopbits=serial.STOPBITS_ONE, timeout=60) #ser = serial.Serial() #ser.setBaudrate(9600) #ser.setParity(serial.PARITY_NONE) #ser.setByteSize(serial.EIGHTBITS) #ser.setStopbits(serial.STOPBITS_ONE) #ser.setPort(self.port) #ser.setTimeout(60) # 60s timeout #ser.open() ser.setRTS(True) ## Do the actual work self.logger.info("Serial port open") self._run(ser) except: self.logger.exception("WMR928NX reader exception") ## Close serial port connection self.logger.critical("Serial port WMR928NX connection failure") try: ser.close() ser = None except: pass ## Wait 10 seconds time.sleep(10) def _run(self, ser): self._WMR928NX_record_types = { 0x00: (9, 'Wind', self._parse_wind_record), 0x01: (14, 'Rain', self._parse_rain_record), 0x02: (7, 'TempHum', self._parse_temp_hum_record), # TH sensor 0x03: (7, 'TempHum (base)', self._parse_temp_hum_main_record), # TH mushrom sensor (channel 1) 0x04: (5, 'Temp', self._parse_temp_record), # T sensor 0x06: (12, 'Console', self._parse_console_record), # EXTBTH sensor 0x0e: (3, 'Minute', self._parse_minute_record), 0x0f: (7, 'Clock', self._parse_clock_record)} input_buffer = [] while True: buffer = ser.read(10) # Read next 10 bytes and return if len(buffer)== 0: # 60s timeout expired without data received self.logger.warning("No data received - reinitializing serial port") try: ser.close() input_buffer = [] time.sleep(10) ser.open() ser.setRTS(True) self.logger.warning("Serial port reinitialized") except: pass else: # data received and added to input buffer n_buffer = map(lambda x: ord(x), buffer) self.logger.debug("Serial RAW DATA: %s" % self._list2bytes(n_buffer)) input_buffer += n_buffer # obtain new messages when input_buffer contains at least 20 bytes to process if len(input_buffer) > 20: # Using two bytes of 0xFF as record separators, extract as many # full messages as possible and add them to the message queue. while True: # start by finding the first record separator in the input startSep = -1 for i in range(len(input_buffer)): if input_buffer[i] == 0xff and input_buffer[i + 1] == 0xff: startSep = i break if startSep == -1: break # find the next most right separator (FF FF), # which will indicate the end of the record endSep = -1 for i in range(startSep + 2, len(input_buffer) - 2): if input_buffer[i] == 0xff and input_buffer[i + 1] == 0xff: endSep = i elif endSep != -1: break if endSep == -1: break if startSep > 0: self.logger.debug("Ignored %d bytes in input", startSep) length = endSep - startSep - 2 if length == 0: self.logger.debug("Warning: zero length message in input") else: # Parse the message self.parse_record(input_buffer[startSep + 2 : endSep]) # remove this message from the input queue input_buffer = input_buffer[endSep:] def parse_record(self, record): # 1 - ID byte (record type) # # n-1 - checksum length = len(record) if length < 3: self.logger.warning("Record: %s - bad checksum + wrong size", self._list2bytes(record)) else: computedChecksum = (reduce(lambda x,y: x + y, record[:-1]) - 2) & 0xff recordChecksum = record[length - 1] if recordChecksum != computedChecksum: self.logger.warning("Record: %s - bad checksum", self._list2bytes(record)) elif record[0] in self._WMR928NX_record_types: (expected_length, record_type, record_parser) = self._WMR928NX_record_types.get(record[0]) if expected_length != length: self.logger.warning("%s Record: %s - wrong length (expected %d, received %d)", record_type, self._list2bytes(record), expected_length, length) return else: self.logger.debug("%s Record: %s", record_type, self._list2bytes(record)) record_parser(record) else: self.logger.warning("Unknown record type: %s", self._list2bytes(record)) def _parse_clock_record(self, record): batteryOK = (record[1] & 0x80) == 0 minute = self._decode_bcd(record[1] & 0x7f) hour = self._decode_bcd(record[2]) day = self._decode_bcd(record[3]) month = self._decode_bcd(record[4]) year = 2000 + self._decode_bcd(record[5]) consoleClock = "%d/%d/%d %d:%d" % (day, month, year, hour, minute) self.logger.info("Clock %s, batteryOK: %s", consoleClock, batteryOK) def _parse_minute_record(self, record): batteryOK = (record[1] & 0x80) == 0 minute = self._decode_bcd(record[1] & 0x7f) self.logger.info("Minute %d, batteryOK: %s" , minute, batteryOK) def _parse_rain_record(self, record): batteryOk = (record[1] & 0x40) == 0 # TODO: investigate meaning of over bits & include in xml file if necessary rateOver = not ((record[1] & 0x10) == 0) totalOver = not ((record[1] & 0x20) == 0) yesterdayOver = not ((record[1] & 0x80) == 0) # results come in inches rate = self._decode_bcd(record[2]) + self._decode_bcd(record[3] & 0xf) * 100.0 yesterday = self._decode_bcd(record[6]) + self._decode_bcd(record[7]) * 100.0 total = ((self._decode_bcd(record[3] & 0xf0)) / 100.0) + \ self._decode_bcd(record[4]) + \ self._decode_bcd(record[5]) * 100.0 minuteT = self._decode_bcd(record[8]) hourT = self._decode_bcd(record[9]) dayT = self._decode_bcd(record[10]) monthT = self._decode_bcd(record[11]) yearT = 2000 + self._decode_bcd(record[12]) self._report_rain(total, rate) self.logger.info("Rain batteryOK Ok: %s, Rate %g, Yesterday %g, Total %g since %d/%d/%d %d:%d", batteryOk, rate, yesterday, total, yearT, monthT, dayT, hourT, minuteT) def _parse_wind_record(self, record): # TODO: investigate meaning other variables avrgOver = not ((record[1] & 0x20) == 0) gustOver = not ((record[1] & 0x10) == 0) batteryOK = ((record[1] & 0x40) == 0) dirDeg = self._decode_bcd(record[2]) + self._decode_bcd(record[3] & 0xf) * 100 gustSpeed = self._decode_bcd(record[3] & 0xf0) / 100.0 + self._decode_bcd(record[4]) avgSpeed = self._decode_bcd(record[5]) / 10.0 + self._decode_bcd(record[6] & 0xf) * 10.0 chillNoData = not ((record[6] & 0x20) == 0) chillOver = not ((record[6] & 0x40) == 0) windChill = self._decode_bcd(record[7]) if not ((record[6] & 0x80) == 0): windChill *= -1.0 if not avrgOver and not gustOver: self._report_wind(dirDeg, avgSpeed, gustSpeed) self.logger.info("Wind batteryOk: %s, direction: %d, gust: %g m/s, avg. speed: %g m/s, WindChill %g C", batteryOK, dirDeg, gustSpeed, avgSpeed, windChill) def _parse_console_record(self, record): """ Pressure = real pressure - 600 Offset - 600 = offset to add to real pressure """ batteryOK = (record[1] & 0x40) == 0 temperature = self._decode_bcd(record[2]) * 0.1 + self._decode_bcd(record[3] & 0x3f) * 10.0 if record[3] & 0x80 == 0x80: temperature *= -1 humidity = self._decode_bcd(record[4]) dewPoint = None if record[1] & 0x10 == 0x00: dewPoint = self._decode_bcd(record[5]) pressure = 600 + record[6] + ((0x1 & record[7]) << 8) offset = (((record[8] & 0xf0) >> 4) / 10.0) + self._decode_bcd(record[9]) + \ (self._decode_bcd(record[10]) * 100.0) - 600 seaLevelPressure = pressure + offset weatherStatus = (record[7] & 0xf0) >> 4 weatherStatusTxt = self.weatherStatusMap.get(weatherStatus, str(weatherStatus)) self._report_barometer_absolute(pressure + self.pressure_cal) self._report_temperature(temperature, humidity, 0) # Log if dewPoint == None: self.logger.info("Console batteryOK: %s, Temp.: %g C, Humidity: %d %%, Pressure: %g, SeaLevelPressure: %g, WeatherStatus: %d, WeatherStatusTxt: %s", batteryOK, temperature, humidity, pressure, seaLevelPressure, weatherStatus, weatherStatusTxt) else: self.logger.info("Console batteryOK: %s, Temp.: %g C, Humidity: %d %%, DewPoint: %g, Pressure: %g, SeaLevelPressure: %g, WeatherStatus: %d, WeatherStatusTxt: %s", batteryOK, temperature, humidity, dewPoint, pressure, seaLevelPressure, weatherStatus, weatherStatusTxt) def _parse_temp_record(self, record): """ """ channel = (record[1] & 0x0f) + 1 batteryOK = (record[1] & 0x40) == 0 overUnder = not((record[3] & 0x40) == 0) # Temperature temp = self._decode_bcd(record[2]) * 0.1 + self._decode_bcd(record[3] & 0x3f) * 10.0; if not ((record[3] & 0x80) == 0): temp *= -1 # Report data if not overUnder: self._report_temperature(temp, None, channel) # Log self.logger.info("Temp_%d batteryOK: %s, Temp.: %g C", channel, batteryOK, temp) def _parse_temp_hum_main_record(self, record): self._parse_temp_hum_record(record, 1) def _parse_temp_hum_record(self, record, channel=None): """ """ if channel == None: channel = (record[1] & 0x0f) + 1 batteryOK = (record[1] & 0x40) == 0 overUnder = not((record[3] & 0x40) == 0) dewUnder = not ((record[1] & 0x10) == 0) # Temperature temp = self._decode_bcd(record[2]) * 0.1 + self._decode_bcd(record[3] & 0x3f) * 10.0; if not ((record[3] & 0x80) == 0): temp *= -1 # Humidity humidity = self._decode_bcd(record[4]) # Station Dew Point dewPoint = self._decode_bcd(record[5]) # Report data if not overUnder: self._report_temperature(temp, humidity, channel) # Log self.logger.info("Temp_Hum_%d batteryOK: %s, Temp.: %g C, Humidity: %d %%, Dew Point: %g C", channel, batteryOK, temp, humidity, dewPoint) wfrog-0.8.2+svn953/wfdriver/station/wmrs200.py000066400000000000000000000436041213472117100210130ustar00rootroot00000000000000## Copyright 2009 Jordi Puigsegur ## Laurent Bovet ## ## This file is part of WFrog ## ## WFrog 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 . # Protocol information obtained from: # 1) http://www.ejeklint.se/development/wmr100n-driver-for-mac-os-x/ # (protocol: https://github.com/ejeklint/WLoggerDaemon/blob/master/Station_protocol.md) # 2) http://wmrx00.sourceforge.net/ (WMR100 weather logger project) # Attention! # In individual measures wind avg. value can be higher than wind gust. # This might be due to the fact that wind gust is an instantaneous measure # whereas wind avg has been calculated over a period of --probably-- several # minutes. from base import BaseStation import time import logging import logging import threading import platform import sys forecastMap = { 0:'PartlyCloudy', 1:'Rainy', 2:'Cloudy', 3:'Sunny', 4:'Snowy' } comfortLevelMap = { 0:'-', 1:'Good', 2:'Poor', 3:'Fair' } trendMap = { 0:'Steady', 1:'Rising', 2:'Falling'} windDirMap = { 0:"N", 1:"NNE", 2:"NE", 3:"ENE", 4:"E", 5:"ESE", 6:"SE", 7:"SSE", 8:"S", 9:"SSW", 10:"SW", 11:"WSW", 12:"W", 13:"WNW", 14:"NW", 15:"NWN" } thSensors = { 0:'thInt', 1:'th1', 2:'th2', 3:'th3', 4:'th4', 5:'th5', 6:'th6', 7:'th7', 8:'th8', 9:'th9' } mainThExtSensor = 'th1' vendor_id = 0xfde product_id = 0xca01 def detect(): station = WMRS200Station() if station._search_device(vendor_id, product_id) is not None: return station class WMRS200Station(BaseStation): ''' Station driver for the Oregon Scientific WMRS200. Reported to work with WMR100. [ Properties ] pressure_cal [numeric] (optional): Pressure calibration offset in mb. Defaults to 0. rain_gauge_diameter [numeric] (optional): Rain gauge diamater in mm. When specified the driver will do the necessary conversions to adjust rain to the new gauge size. Defaults to 0 (= no calculation) ''' pressure_cal = 0 rain_gauge_diameter = 0 logger = logging.getLogger('station.wmrs200') name = "Oregon Scientific WMRS200" def _list2bytes(self, d): return reduce(lambda a, b: a + b, map(lambda a: "%02X " % a, d)) def _search_device(self, vendor_id, product_id): try: import usb except Exception, e: self.logger.warning(e) return None for bus in usb.busses(): for dev in bus.devices: if dev.idVendor == vendor_id and dev.idProduct == product_id: return dev def run(self, generate_event, send_event): import usb # Initialize injected functions used by BaseStation self.generate_event = generate_event self.send_event = send_event self.logger.info("Thread started") while True: try: self.logger.info("USB initialization") dev = self._search_device(vendor_id, product_id) if dev == None: raise Exception("USB WMRS200 not found (%04X %04X)" % (vendor_id, product_id)) self.logger.info("USB WMRS200 found") devh = dev.open() self.logger.info("USB WMRS200 open") if platform.system() is 'Windows': devh.setConfiguration(1) try: devh.claimInterface(0) except usb.USBError: devh.detachKernelDriver(0) devh.claimInterface(0) # WMRS200 Init sequence devh.controlMsg(usb.TYPE_CLASS + usb.RECIP_INTERFACE, # requestType 0x0000009, # request [0x20,0x00,0x08,0x01,0x00,0x00,0x00,0x00], # buffer 0x0000200, # value 0x0000000, # index 1000) # timeout ## Do the actual work self.logger.info("USB WMRS200 initialized") self._run(devh) except Exception, e: self.logger.exception("WMRS200 exception: %s" % str(e)) self.logger.critical("USB WMRS200 connection failure") ## Wait 10 seconds time.sleep(10) def _run(self, devh): import usb ## Initialize internal data self._WMRS200_record_types = { 0x41: (17, 'Rain', self._parse_rain_record), 0x42: (12, 'Temperature', self._parse_temperature_record), 0x46: (8, 'Barometer', self._parse_barometer_record), 0x47: (6, 'UV', self._parse_uv_record), 0x48: (11, 'Wind', self._parse_wind_record), 0x60: (12, 'Clock', self._parse_clock_record)} input_buffer = [] errors = 0 while True: try: # Ignore USBError("No error") exceptions http://bugs.debian.org/476796 try: packet = devh.interruptRead(usb.ENDPOINT_IN + 1, # endpoint number 0x0000008, # bytes to read 15000) # timeout (15 seconds) errors = 0 except usb.USBError, e: if e.args == ('No error',): self.logger.debug('USBError("No error") exception received. Ignoring...(http://bugs.debian.org/476796)') packet = None time.sleep(1) elif e.args == ('Connection timed out',): self.logger.debug('No event received within timeout.') packet = None time.sleep(1) else: raise e except Exception, e: self.logger.exception("Exception reading interrupt: "+ str(e)) errors = errors + 1 packet = None ## error in this packet, we do not want it if errors == 1: ## Very often we missed 0xFF, let's try to recover packet = [1, 0xff, 0, 0, 0, 0, 0, 0] elif errors > 3: break ## Maximum 3 consecutive errors before reconnection time.sleep(3) if packet != None: if len(packet) > 0 and packet[0] >= 1 and packet[0] <= 7: ## Ignore packets with wrong lengths input_buffer += packet[1:packet[0]+1] self.logger.debug("USB RAW DATA: %s" % self._list2bytes(packet)) if len(input_buffer) > 20: errors = 0 # Using two bytes of 0xFF as record separators, extract as many # full messages as possible and add them to the message queue. while True: # start by finding the first record separator in the input startSep = -1 for i in range(len(input_buffer) - 2): if input_buffer[i] == 0xff and input_buffer[i + 1] == 0xff: startSep = i break if startSep < 0: break # find the next separator, which will indicate the end of the 1st record endSep = -1 for i in range(startSep + 2, len(input_buffer) - 2): if input_buffer[i] == 0xff and input_buffer[i + 1] == 0xff: endSep = i; break if endSep < 0: break if startSep > 0: self.logger.debug("Ignored %d bytes in input", startSep) length = endSep - startSep - 2 if length == 0: self.logger.debug("Warning: zero length message in input") else: # Parse the message try: self.parse_record(input_buffer[startSep + 2 : endSep]) except: self.logger.exception("WMRS200 reader exception") # remove this message from the input queue input_buffer = input_buffer[endSep:] def parse_record(self, record): # 0 - Flag # 1 - ID byte (record type) # # n-2 - checksum # n-1 - checksum length = len(record) if length < 3: self.logger.warning("Record: %s - bad checksum + wrong size", self._list2bytes(record)) else: computedChecksum = reduce(lambda x,y: x + y, record[:-2]) recordChecksum = (record[length - 1] << 8) + record[length - 2] if recordChecksum != computedChecksum: self.logger.warning("Record: %s - bad checksum", self._list2bytes(record)) elif record[1] in self._WMRS200_record_types: (expected_length, record_type, record_parser) = self._WMRS200_record_types[record[1]] if expected_length != length: self.logger.warning("%s Record: %s - wrong length (expected %d, received %d)", record_type, self._list2bytes(record), expected_length, length) return else: self.logger.debug("%s Record: %s", record_type, self._list2bytes(record)) record_parser(record) else: self.logger.warning("Unknown record type: %s", self._list2bytes(record)) def _parse_clock_record(self, record): """ Length 11 Example: 00 60 00 00 14 09 1c 04 09 01 a7 Byte Data Comment 0 00 Battery data in high nibble, lowest bit 1 if main unit runs only on battery 1 60 Identifier 2-3 00 00 Unknown 4 14 Minutes: 20 5 09 Hour: 09 6 1c Day: 28 7 04 Month: 04, April 8 09 Year: 2009 (add 2000) 9 01 Time Zone: GMT +1 (highest bit 1 if negative) 10 a7 Checksum: 167 """ power = (record[0]) >> 4 powered = ((power & 0x8) >> 3) == 0 # VERIFIED -- EXTERNAL POWER INDICATOR batteryOK = ((power & 0x4) >> 2) == 0 # VERIFIED -- BATTERY LOW FLAG rf = ((power & 0x2) >> 1) == 0 # CLOCK SYNCHRONIZED FLAG level = (power & 0x1) # What is this??? minute = record[4] hour = record[5] day = record[6] month = record[7] year = 2000 + record[8] consoleDate = "%d/%d/%d %d:%d" % (day, month, year, hour, minute) # Log self.logger.info("Clock %s, power: %s, Powered: %s, Battery: %s, RF: %s", consoleDate, power, powered, batteryOK, rf) def _parse_rain_record(self, record): """ Length 16 Example: 00 41 ff 02 0c 00 00 00 25 00 00 0c 01 01 06 87 Byte Data Comment 0 00 Battery level in high nibble 1 41 Identifier 2-3 ff 02 Rain rate: byte 3 * 256 + byte 2, in inches/hour 4-5 0c 00 Rain last hour: byte 5 * 256 + byte 4, in inches 6-7 00 00 Rain last 24 hours: byte 7 * 256 + byte 6, in inches 8-9 00 25 Total rain since reset date: byte 9 * 256 + byte 8, in inches 10 00 Minute of reset date 11 0c Hour of reset date 12 01 Day of reset date 13 01 Month of reset date 14 06 Year + 2000 of reset date 15 4e Checksum """ batteryOk = (record[0] & 0x40) == 0 # 1 inch = 25,4 mm rate = (record[2] + record[3] * 256) * 0.01 * 25.4 thisHour = (record[4] + record[5] * 256) * 0.01 * 25.4 thisDay = (record[6] + record[7] * 256) * 0.01 * 25.4 total = (record[8] + record[9] * 256) * 0.01 * 25.4 minuteT = record[10] hourT = record[11] dayT = record[12] monthT = record[13] yearT = 2000 + record[14] # Convert rain if the rain gauge is modified if self.rain_gauge_diameter != 0: x = 100.0 ** 2 / self.rain_gauge_diameter ** 2 total = x * total rate = x * rate # Report data self._report_rain(total, rate) # Log self.logger.info("Rain Battery Ok: %s Rate %g, This Hr %g, This Day %g, Total %g since %4d/%2d/%2d %2d:%2d", batteryOk, rate, thisHour, thisDay, total, yearT, monthT, dayT, hourT, minuteT) def _parse_wind_record(self, record): """ Length 10 Example: 00 48 0a 0c 16 e0 02 00 20 76 Byte Data Comment 0 00 Battery level in high nibble 1 48 Identifier 2 0a Wind direction in low nibble, 10 * 360 / 16 = 225 degrees 3 0c Unknown 4-5 16 e0 Wind gust, (low nibble of byte 5 * 256 + byte 4) / 10 5-6 e0 02 Wind average, (high nibble of byte 5 + byte 6 * 16) / 10 7 00 ? 8 20 ? 9 76 Checksum """ batteryOk = (record[0] & 0x40) == 0 dir = record[2] & (0x0f) if dir == 0: dirDeg = 360 else: dirDeg = dir * 360 / 16 dirStr = windDirMap[dir] avgSpeed = 0.1 * ((record[6] << 4) + ((record[5]) >> 4)) gustSpeed = 0.1 * (((record[5] & 0x0F) << 8) + record[4]) # Report Data self._report_wind(dirDeg, avgSpeed, gustSpeed) # Log self.logger.info("Wind Battery Ok: %s direction: %d (%g/%s), gust: %g m/s, avg. speed: %g m/s", batteryOk, dir, dirDeg, dirStr, gustSpeed, avgSpeed) def _parse_barometer_record(self, record): """ Length 7 Example: 00 46 ed 03 ed 33 56 Byte Data Comment 0 00 Unused? 1 46 Identifier 2-3 ed 03 Absolute pressure, low nibble of byte 3 * 256 + byte 2 3 03 High nibble is forecast indicator for absolute pressure 4-5 ed 03 Relative pressure, low nibble of byte 5 * 256 + byte 4 5 03 High nibble is forecast indicator for relative pressure 6 56 """ pressure = (record[3] & (0x0f)) * 256 + record[2] forecast = record[3] >> 4 forecastTxt = forecastMap.get(forecast, str(forecast)) ## Can't use WMRS200 seaLevelPressure (cannot set altitude from wfrog) seaLevelPressure = (record[5] & (0x0f)) * 256 + record[4] slpForecast = record[5] >> 4 slpForecastTxt = forecastMap.get(slpForecast, str(slpForecast)) # Report data if (pressure == 2816): self.logger.warning("Barometer overflow: max measurement value 1050 exceeded, reporting as 1051") self._report_barometer_absolute(1051 + self.pressure_cal) else: self._report_barometer_absolute(pressure + self.pressure_cal) # Log self.logger.info("Barometer Forecast: %s, Absolute pressure: %.1f mb, Sea Level Pressure: %.1f", forecastTxt, pressure, seaLevelPressure) def _parse_temperature_record(self, record): """ Length 11 Example: 20 42 d1 91 00 48 64 00 00 20 90 Byte Data Comment 0 20 Battery level in high nibble. Temp trend in high nibble. 1 42 Identifier 2 d1 Low nibble is device channel number, high nibble humidity trend and smiley code 3-4 91 00 Temperature: (256 * byte 4 + byte 3) / 10 = 14,5 degrees 5 48 Humidity: 72% 6-7 64 00 Dew point: (256 * byte 7 + byte 6) / 10 = 10 degrees 8 00 ? 9 20 ? 10 90 """ batteryOk = (record[0] & 0x40) == 0 # Temperature trend ttrend = (record[0] >> 4) & 0x03 ttrendTxt = trendMap.get(ttrend, str(ttrend)) # Sensor id sensor = record[2] & 0x0f sensorName = thSensors[sensor] # Comfort level and humidity trend comfortLevel = record[2] >> 6 comfortLevelTxt = comfortLevelMap.get(comfortLevel,str(comfortLevel)) htrend = (record[2] >> 4) & 0x03 htrendTxt = trendMap.get(htrend, str(htrend)) # Temperature temp = (((record[4] & 0x0f) * 255.0) + record[3]) / 10.0 if ((record[4] >> 4) == 0x08): temp = temp * -1 # Humidity humidity = record[5] # Station Dew Point dewPoint = (((record[7] & 0x0f) * 255.0) + record[6]) / 10.0 if ((record[7] >> 4) == 0x08): dewPoint = dewPoint * -1 self._report_temperature(temp, humidity, sensor) # Log self.logger.info("Temp Battery Ok: %s Sensor %s Temperature: %g C (%s), Humidity: %d %% (%s, %s), Dew Point: %g C", batteryOk, sensorName, temp, ttrendTxt, humidity, comfortLevelTxt, htrendTxt, dewPoint) def _parse_uv_record(self, record): """ Length 6 Example: 00 47 01 00 48 00 Byte Data Comment 0 00 Battery level in high nibble 1 47 Identifier 2 01 ??? 3 00 UV Index (value 0-11) 4 48 Checksum 5 00 Checksum """ batteryOk = (record[0] & 0x40) == 0 uv = record[3] # Report data self._report_uv(uv) # Log self.logger.info("UV Battery Ok: %s UV Index: %d" % (batteryOk, uv)) name = WMRS200Station.name wfrog-0.8.2+svn953/wfdriver/station/ws23xx.py000066400000000000000000000077531213472117100207640ustar00rootroot00000000000000## Copyright 2010 Laurent Bovet ## derived from ws2300 by Russell Stuart ## ## This file is part of wfrog ## ## wfrog 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 . import time import logging from wfcommon import units class WS2300Station(object): ''' Station driver for LaCrosse WS2300. This driver is a wrapper around ws2300 (http://ace-host.stuart.id.au/russell/files/ws2300/), thus needs this package installed on your system. [Properties] port [string] (optional): The serial port the station is attached to. Defaults to /dev/ttyS0. period [numeric] (optional): Polling interval in seconds. Defaults to 60. ''' port = "/dev/ttyS0" period=60 logger = logging.getLogger('station.ws2300') name = 'LaCrosse WS2300' def run(self, generate_event, send_event, context={}): import ws2300 print(dir(ws2300)) while True: serialPort = ws2300.LinuxSerialPort(self.port) serialPort.open() try: ws = ws2300.Ws2300(serialPort) measures = [ ws2300.Measure.IDS["pa"], ws2300.Measure.IDS["it"], ws2300.Measure.IDS["ih"], ws2300.Measure.IDS["ot"], ws2300.Measure.IDS["oh"], ws2300.Measure.IDS["rh"], ws2300.Measure.IDS["rt"], ws2300.Measure.IDS["ws"], ws2300.Measure.IDS["wsm"], ws2300.Measure.IDS["w0"], ] raw_data = ws2300.read_measurements(ws, measures) data = [ m.conv.binary2value(d) for m, d in zip(measures, data)] finally: serialPort.close() try: e = generate_event('press') e.value = data[0] send_event(e) e = generate_event('temp') e.sensor = 0 e.value = data[1] send_event(e) e = generate_event('hum') e.sensor = 0 e.value = data[2] send_event(e) e = generate_event('temp') e.sensor = 1 e.value = data[3] send_event(e) e = generate_event('hum') e.sensor = 1 e.value = data[4] send_event(e) e = generate_event('rain') e.rate = data[5] e.total = data[6] send_event(e) e = generate_event('wind') e.create_child('mean') e.mean.speed = units.MphToMps(data[7]) e.mean.dir = data[9] e.create_child('gust') e.gust.speed = units.MphToMps(data[8]) e.gust.dir = data[9] send_event(e) except Exception, e: self.logger.error(e) # pause until next update time next_update = self.period - (time.time() % self.period) time.sleep(next_update) wfrog-0.8.2+svn953/wfdriver/station/ws28xx.py000066400000000000000000000132741213472117100207640ustar00rootroot00000000000000## Copyright 2012 Eddi De Pieri ## ## This file is part of wfrog ## ## wfrog 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 . ## To use this module you need to install somewhere ## the library available at https://github.com/dpeddi/ws-28xx.git ## Before you start wfrog you need to export path to ws-28xx module ## export PYTHONPATH=$PYTHONPATH:/path/to/ws-28xx-module ## then you can start wfrog. ## The ws-28xx at github and driver are still under heavy ## development. Feel free to contribute. ## 2012-04-27: my station stopped working. I've imported a US unit ## while I live in EU. I've asked support for my unit ## both to lacrossetechnology.com and lacrossetecnhology.fr ## ## Now I'm in the situation that both give email support ## but I can't get my station back to repair. import time import logging def detect(): try: station = WS28xxStation() except: print "ws28xx: failed loading modules" station = None return station class WS28xxStation(object): logger = logging.getLogger('station.ws28xx') name = "LaCrosse WS28xx" def run(self, generate_event, send_event, context={}): import HeavyWeatherService import CWeatherTraits CWeatherTraits = CWeatherTraits.CWeatherTraits() myCCommunicationService = HeavyWeatherService.CCommunicationService() HeavyWeatherService.CDataStore.setCommModeInterval(myCCommunicationService.DataStore,3) time.sleep(5) if HeavyWeatherService.CDataStore.getDeviceId(myCCommunicationService.DataStore) == -1: TimeOut = HeavyWeatherService.CDataStore.getPreambleDuration(myCCommunicationService.DataStore) + HeavyWeatherService.CDataStore.getRegisterWaitTime(myCCommunicationService.DataStore) ID=[0] ID[0]=0 print "Press [v] key on Weather Station" HeavyWeatherService.CDataStore.FirstTimeConfig(myCCommunicationService.DataStore,ID,TimeOut) HeavyWeatherService.CDataStore.setDeviceRegistered(myCCommunicationService.DataStore, True); #temp hack Weather = [0] Weather[0]=[0] TimeOut = HeavyWeatherService.CDataStore.getPreambleDuration(myCCommunicationService.DataStore) + HeavyWeatherService.CDataStore.getRegisterWaitTime(myCCommunicationService.DataStore) HeavyWeatherService.CDataStore.GetCurrentWeather(myCCommunicationService.DataStore,Weather,TimeOut) time.sleep(1) while True: if HeavyWeatherService.CDataStore.getRequestState(myCCommunicationService.DataStore) == HeavyWeatherService.ERequestState.rsFinished \ or HeavyWeatherService.CDataStore.getRequestState(myCCommunicationService.DataStore) == HeavyWeatherService.ERequestState.rsINVALID: TimeOut = HeavyWeatherService.CDataStore.getPreambleDuration(myCCommunicationService.DataStore) + HeavyWeatherService.CDataStore.getRegisterWaitTime(myCCommunicationService.DataStore) HeavyWeatherService.CDataStore.GetCurrentWeather(myCCommunicationService.DataStore,Weather,TimeOut) try: if abs(CWeatherTraits.TemperatureNP() - myCCommunicationService.DataStore.CurrentWeather._IndoorTemp ) > 0.001: e = generate_event('temp') e.sensor = 0 e.value = myCCommunicationService.DataStore.CurrentWeather._IndoorTemp send_event(e) if abs(CWeatherTraits.HumidityNP() - myCCommunicationService.DataStore.CurrentWeather._IndoorHumidity ) > 0.001: e = generate_event('hum') e.sensor = 0 e.value = myCCommunicationService.DataStore.CurrentWeather._IndoorHumidity send_event(e) if abs(CWeatherTraits.TemperatureNP() - myCCommunicationService.DataStore.CurrentWeather._OutdoorTemp ) > 0.001: e = generate_event('temp') e.sensor = 1 e.value = myCCommunicationService.DataStore.CurrentWeather._OutdoorTemp send_event(e) if abs(CWeatherTraits.HumidityNP() - myCCommunicationService.DataStore.CurrentWeather._OutdoorHumidity ) > 0.001: e = generate_event('hum') e.sensor = 1 e.value = myCCommunicationService.DataStore.CurrentWeather._OutdoorHumidity send_event(e) if abs(CWeatherTraits.PressureNP() - myCCommunicationService.DataStore.CurrentWeather._PressureRelative_hPa ) > 0.001: e = generate_event('press') e.value = myCCommunicationService.DataStore.CurrentWeather._PressureRelative_hPa send_event(e) if CWeatherTraits.RainNP() != myCCommunicationService.DataStore.CurrentWeather._RainTotal: e = generate_event('rain') e.rate = myCCommunicationService.DataStore.CurrentWeather._Rain1H e.total = myCCommunicationService.DataStore.CurrentWeather._RainTotal send_event(e) if abs(CWeatherTraits.WindNP() - myCCommunicationService.DataStore.CurrentWeather._WindSpeed) > 0.001: e = generate_event('wind') e.create_child('mean') e.mean.speed = myCCommunicationService.DataStore.CurrentWeather._WindSpeed e.mean.dir = myCCommunicationService.DataStore.CurrentWeather._WindDirection * 360 / 16 e.create_child('gust') e.gust.speed = myCCommunicationService.DataStore.CurrentWeather._Gust e.gust.dir = myCCommunicationService.DataStore.CurrentWeather._GustDirection * 360 / 16 send_event(e) except Exception, e: self.logger.error(e) time.sleep(5) name = WS28xxStation.name wfrog-0.8.2+svn953/wfdriver/test/000077500000000000000000000000001213472117100165165ustar00rootroot00000000000000wfrog-0.8.2+svn953/wfdriver/test/call-test.py000066400000000000000000000004171213472117100207620ustar00rootroot00000000000000import sys, os.path if __name__ == "__main__": sys.path.append(os.path.abspath(sys.path[0] + '/..')) from wfdriver import Driver from output import call def out(event): print '> '+str(event) call.call = out Driver.DEFAULT_CONFIG='call-test.yaml' Driver().run() wfrog-0.8.2+svn953/wfdriver/test/call-test.yaml000066400000000000000000000000571213472117100212740ustar00rootroot00000000000000station: !random-simulator {} output: !call {} wfrog-0.8.2+svn953/wfdriver/test/csv.yaml000066400000000000000000000001021213472117100201660ustar00rootroot00000000000000station: !random-simulator {} output: !service { name: events } wfrog-0.8.2+svn953/wfdriver/test/multi-test.yaml000066400000000000000000000002701213472117100215100ustar00rootroot00000000000000station: !random-simulator {} output: !multi children: one: !service name: out instance: !stdio-out {} two: !service name: out wfrog-0.8.2+svn953/wfdriver/wfdriver.py000077500000000000000000000074771213472117100177630ustar00rootroot00000000000000#!/usr/bin/python ## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . # Before loading other modules add wfrog directory to sys.path to be able to use wfcommon import os.path import sys if __name__ == "__main__": sys.path.append(os.path.abspath(sys.path[0] + '/..')) import station import output import wfcommon.generic from output import stdio import optparse import logging import wfcommon.config from threading import Thread from Queue import Queue, Full import event def gen(type): e = event.Event(type) return e class Driver(object): ''' Root Elements ------------- station [station]: Weather station providing the events. output [output]: Destination of events sent by this driver. Typically a WESTEP connector if running standalone. logging [logging configuration] (optional): See below the Logging Configuration section. ''' logger = logging.getLogger('wfdriver') # default values output = stdio.StdioOutput() queue_size = 10 configurer = None def __init__(self, opt_parser=optparse.OptionParser()): # Prepare the configurer module_map = ( ( "Stations" , station ), ( "Output" , output), ( "Generic Elements", wfcommon.generic) ) self.configurer = wfcommon.config.Configurer(module_map) self.opt_parser = opt_parser self.configurer.add_options(self.opt_parser) def configure(self, config_file, settings_file, embedded): # Parse the options and create object trees from configuration (options, args) = self.opt_parser.parse_args() (config, context) = self.configurer.configure(options, self, config_file, settings_file, embedded) # Initialize the driver from object trees self.station = config['station'] if config.has_key('output'): self.output = config['output'] if config.has_key('queue_size'): self.queue_size = config['queue_size'] self.event_queue = Queue(self.queue_size) def enqueue_event(self,event): self.logger.debug('Enqueuing: %s, Queue size: %d', event, self.event_queue.qsize()) try: self.event_queue.put(event, block=False) except Full: self.logger.critical('Consumer of events is dead or not consuming quickly enough') def output_loop(self): while True: event = self.event_queue.get(block=True) try: self.output.send_event(event) except Exception: self.logger.exception("Could not send event to " + str(self.output)) def run(self, config_file="config/wfdriver.yaml", settings_file=None, embedded=False): self.configure(config_file, settings_file, embedded) # Start the logger thread logger_thread = Thread(target=self.output_loop) logger_thread.setDaemon(True) logger_thread.start() self.station.run(gen, self.enqueue_event) if __name__ == "__main__": driver = Driver() driver.logger.debug("Started main()") driver.run() driver.logger.debug("Finished main()") wfrog-0.8.2+svn953/wflogger/000077500000000000000000000000001213472117100155235ustar00rootroot00000000000000wfrog-0.8.2+svn953/wflogger/__init__.py000066400000000000000000000000001213472117100176220ustar00rootroot00000000000000wfrog-0.8.2+svn953/wflogger/collector/000077500000000000000000000000001213472117100175115ustar00rootroot00000000000000wfrog-0.8.2+svn953/wflogger/collector/__init__.py000066400000000000000000000024211213472117100216210ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import yaml import aggregator import flush import xmlfile import buffer # YAML mappings class YamlAggregatorCollector(aggregator.AggregatorCollector, yaml.YAMLObject): yaml_tag = u'!aggregator' class YamlFlushCollector(flush.FlushCollector, yaml.YAMLObject): yaml_tag = u'!flush' class YamlBufferCollector(buffer.BufferCollector, yaml.YAMLObject): yaml_tag = u'!buffer' class YamlXmlFileCollector(xmlfile.XmlFileCollector, yaml.YAMLObject): yaml_tag = u'!xmlfile' wfrog-0.8.2+svn953/wflogger/collector/aggregator.py000066400000000000000000000160201213472117100222040ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import logging import base import datetime import wfcommon.meteo MAX_TH_SENSORS = 10 # 0 ..9 MAIN_TH_SENSOR = 1 # sensor number 1 is the main TH sensor INT_TH_SENSOR = 0 # sensor number 0 is the interior TH sensor class AggregatorCollector(base.BaseCollector): ''' Collects events, compute aggregated values incrementally and issues samples to an underlying storage on 'flush events'. Typically wrapped in a !flush element to receive the 'flush events'. [ Properties ] storage [storage]: The underlying storage receiving the aggregated samples. ''' storage = None logger = logging.getLogger('collector.aggregator') ## Init internal data _rain_last = None initialized = False def init(self): if not self.initialized: self._new_period() self.initialized = True def _new_period(self): ## Temperature (up to 10 sensors, sensor number 1 is the main sensor) self._temp = {} for sensor in xrange(MAX_TH_SENSORS): self._temp[sensor] = [] ## Humidity (up to 10 sensors, sensor number 1 is the main sensor) self._hum = {} for sensor in xrange(MAX_TH_SENSORS): self._hum[sensor] = [] ## Wind self._wind = [] self._wind_dir = [] ## Wind gust self._wind_gust = 0.0 self._wind_gust_dir = None ## Rain if self._rain_last != None: self._rain_first = self._rain_last else: self._rain_first = None self._rain_rate = 0.0 ## Pressure self._pressure = [] ## UV self._uv_index = None ## Solar Rad self._solar_rad = [] ## Log self._timestamp_last = None self.logger.info ('New period') def _report_rain(self, total, rate): if self._rain_first == None: self._rain_first = total self._rain_last = total if self._rain_rate < rate: self._rain_rate = rate def _report_wind(self, avgSpeed, dirDeg, gustSpeed, gustDir): self._wind_dir.append((avgSpeed, dirDeg)) # Keep vector to calculate composite wind direction self._wind.append(avgSpeed) if self._wind_gust < gustSpeed: self._wind_gust = gustSpeed self._wind_gust_dir = gustDir def _report_barometer_sea_level(self, pressure): self._pressure.append(pressure) def _report_temperature(self, temp, sensor): if sensor >= 0 and sensor < MAX_TH_SENSORS: self._temp[sensor].append(temp) def _report_humidity(self, humidity, sensor): if sensor >= 0 and sensor < MAX_TH_SENSORS: self._hum[sensor].append(humidity) def _report_uv(self, uv_index): if self._uv_index == None or self._uv_index < uv_index: self._uv_index = uv_index def _report_solar_rad(self, solar_rad): self._solar_rad.append(solar_rad) def get_data(self): data = { 'temp': None, 'hum': None, 'pressure' : None, 'wind': None, 'wind_dir': None, 'wind_gust' : None, 'wind_gust_dir': None, 'rain': None, 'rain_rate': None, 'uv_index' : None, 'dew_point' : None, 'solar_rad' : None } for sensor in xrange(MAX_TH_SENSORS): if sensor == MAIN_TH_SENSOR: tsn = 'temp' hsn = 'hum' elif sensor == INT_TH_SENSOR: tsn = 'tempint' hsn = 'humint' else: tsn = 'temp%d' % sensor hsn = 'hum%d' % sensor if len(self._temp[sensor]) > 0: data[tsn] = round(sum(self._temp[sensor])/len(self._temp[sensor]), 1) elif sensor == MAIN_TH_SENSOR: self.logger.warning('Missing temperature data from main sensor') if len(self._hum[sensor]) > 0: data[hsn] = round(sum(self._hum[sensor])/len(self._hum[sensor]), 1) elif sensor == MAIN_TH_SENSOR: self.logger.warning('Missing humidity data from main sensor') if len(self._wind) > 0: data['wind'] = round(sum(self._wind)/len(self._wind), 1) data['wind_dir'] = round(wfcommon.meteo.WindPredominantDirection(self._wind_dir), 1) data['wind_gust_dir'] = self._wind_gust_dir # Wind gust cannot be smaller than wind average # (might happen due to different sampling periods) if data['wind'] <= self._wind_gust: data['wind_gust'] = self._wind_gust else: data['wind_gust'] = round(data['wind'], 1) else: self.logger.warning('Missing wind data') if self._rain_first is not None: if self._rain_last > self._rain_first: data['rain'] = round(self._rain_last - self._rain_first, 1) data['rain_rate'] = round(self._rain_rate, 1) else: data['rain'] = 0.0 data['rain_rate'] = 0.0 else: self.logger.warning('Missing rain data') if len(self._pressure) > 0: ## QFF pressure (Sea Level Pressure) pressure = round(sum(self._pressure)/len(self._pressure), 1) data['pressure'] = pressure else: self.logger.warning('Missing pressure data') if data['temp'] and data['hum']: ## Dew Point data['dew_point'] = round(wfcommon.meteo.DewPoint(data['temp'], data['hum'], 'vaDavisVP'), 1) ## UV if self._uv_index != None: data['uv_index'] = int(self._uv_index) ## Solar rad if len(self._solar_rad) > 0: solar_rad = round(sum(self._solar_rad)/len(self._solar_rad), 1) data['solar_rad'] = solar_rad data['localtime'] = self._timestamp_last self.logger.debug('data = %s', data) return data def flush(self, context): if self._timestamp_last is not None: sample = self.get_data() self._new_period() self.logger.debug("Flushing sample: "+repr(sample)) self.storage.write_sample(sample, context=context) wfrog-0.8.2+svn953/wflogger/collector/base.py000066400000000000000000000104411213472117100207750ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import logging import wfcommon.meteo from wfcommon.formula.base import AverageFormula import datetime class BaseCollector(object): ''' Base class for collectors. ''' _timestamp_last = None _temp_last = None _hum_last = None _mean_temp = None _mean_temp_last_time = None storage = None def send_event(self, event, context={}): self.init() if hasattr(event, "timestamp") and event.timestamp is not None: self._timestamp_last = event.timestamp else: self._timestamp_last = datetime.datetime.now() if event._type == "_flush": self.flush(context) else: if event._type == 'rain': self._report_rain(event.total, event.rate) elif event._type == 'wind': self._report_wind(event.mean.speed, event.mean.dir, event.gust.speed, event.gust.dir) elif event._type == 'press': if hasattr(event, 'code'): if event.code == 'RAW' or event.code == 'QFE': self._report_barometer_absolute(event.value) else: self._report_barometer_sea_level(event.value) else: self._report_barometer_absolute(event.value, context) elif event._type == 'temp': self._report_temperature(event.value, event.sensor) if event.sensor == 1: self._temp_last = event.value elif event._type == 'hum': self._report_humidity(event.value, event.sensor) if event.sensor == 1: self._hum_last = event.value elif event._type == 'uv': self._report_uv(event.value) elif event._type == 'rad': self._report_solar_rad(event.value) def _get_mean_temp(self, current_temp, context): # Last 12 hours mean temp if self.storage is None: return current_temp if self._mean_temp != None: if (datetime.datetime.now()-self._mean_temp_last_time).seconds < 3600: # New value each hour return self._mean_temp try: average = AverageFormula(self.storage.keys().index('temp')) for sample in self.storage.samples(datetime.datetime.now() - datetime.timedelta(hours=12), context=context): average.append(sample) self._mean_temp = average.value() if self._mean_temp is None: return current_temp self._mean_temp_last_time = datetime.datetime.now() self.logger.info("Calculated last 12 hours mean temp: %4.1f" % self._mean_temp) return self._mean_temp except Exception, e: self.logger.warning("Error calculating last 12 hours mean temp: %s, returning current temperature" % str(e)) return current_temp return current_temp def _report_barometer_absolute(self, pressure, context): if self._temp_last != None and self._hum_last != None: seaLevelPressure = wfcommon.meteo.StationToSeaLevelPressure( pressure, context['altitude'], self._temp_last, self._get_mean_temp(self._temp_last, context), self._hum_last, 'paDavisVP') self._report_barometer_sea_level(seaLevelPressure) wfrog-0.8.2+svn953/wflogger/collector/buffer.py000066400000000000000000000122621213472117100213370ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import logging import base import datetime import wfcommon.meteo import itertools import logging import heapq class BufferCollector(object): ''' Collects events and wait for some rentention time before sending them to an underlying collector. This allow for later receiving of events dated before the buffered ones. This collector re-orders them. This collector replaces the !flush collector to support weather stations with internal log. [ Properties ] collector [collector]: The underlying collector receiving the timestamped events in the correct order. period [numeric] (optional): Minimum number of seconds between flush events. Defaults to 600. retention [numeric] (optional): The retention time (in seconds) for current events before forwarding them to give a chance to upcoming "events from the past" to be treated. Defaults to 30. ''' collector = None period = 600 retention = 30 queue = None # Queue for retained events count = itertools.count() last_flush = None # Timestamp of the event following the last flush event last_receive_past = None # When the last event arrived "from the past" last_sent = None # Timestamp of the last event forwarded logger = logging.getLogger('collector.buffer') def init(self): if self.queue is None: self.queue = [] self.retention_delta = datetime.timedelta(0, self.retention) self.period_delta = datetime.timedelta(0, self.period) self.last_sent = datetime.datetime(2001,1,1) self.last_receive_past = datetime.datetime(2001,1,1) def send_event(self, event, context={}): self.init() now = datetime.datetime.now() expiry = now - self.retention_delta if not hasattr(event, 'timestamp') or event.timestamp is None: # If event has no timestamp, give it one. event.timestamp = now if event.timestamp < expiry: self.logger.debug("Got event from past: %s", event) # The event is historic, we send it immediately (assuming they come ordered). if event.timestamp >= self.last_sent: # Send it only if is not older as the last one we sent. Otherwise, discard it. self.forward_event(event, context) self.last_sent = event.timestamp self.last_receive_past = now else: # Put the event in the retention queue, it is recent enough. self.logger.debug("Got a recent %s event, keep it in retention queue", event._type) self.push(event) # Dump the event queue if we are no more receiving events from the past if self.last_receive_past < expiry: self.logger.debug("Dumping retention queue.") while True: event_to_send = self.pop_older(expiry) if event_to_send is not None: self.forward_event(event_to_send, context) self.last_sent = event_to_send.timestamp else: break def push(self, event): heapq.heappush(self.queue, (event.timestamp, self.count.next(), event)) def oldest(self): if len(self.queue) > 0: (timestamp, count, event) = self.queue[0] return event else: return None def pop_older(self, timestamp): if len(self.queue) ==0: return None if(self.queue[0][0] < timestamp): (timestamp, count, event) = heapq.heappop(self.queue) return event else: return None def forward_event(self, event, context): if self.last_flush is None: # Initialize flush timer with first event. self.last_flush = event.timestamp flush_time = self.last_flush + self.period_delta if event.timestamp > flush_time: # Flushes if needed flush_event = FlushEvent() flush_event.timestamp = flush_time self.do_send(flush_event, context) self.last_flush = event.timestamp self.do_send(event, context) def do_send(self, event, context): self.logger.debug("Forwarding event: %s", event) self.collector.send_event(event, context) class FlushEvent(object): _type = '_flush' def __str__(self): return "*FLUSH*" wfrog-0.8.2+svn953/wflogger/collector/flush.py000066400000000000000000000053201213472117100212040ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import logging import time import datetime class FlushEvent(object): _type = '_flush' def __str__(self): return "*FLUSH*" class FlushCollector(object): ''' Forwards incoming events to a wrapped collector and periodically issues a 'flush event'. Flush events are issued only together with a forwarded event, there is no internal thread and no flushing occurs during periods where no events are received. If an event is timestamped and older than an already treated event, it is discarded. [ Properties ] collector [collector]: The wrapped collector the events are forwarded to. period [numeric] (optional): Minimum number of seconds between flush events. Defaults to 10. ''' period = 300 collector = None last_flush_time = None max_event_time = datetime.datetime(2001, 01, 01) logger = logging.getLogger('collector.flush') def send_event(self, event, context={}): if self.collector == None: raise Exception('Attribute collector must be set') now = datetime.datetime.now() if hasattr(event, "timestamp"): timestamp = event.timestamp else: timestamp = now if timestamp >= self.max_event_time and timestamp >= now - datetime.timedelta(0, self.period): self.max_event_time = timestamp else: self.logger.debug("Discarded old event") return current_time = time.time() # Initialization. Do not flush at startup if self.last_flush_time == None: self.last_flush_time = current_time # Send the event self.collector.send_event(event, context) # Flush if needed. if self.last_flush_time + self.period <= current_time: self.logger.debug('Flushing') self.last_flush_time = current_time self.collector.send_event(FlushEvent(), context) wfrog-0.8.2+svn953/wflogger/collector/xmlfile.py000066400000000000000000000065131213472117100215300ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import logging import base import time import os.path from lxml import etree from lxml.builder import E def element(parent, name): result = parent.find(name) if result is None: result = etree.SubElement(parent, name) return result class XmlFileCollector(base.BaseCollector): ''' Keep the latest event values and flush them in an XML file on 'flush events'. Should be wrapped in a !flush elements to receive the 'flush events'. [ Properties ] path [string]: Location of the XML file to write. ''' path = None doc = None initialized = False logger = logging.getLogger('collector.xmlfile') def init(self): if not self.initialized: self.reset() self.initialized = True def _report_rain(self, total, rate): rain_elt = element(self.doc, 'rain') element(rain_elt, 'rate').text = str(rate) def _report_wind(self, avgSpeed, dirDeg, gustSpeed, gustDir): wind_elt = element(self.doc, 'wind') element(wind_elt, 'avgSpeed').text = str(avgSpeed) element(wind_elt, 'dirDeg').text = str(dirDeg) element(wind_elt, 'gustSpeed').text = str(gustSpeed) def _report_barometer_sea_level(self, pressure): press_elt = element(self.doc, 'barometer') element(press_elt, 'pressure').text = str(pressure) def _report_temperature(self, temp, sensor): if sensor == 0: temp_elt = element(self.doc, 'thInt') elif sensor == 1: temp_elt = element(self.doc, 'th1') else: return element(temp_elt, 'temp').text = str(temp) def _report_humidity(self, humidity, sensor): if sensor == 0: hum_elt = element(self.doc, 'thInt') elif sensor == 1: hum_elt = element(self.doc, 'th1') else: return element(hum_elt, 'humidity').text = str(humidity) def _report_uv(self, uv_index): return def _report_solar_rad(self, solar_rad): return def flush(self, context={}): time_elt = element(self.doc, 'time') time_elt.text = time.strftime("%Y-%m-%d %H:%M:%S") doc_string = etree.tostring(self.doc) self.logger.debug("Flushing: %s to %s", doc_string, self.path) dir = os.path.realpath(os.path.dirname(self.path)) if not os.path.exists(dir): os.makedirs(dir) file = open(self.path, 'w') file.write(doc_string) file.close() def reset(self, context={}): self.doc = E.current() wfrog-0.8.2+svn953/wflogger/config/000077500000000000000000000000001213472117100167705ustar00rootroot00000000000000wfrog-0.8.2+svn953/wflogger/config/wflogger.yaml000066400000000000000000000032071213472117100214720ustar00rootroot00000000000000init: storage: !service name: storage instance: !include { path: ../../wfcommon/config/storage.yaml } input: !service name: events # Use embedded wfdriver to receive events instance: !function {} #input: !http-in { port: 8888 } #input: !stdio-in {} collector: !multi children: aggregator : !buffer period: 600 collector: !aggregator storage : !service name: storage current : !flush period: 10 collector: !user choices: root: !xmlfile path: /var/lib/wfrog/wfrog-current.xml storage : !service name: storage default: !xmlfile path: data/wfrog-current.xml storage : !service name: storage embed: wfdriver: { config: ../../wfdriver/config/embedded.yaml } logging: level: info filename: !user choices: root: /var/log/wflogger.log default: wflogger.log ## By default wfrog uses a Rotating file handler. To set up different handlers ## uncomment the following section. (Warning: does not work under python > 2.7) #handlers: # default: # level: debug # handler: !include # path: ../../wfcommon/config/loghandler.yaml # variables: # process: wflogger # #mail: # # level: critical # # handler: !include # # path: ../../wfcommon/config/mailloghandler.yaml wfrog-0.8.2+svn953/wflogger/config/wfrog.yaml000066400000000000000000000060151213472117100210020ustar00rootroot00000000000000init: storage: !service name: storage instance: !include path: ../../wfcommon/config/storage.yaml accu_3h: !service name: accu_3h instance: !include path: ../../wfrender/config/default/chart_accumulator.yaml variables: slice: minute span: 180 accu_24h: !service name: accu_24h instance: !include path: ../../wfrender/config/default/chart_accumulator.yaml variables: slice: hour span: 24 accu_7d: !service name: accu_7d instance: !include path: ../../wfrender/config/default/chart_accumulator.yaml variables: slice: hour span: 168 accu_30d: !service name: accu_30d instance: !include path: ../../wfrender/config/default/chart_accumulator.yaml variables: slice: day span: 31 accu_365d_w: !service name: accu_365d_w instance: !include path: ../../wfrender/config/default/chart_accumulator.yaml variables: slice: week span: 60 accu_365d_m: !service name: accu_365d_m instance: !include path: ../../wfrender/config/default/table_accumulator.yaml variables: slice: month span: 12 input: !service name: events # Use embedded wfdriver to receive events instance: !function {} collector: !multi children: aggregator : !buffer period: 600 collector: !aggregator storage : !service name: storage current : !flush period: 10 collector: !user choices: root: !xmlfile path: /var/lib/wfrog/wfrog-current.xml storage : !service name: storage default: !xmlfile path: data/wfrog-current.xml storage : !service name: storage embed: wfdriver: { config: ../../wfdriver/config/embedded.yaml } wfrender: { config: ../../wfrender/config/embedded.yaml } logging: level: debug filename: !user choices: root: /var/log/wfrog.log default: wfrog.log ## By default wfrog uses a Rotating file handler. To set up different handlers ## uncomment the following section. (Warning: does not work under python > 2.7) #handlers: # default: # level: debug # # handler: !include # path: ../../wfcommon/config/loghandler.yaml # variables: # process: wfrender # # ## Uncomment to receive mail on critical events # #mail: # # level: critical # # handler: !include # # path: ../../wfcommon/config/mailloghandler.yaml wfrog-0.8.2+svn953/wflogger/input/000077500000000000000000000000001213472117100166625ustar00rootroot00000000000000wfrog-0.8.2+svn953/wflogger/input/__init__.py000066400000000000000000000023261213472117100207760ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import yaml import function import stdio import http import atom # YAML mappings class YamlFunctionInput(function.FunctionInput, yaml.YAMLObject): yaml_tag = u'!function' class YamlStdioInput(stdio.StdioInput, yaml.YAMLObject): yaml_tag = u'!stdio-in' class YamlHttpInput(http.HttpInput, yaml.YAMLObject): yaml_tag = u'!http-in' class YamlAtomInput(atom.AtomInput, yaml.YAMLObject): yaml_tag = u'!atom-in'wfrog-0.8.2+svn953/wflogger/input/atom.py000066400000000000000000000124431213472117100202000ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import logging import base import time import datetime class AtomInput(base.XmlInput): """ Poll an Atom feed containing events according to the WESTEP ATOM transport (which is not yet formally defined). [ Properties ] url [string]: The URL of the feed to poll. period [numeric] (optional): The number of seconds between two polls. Defaults to 60. """ url = None period = 60 logger = logging.getLogger('input.atom') def do_run(self): import feedparser # Tweek feedparser to accept XML as content feedparser._FeedParserMixin.unknown_starttag = feedparser_unknown_starttag feedparser._FeedParserMixin.unknown_endtag = feedparser_unknown_endtag feedparser._sanitizeHTML = lambda source, encoding: source self.logger.debug('Starting') # Does not accept events pre-dating the startup self.last_event = time.gmtime() if self.url == None: raise Exception('Attribute url must be set') while True: self.logger.debug("Reading feed") feed = feedparser.parse(self.url) last_update = self.last_event new_events=0 old_events=0 off = datetime.datetime.now() - datetime.datetime.utcnow() for entry in feed.entries: if entry.updated_parsed > self.last_event: new_events = new_events + 1 event = entry.content[0]['value'] timestamp = (datetime.datetime(*(entry.updated_parsed[0:6]))+off).replace(microsecond=0) self.process_message(event, timestamp) else: old_events = old_events + 1 if entry.updated_parsed > last_update: last_update = entry.updated_parsed if new_events: self.logger.info("Got %s new events dated %s UTC", new_events, time.asctime(last_update)) if old_events: self.logger.debug("Skipped %s old events dated before %s UTC", old_events, time.asctime(self.last_event)) if last_update > self.last_event: self.last_event = last_update # Notifies errors if feed.bozo: self.logger.warn('Unable to open feed %s: %s', self.url, feed.bozo_exception) time.sleep(self.period) def feedparser_unknown_starttag(self, tag, attrs): attrs = [(k.lower(), v) for k, v in attrs] attrs = [(k, k in ('rel', 'type') and v.lower() or v) for k, v in attrs] attrsD = dict(attrs) for prefix, uri in attrs: if prefix.startswith('xmlns:'): self.trackNamespace(prefix[6:], uri) elif prefix == 'xmlns': self.trackNamespace(None, uri) if self.incontent: tag = tag.split(':')[-1] return self.handle_data('<%s%s>' % (tag, ''.join([' %s="%s"' % t for t in attrs])), escape=0) if tag.find(':') <> -1: prefix, suffix = tag.split(':', 1) else: prefix, suffix = '', tag prefix = self.namespacemap.get(prefix, prefix) if prefix: prefix = prefix + '_' if (not prefix) and tag not in ('title', 'link', 'description', 'name'): self.intextinput = 0 if (not prefix) and tag not in ('title', 'link', 'description', 'url', 'href', 'width', 'height'): self.inimage = 0 methodname = '_start_' + prefix + suffix try: method = getattr(self, methodname) return method(attrsD) except AttributeError: return self.push(prefix + suffix, 1) def feedparser_unknown_endtag(self, tag): if tag.find(':') <> -1: prefix, suffix = tag.split(':', 1) else: prefix, suffix = '', tag prefix = self.namespacemap.get(prefix, prefix) if prefix: prefix = prefix + '_' methodname = '_end_' + prefix + suffix try: method = getattr(self, methodname) method() except AttributeError: self.pop(prefix + suffix) if self.incontent: tag = tag.split(':')[-1] self.handle_data('' % tag, escape=0) if self.basestack: self.basestack.pop() if self.basestack and self.basestack[-1]: self.baseuri = self.basestack[-1] if self.langstack: self.langstack.pop() if self.langstack: self.lang = self.langstack[-1] wfrog-0.8.2+svn953/wflogger/input/base.py000066400000000000000000000067131213472117100201550ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import logging import urllib class XmlInput(object): ''' validate [true|false] (optional): Whether to validate or not the events against the WESTEP XML schema. Validation errors are reported using the log system but do not discard the event. namespace [string] (optional): If validation is activated, specifie the default namespace of events. Defaults to 'http://www.westep.org/2010/westep'. location [url] (optional): If validation is activated, location of the WESTEP XML schema. Defaults to 'http://wfrog.googlecode.com/svn/trunk/xsd/westep.xsd'. ''' _element_doc=True send_event = None logger = logging.getLogger("input.xml") validate = False schema = None namespace = 'http://www.westep.org/2010/westep' location = 'http://wfrog.googlecode.com/svn/trunk/xsd/westep.xsd' def run(self, send_event): self.send_event = send_event self.do_run() def process_message(self, message, timestamp=None): from lxml import objectify self.logger.debug("Received: %s ", message) if self.validate: from lxml import etree if self.schema is None: self.schema = etree.XMLSchema(file=urllib.urlopen(self.location)) parsed_message = etree.fromstring(message) if not parsed_message.nsmap.has_key(None): #if no default namespace specified, set it new_element = etree.Element(parsed_message.tag, attrib=parsed_message.attrib, nsmap={ None: self.namespace }) new_element.extend(parsed_message.getchildren()) parsed_message = etree.fromstring(etree.tostring(new_element)) if parsed_message.tag.startswith('{'+self.namespace+'}'): if not self.schema.validate(parsed_message): log = self.schema.error_log error = log.last_error self.logger.error("XML validation error: %s", error) else: self.logger.info('Element not in standard namespace, considered as extension: %s', parsed_message.tag) event = objectify.XML(message) event._type = event.tag.replace('{'+self.namespace+'}','') # transform the objectified XML to a pure python object to be able to add typed fields pure_event = Event() for el in event.iterchildren(): pure_event.__setattr__(el.tag, el) if(timestamp): pure_event.timestamp = timestamp self.send_event(pure_event) class Event(object): passwfrog-0.8.2+svn953/wflogger/input/function.py000066400000000000000000000026151213472117100210650ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import logging import wfcommon.generic.wrapper class FunctionInput(wfcommon.generic.wrapper.ElementWrapper): """ Input receiving events as method calls. On any method call on this object, the first argument is considered being an event. Usually used as a registered !service. """ send_event = None logger = logging.getLogger("input.function") def run(self, send_event): self.send_event = send_event def _call(self, attr, *args, **keywords): if self.send_event: self.logger.debug('Calling send_event') self.send_event(args[0]) wfrog-0.8.2+svn953/wflogger/input/http.py000066400000000000000000000046641213472117100202250ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer import logging import base server_map = {} class HTTPEventHandler(BaseHTTPRequestHandler): protocol_version = 'HTTP/1.1' process_message = None def __init__(self, request, client_address, server): global server_map self.input = server_map[server] BaseHTTPRequestHandler.__init__(self, request, client_address, server) def do_POST(self): clen = self.headers.getheader('content-length') if clen: clen = int(clen) else: self.send_error(411) message = self.rfile.read(clen) self.input.process_message(message) self.send_response(200) self.send_header('Content-length', 0) self.end_headers() def do_GET(self): self.send_response(200) text="Ready to receive events." self.send_header('Content-type', "text/html") self.send_header('Content-length', len(text)) self.end_headers() self.wfile.write(text) class HttpInput(base.XmlInput): """ Listen to HTTP events according to WESTEP HTTP transport. [ Properties ] port [numeric] (optional): The TCP port listening to events. Default to 8888. """ port = 8888 logger = logging.getLogger("input.http") def do_run(self): self.listen_http(self.port) def listen_http(self, port): self.logger.info("Starting WESTEP HTTP transport listening on port "+str(port)) global server_map server = HTTPServer(('', port), HTTPEventHandler) server_map[server] = self server.serve_forever() wfrog-0.8.2+svn953/wflogger/input/stdio.py000066400000000000000000000032341213472117100203600ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import logging import base from StringIO import StringIO import sys class StdioInput(base.XmlInput): """ Receives events on standard input according to WESTEP STDIO transport. """ logger = logging.getLogger('input.stdio') def do_run(self): self.logger.debug('Starting') end = False buffer = StringIO() while True: line = sys.stdin.readline() if not line: break if line.strip() == "": self.logger.debug('Got empty line') message = buffer.getvalue().strip() if not message == "": # skip additional emtpy lines self.process_message(buffer.getvalue()) buffer.close() buffer = StringIO() else: buffer.write(line.strip()) wfrog-0.8.2+svn953/wflogger/setup.py000066400000000000000000000131411213472117100172350ustar00rootroot00000000000000#!/usr/bin/python ## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import sys import os import os.path import yaml import logging import inspect import wfcommon.config import wfdriver.station class SetupClient(object): '''Interactive setup for user settings''' logger = logging.getLogger('setup') def setup_settings(self, settings_def_file, source_file, target_file): self.logger.debug('Current settings file: '+str(source_file)) self.logger.debug('New settings file:'+target_file) defs = yaml.load( file(settings_def_file, 'r') ) if source_file is not None: source = yaml.load( file(source_file, 'r') ) else: source = {} target = {} try: os.makedirs(os.path.dirname(target_file)) except: pass # First question is about the station section = {} section['name'] = "station" section['description'] = "Station information" section['type'] = 'dict' question = {} question['name'] = "driver" question['description'] = "the driver for your station model" question['type'] = 'choice' question['default'] = 'none' question['choices'] = {} section['children'] =[question] stations = inspect.getmembers(wfdriver.station, lambda l : inspect.isclass(l) and yaml.YAMLObject in inspect.getmro(l)) for station in stations: station_class = station[1] if hasattr(station_class,('name')): question['choices'][str(station_class.yaml_tag)[1:]] = station_class.name defs.insert(0,section) self.welcome(target_file) self.recurse_create(defs, source, target) yaml.dump(target, file(target_file, 'w'), default_flow_style=False) self.bye() return target_file def recurse_create(self, defs, source_node, target_node): for v in defs: k=v['name'] if v['type'] == 'dict': target_node[k] = {} if source_node.has_key(k): new_source_node = source_node[k] else: new_source_node = {} self.recurse_create(v['children'], new_source_node, target_node[k]) else: if source_node.has_key(k): default = source_node[k] else: if v.has_key('default') and v['default'] == 'none': default = 'none' else: default = None value = None while value == None: value = self.create_value(v, default) target_node[k] = value def create_value(self, node, default): question = 'Please enter '+node['description']+':' if node['type'] == 'number': if default is None: default = node['default'] answer = self.ask_question(question, default, default) try: return int(answer) except: return None if node['type'] == 'choice': choices_collection = node['choices'] if type(choices_collection) == dict: choices=sorted(choices_collection.keys()) else: choices=choices_collection if default != 'none': if default is not None and choices.count(default) > 0: default = str(choices.index(default)+1) else: default = '1' prompt = choices[int(default)-1] else: prompt = None for i in range(len(choices)): if type(choices_collection) == dict: value = choices[i] + ' - ' + choices_collection[choices[i]] else: value = choices[i] question = question+'\n '+ str(i+1) +') '+ value answer = self.ask_question(question, default, prompt) try: return choices[int(answer)-1] except: if choices.count(answer) > 0: return answer else: return None def ask_question(self, question, default, prompt): print '\n'+question if prompt is not None: sys.stdout.write('['+str(prompt)+'] ') sys.stdout.write('> ') line=sys.stdin.readline() if line is None or line.strip() == '': return default else: return line.strip() def welcome(self, settings_file): print 'This is the setup of wfrog '+wfcommon.config.wfrog_version+' user settings that will be written in '+settings_file def bye(self): print print 'Thanks.' wfrog-0.8.2+svn953/wflogger/test/000077500000000000000000000000001213472117100165025ustar00rootroot00000000000000wfrog-0.8.2+svn953/wflogger/test/atom.yaml000066400000000000000000000002551213472117100203300ustar00rootroot00000000000000 input: !atom-in url: test/feed.xml period: 10 collector: !flush period: 0 collector: !xmlfile path: test/wfrog-current.xml wfrog-0.8.2+svn953/wflogger/test/csv.yaml000066400000000000000000000007621213472117100201660ustar00rootroot00000000000000context: altitude: 430 #input: !stdio-in { validate: true } input: !service {name: events, instance: !function {} } collector: !multi children: aggregator : !flush period: 10 collector: !stopwatch target: !aggregator storage : !service name: storage instance: !include { path: ../../wfcommon/test/csv.yaml } embed: wfdriver: { config: ../../wfdriver/test/csv.yaml } wfrog-0.8.2+svn953/wflogger/test/events.xmls000066400000000000000000000006051213472117100207140ustar00rootroot00000000000000 3 2 23.0 10503232 113 160 wfrog-0.8.2+svn953/wflogger/test/feed.xml000066400000000000000000000053411213472117100201320ustar00rootroot00000000000000 Weather Station Located in a nice location 2011-03-21T21:47:46Z http://www.yourdomain.org/feed Outdoor Temperature: 10°C

      Sensor placed outside tag:yourdomain.org,2011-02-21:/temp/0/20110221214746 2011-03-21T21:47:46Z 0 10 Outdoor Humidity: 50°C Sensor placed outside tag:yourdomain.org,2011-02-21:/hum/0/20110221214746 2011-03-21T21:47:46Z 0 50 Indoor Temperature: 20°C Sensor placed inside tag:yourdomain.org,2011-02-21:/temp/1/20110221214746 2011-03-21T21:47:46Z 1 20 Indoor Humidity: 50°C Sensor placed inside tag:yourdomain.org,2011-02-21:/hum/1/20110221214746 2011-02-21T21:47:46Z 1 50 Wind Speed: 0 kmh Sensor placed outside tag:yourdomain.org,2011-02-21:/wind/0/20110221214746 2011-03-21T21:47:46Z 3 120 3 120 Rain Level: 40 Sensor placed outside tag:yourdomain.org,2011-02-21:/rain/0/20110221214746 2011-03-21T21:47:46Z 1 0.4 wfrog-0.8.2+svn953/wflogger/test/wfrog.csv000066400000000000000000000100521213472117100203410ustar00rootroot00000000000000timestamp,localtime,temp,hum,wind,wind_dir,wind_gust,wind_gust_dir,dew_point,rain,rain_rate,pressure,uv_index 1269293751,2010-03-22 22:35:51,9.0,,3.3,168.1,3.3,168.1,,0.0,0.0,,5 1269293763,2010-03-22 22:36:03,,,3.6,151.8,5.7,156.7,,0.0,0.0,,5 1269293775,2010-03-22 22:36:15,10.4,,,,,,,1.0,10.6,,5 1269293785,2010-03-22 22:36:25,,,4.0,153.3,6.0,162.7,,0.0,0.0,, 1269293797,2010-03-22 22:36:37,10.3,,,,,,,2.0,10.2,,5 1269293807,2010-03-22 22:36:47,,63.2,3.8,144.5,4.8,144.5,,0.0,0.0,1059.7, 1269293860,2010-03-22 22:37:40,,67.0,2.6,167.8,3.6,167.8,,,,,4 1269293870,2010-03-22 22:37:50,,66.3,,,,,,,,, 1269293882,2010-03-22 22:38:02,,,,,,,,1.0,11.2,,5 1269293894,2010-03-22 22:38:14,,,,,,,,2.0,10.5,, 1269293904,2010-03-22 22:38:24,,,,,,,,2.0,8.9,,5 1269294023,2010-03-22 22:40:23,,,3.1,175.3,5.1,175.3,,,,,5 1269294033,2010-03-22 22:40:33,,,2.7,170.5,2.7,170.5,,0.0,0.0,,5 1269294043,2010-03-22 22:40:43,,65.9,,,,,,0.0,0.0,,5 1269294055,2010-03-22 22:40:55,10.7,,2.8,177.1,3.8,177.1,,0.0,0.0,, 1269294245,2010-03-22 22:44:05,8.7,,3.0,201.2,3.0,201.2,,0.0,0.0,, 1269294257,2010-03-22 22:44:17,,,,,,,,0.0,0.0,, 1269294269,2010-03-22 22:44:29,,,,,,,,0.0,0.0,,4 1269294279,2010-03-22 22:44:39,8.2,,,,,,,0.0,0.0,,4 1269294291,2010-03-22 22:44:51,,,3.0,214.8,4.0,214.8,,1.0,9.1,, 1269294301,2010-03-22 22:45:01,,,2.7,220.0,3.7,220.0,,1.0,8.7,,5 1269294313,2010-03-22 22:45:13,,60.7,,,,,,2.0,8.7,1073.4, 1269294413,2010-03-22 22:46:53,,66.6,3.0,181.7,5.0,181.7,,,,,4 1269294425,2010-03-22 22:47:05,11.5,69.1,,,,,6.0,0.0,0.0,, 1269294436,2010-03-22 22:47:16,,70.8,,,,,,2.0,10.1,, 1269294448,2010-03-22 22:47:28,14.2,,2.8,182.7,4.8,182.7,,0.0,0.0,,5 1269294673,2010-03-22 22:51:13,,63.6,2.9,190.5,4.0,180.1,,0.0,0.0,, 1269294685,2010-03-22 22:51:25,,61.9,2.7,180.9,4.7,180.9,,0.0,0.0,,5 1269294697,2010-03-22 22:51:37,,63.7,3.1,167.9,3.1,167.9,,0.0,0.0,,5 1269294709,2010-03-22 22:51:49,,,2.8,152.8,4.8,156.2,,1.0,11.4,,6 1269294721,2010-03-22 22:52:01,11.4,,,,,,,0.0,0.0,,6 1269294731,2010-03-22 22:52:11,,61.5,,,,,,1.0,12.4,1062.0,5 1269295101,2010-03-22 22:58:21,,65.1,3.3,169.6,5.1,172.3,,,,, 1269295113,2010-03-22 22:58:33,,,3.1,181.0,4.3,187.0,,,,,6 1269295123,2010-03-22 22:58:43,,,,,,,,0.0,0.0,,5 1269295133,2010-03-22 22:58:53,,,3.4,203.2,4.5,213.4,,0.0,0.0,,6 1269295143,2010-03-22 22:59:03,8.4,,3.6,206.0,4.6,196.6,,0.0,0.0,1071.7, 1269295224,2010-03-22 23:00:24,,,,,,,,,,,5 1269295234,2010-03-22 23:00:34,11.8,,3.1,169.4,3.1,169.4,,,,,5 1269295244,2010-03-22 23:00:44,12.4,,,,,,,1.0,9.4,,5 1269295256,2010-03-22 23:00:56,,,2.9,180.4,5.2,172.9,,2.0,8.3,,5 1269295266,2010-03-22 23:01:06,13.0,62.4,,,,,6.0,0.0,0.0,,4 1269295276,2010-03-22 23:01:16,14.0,,,,,,,1.0,9.8,,4 1269295288,2010-03-22 23:01:28,,60.2,,,,,,3.0,11.5,1068.5, 1269295298,2010-03-22 23:01:38,,,2.9,154.5,3.9,154.5,,0.0,0.0,,4 1269295310,2010-03-22 23:01:50,,,,,,,,0.0,0.0,1063.9,5 1269295322,2010-03-22 23:02:02,,61.1,2.8,175.2,2.8,175.2,,1.0,11.1,, 1269295332,2010-03-22 23:02:12,,61.6,,,,,,0.0,0.0,1060.0,4 1269295342,2010-03-22 23:02:22,,,2.9,185.2,4.9,185.2,,0.0,0.0,,4 1269295354,2010-03-22 23:02:34,,,2.7,192.9,3.7,192.9,,0.0,0.0,1057.3,4 1269295366,2010-03-22 23:02:46,,,3.1,178.2,4.2,174.7,,1.0,13.1,1052.2,4 1269295378,2010-03-22 23:02:58,13.8,,3.1,166.8,4.1,166.8,,0.0,0.0,1054.2, 1269451692,2010-03-24 18:28:12,,,,,,,,0.0,0.0,, 1269451705,2010-03-24 18:28:25,11.7,63.3,,,,,5.0,,,,5 1269451717,2010-03-24 18:28:37,9.9,,2.7,159.8,2.7,159.8,,1.0,10.3,,5 1269451769,2010-03-24 18:29:29,,,,,,,,0.0,0.0,,5 1269451878,2010-03-24 18:31:18,11.8,,,,,,,0.0,0.0,,5 1269451888,2010-03-24 18:31:28,,64.9,,,,,,0.0,0.0,,5 1269451900,2010-03-24 18:31:40,10.5,66.8,,,,,4.6,1.0,10.6,,5 1269451912,2010-03-24 18:31:52,,,,,,,,5.0,11.5,1074.8, 1269451932,2010-03-24 18:32:12,,,3.3,182.1,5.3,182.1,,0.0,0.0,,5 1269451942,2010-03-24 18:32:22,,,3.0,161.2,5.0,161.2,,1.0,11.6,, 1269451954,2010-03-24 18:32:34,11.4,65.3,2.7,166.6,3.7,166.6,5.1,0.0,0.0,1077.3, 1269452062,2010-03-24 18:34:22,,,,,,,,,,,5 1269452072,2010-03-24 18:34:32,10.1,63.8,,,,,3.5,,,, 1269466168,2010-03-24 22:29:28,,,3.2,195.9,5.3,202.4,,0.0,0.0,,5 wfrog-0.8.2+svn953/wflogger/wflogger.py000077500000000000000000000137371213472117100177270ustar00rootroot00000000000000#!/usr/bin/python ## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . # Before loading other modules add wfrog directory to sys.path to be able to use wfcommon import os.path import sys if __name__ == "__main__": sys.path.append(os.path.abspath(sys.path[0] + '/..')) import input import collector import wfcommon.generic import wfcommon.storage import optparse import logging import time import wfcommon.config from threading import Thread from Queue import Queue, Full import copy def gen(type): return event.Event(type) class Logger(object): ''' Root Elements ------------- context [dict] (optional): Contains context values propagated to input and collector. input [input]: Source of events, usually a listening object receiving events from the driver. collector [collector]: Where events are forwarded to be logged. Events are forwarded one-by-one, so no concurrency must be handled by the collector. embed [dict] (optional): Dictionary specifying which module must be run embedded in the same process as the logger. Keys can be 'wfdriver' or 'wfrender'. Values are dictionaries with the following key-values: - config: specifies the configuration file of the embedded module. logging [logging configuration] (optional): See below the Logging Configuration section. ''' logger = logging.getLogger('wflogger') queue_size=10 embedded = {} context = None config_file = None opt_parser = None def __init__(self, opt_parser=optparse.OptionParser(conflict_handler='resolve')): # Prepare the configurer module_map = ( ( "Inputs" , input ), ( "Collectors" , collector ), ( "Storages" , wfcommon.storage ), ( "Generic Elements", wfcommon.generic) ) self.configurer = wfcommon.config.Configurer(module_map) self.configurer.add_options(opt_parser) self.opt_parser = opt_parser def configure(self, config_file, settings_file): # Parse the options and create object trees from configuration (options, args) = self.opt_parser.parse_args() (config, self.context) = self.configurer.configure(options, self, config_file, settings_file) self.config_file = self.configurer.config_file # Initialize the logger from object trees self.input = config['input'] self.collector = config['collector'] if config.has_key('queue_size'): self.queue_size = config['queue_size'] if config.has_key('period'): self.period = config['period'] if config.has_key('embed'): self.embedded = config['embed'] self.event_queue = Queue(self.queue_size) def enqueue_event(self, event): self.logger.debug("Got '%s' event. Queue size: %d", event._type, self.event_queue.qsize()) try: self.event_queue.put(event, block=False) except Full: self.logger.critical('Consumer of events is dead or not consuming quickly enough') def input_loop(self): self.input.run(self.enqueue_event) def output_loop(self): context = copy.deepcopy(self.context) while True: event = self.event_queue.get(block=True) try: self.collector.send_event(event, context=context) except Exception: self.logger.exception("Could not send event to "+str(self.collector)) def run(self, config_file="config/wflogger.yaml", settings_file=None): self.configure(config_file, settings_file) # Start the logger thread logger_thread = Thread(target=self.output_loop) logger_thread.setDaemon(True) logger_thread.start() # Start the input thread input_thread = Thread(target=self.input_loop) input_thread.setDaemon(True) input_thread.start() dir_name = os.path.dirname(self.config_file) # Start the embedded processes if self.embedded.has_key('wfdriver'): self.logger.debug("Starting embedded wfdriver") import wfdriver.wfdriver driver = wfdriver.wfdriver.Driver(self.opt_parser) driver_thread = Thread(target=driver.run, kwargs={ 'config_file':os.path.join(dir_name, self.embedded['wfdriver']['config']), 'settings_file':settings_file, 'embedded':True}) driver_thread.setDaemon(True) driver_thread.start() if self.embedded.has_key('wfrender'): self.logger.debug("Starting embedded wfrender") import wfrender.wfrender renderer = wfrender.wfrender.RenderEngine(self.opt_parser) renderer_thread = Thread(target=renderer.run, kwargs={ 'config_file':os.path.join(dir_name, self.embedded['wfrender']['config']), 'settings_file':settings_file, 'embedded':True}) renderer_thread.setDaemon(True) renderer_thread.start() # Wait for ever try: while True: time.sleep(999999) except KeyboardInterrupt: pass if __name__ == "__main__": logger = Logger() logger.logger.debug("Started main()") logger.run() logger.logger.debug("Finished main()") wfrog-0.8.2+svn953/wfrender/000077500000000000000000000000001213472117100155235ustar00rootroot00000000000000wfrog-0.8.2+svn953/wfrender/__init__.py000066400000000000000000000014521213472117100176360ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . wfrog-0.8.2+svn953/wfrender/config.py000066400000000000000000000144441213472117100173510ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import yaml import renderer import datasource import wfcommon.generic import wfcommon.config import wfcommon.storage import optparse import sys import os from os import path import time from threading import Thread import inspect import logging import traceback import wfcommon.dict class RendererConfigurer(wfcommon.config.Configurer): """Returns a configuration read from a yaml file (default to wfrender.yaml in cwd)""" watcher_running = False builtins = [ "renderer", "datasource" ] extensions = {} logger=logging.getLogger("config") embedded = False def __init__(self, opt_parser): # Prepare the configurer module_map = ( ( "Renderers" , renderer), ( "Data Sources" , datasource ), ( "Storages" , wfcommon.storage ), ( "Generic Elements", wfcommon.generic) ) wfcommon.config.Configurer.__init__(self, module_map) self.add_options(opt_parser) # deactivated because untested with new structure # TODO: test and fix if needed # opt_parser.add_option("-r", "--reload-config", action="store_true", dest="reload_config", help="Reloads the yaml configuration if it changes during execution") # opt_parser.add_option("-M", "--reload-modules", action="store_true", dest="reload_mod", help="Reloads the data source, renderer and extension modules if they change during execution") # opt_parser.add_option("-c", "--command", dest="command", help="A command to execute after automatic reload. Useful to trigger events during development such as browser reload.") def configure_engine(self, engine, options, args, embedded, config_file, settings_file=None): # TODO: remove if above fixed options.reload_mod=False options.reload_config=False options.command=False (config, config_context) = self.configure(options, engine, config_file, settings_file, embedded=embedded) engine.root_renderer = config["renderer"] engine.initial_context = wfcommon.dict.merge(engine.initial_context, config_context) if ( options.reload_config or options.reload_mod) and not self.watcher_running: self.watcher_running = True engine.daemon = True modules = [] modules.extend(self.builtins) modules.extend(self.extensions.keys()) FileWatcher(options, modules, self, engine, options, args).start() class FileWatcher(Thread): logger = logging.getLogger("config.watcher") def __init__(self, options, modules, configurer, engine, *args, **kwargs): Thread.__init__(self) self.config_file = options.config self.options = options self.modules = modules self.configurer = configurer self.engine = engine self.args = args self.kwargs = kwargs def run(self): config_this_modified = time.time() templates_this_modified = time.time() modules_modified = {} for m in self.modules: modules_modified[m] = time.time() while self.engine.daemon: config_last_modified = os.stat(self.config_file).st_mtime if config_last_modified > config_this_modified and self.options.reload_config: self.logger.debug("Changed detected on "+self.config_file) self.reconfigure() config_this_modified = config_last_modified continue templates_last_modified = last_mod('templates') if templates_last_modified > templates_this_modified and self.options.reload_config: self.logger.debug("Changed detected on templates") self.reconfigure() templates_this_modified = templates_last_modified continue if self.options.reload_mod: for m in modules_modified.keys(): last_modified = last_mod(m) if last_modified > modules_modified[m]: try: reload_modules(m) self.reconfigure() except: traceback.print_exc() modules_modified[m] = last_mod(m) def reconfigure(self): self.logger.info("Reconfiguring engine...") old_root_renderer = self.engine.root_renderer self.configurer.configure(self.engine,*self.args, **self.kwargs) try: old_root_renderer.close() time.sleep(0.1) except: pass if self.options.command: self.logger.info("Running command: "+self.options.command) command_thread = CommandThread() command_thread.command = self.options.command command_thread.start() class CommandThread(Thread): command = None def run(self): time.sleep(0.1) os.system(self.command) def reload_modules(parent): logger = logging.getLogger("config.loader") logger.info("Reloading module '"+parent+"' and direct sub-modules...") parent = __import__(parent) for m in inspect.getmembers(parent, lambda l: inspect.ismodule(l)): logger.debug("Reloading module '"+m[0]+"'.") reload(m[1]) logger.debug("Reloading module '"+parent.__name__+"'.") reload(parent) def last_mod(parent): logger = logging.getLogger("config.loader") max=0 for fname in os.listdir(parent): mod = os.stat(parent+'/'+fname).st_mtime if(mod > max): max = mod return max wfrog-0.8.2+svn953/wfrender/config/000077500000000000000000000000001213472117100167705ustar00rootroot00000000000000wfrog-0.8.2+svn953/wfrender/config/default/000077500000000000000000000000001213472117100204145ustar00rootroot00000000000000wfrog-0.8.2+svn953/wfrender/config/default/24hours.yaml000066400000000000000000000007671213472117100226200ustar00rootroot00000000000000renderer: !template path: ../../templates/default/main.html mime: text/html renderer: !data source: !service name: accu_24h renderer: !multi children: current: !include path: current.yaml chart: !include path: charts.yaml variables: interpolate: off summary: !datatable label: 2 wfrog-0.8.2+svn953/wfrender/config/default/30days.yaml000066400000000000000000000007461213472117100224120ustar00rootroot00000000000000renderer: !template path: ../../templates/default/main.html mime: text/html renderer: !data source: !service name: accu_30d renderer: !multi children: current: !include path: current.yaml chart: !include path: charts.yaml variables: interpolate: off summary: !datatable label: 2 wfrog-0.8.2+svn953/wfrender/config/default/365days.yaml000066400000000000000000000010671213472117100225020ustar00rootroot00000000000000renderer: !template path: ../../templates/default/main.html mime: text/html renderer: !data source: !service name: accu_365d_w renderer: !multi children: current: !include path: current.yaml chart: !include path: charts.yaml variables: interpolate: off summary: !datatable source: !service name: accu_365d_m label: 2 wfrog-0.8.2+svn953/wfrender/config/default/3hours.yaml000066400000000000000000000007451213472117100225310ustar00rootroot00000000000000renderer: !template path: ../../templates/default/main.html mime: text/html renderer: !data source: !service name: accu_3h renderer: !multi children: current: !include path: current.yaml chart: !include path: charts.yaml variables: interpolate: true summary: !datatable label: 2 wfrog-0.8.2+svn953/wfrender/config/default/7days.yaml000066400000000000000000000007661213472117100223400ustar00rootroot00000000000000renderer: !template path: ../../templates/default/main.html mime: text/html renderer: !data source: !service name: accu_7d renderer: !multi children: current: !include path: current.yaml chart: !include path: charts.yaml variables: interpolate: off summary: !datatable label: 2 wfrog-0.8.2+svn953/wfrender/config/default/chart_accumulator.yaml000066400000000000000000000117401213472117100250030ustar00rootroot00000000000000source: !accumulator slice: $slice span: $span storage: !service name: storage formulas: temp: avg: !avg { index: temp } min: !min { index: temp } max: !max { index: temp } ## Uncomment to use windchill and / or humidex in temperature chart (modify also charts.yaml) #wind_chill: !windchill { index: [temp, wind_gust] } #humidex: !humidex { index: [temp, hum] } ## Uncomment to display tempint (modify also charts.yaml) #tempint: # avg: !avg { index: tempint } # min: !min { index: tempint } # max: !max { index: tempint } ## Uncomment to display temp2 (modify also charts.yaml) #temp2: # avg: !avg { index: temp2 } # min: !min { index: temp2 } # max: !max { index: temp2 } ## Uncomment to display temp3 (modify also charts.yaml) #temp3: # avg: !avg { index: temp3 } # min: !min { index: temp3 } # max: !max { index: temp3 } ## Uncomment to display temp4 (modify also charts.yaml) #temp4: # avg: !avg { index: temp4 } # min: !min { index: temp4 } # max: !max { index: temp4 } ## Uncomment to display temp5 (modify also charts.yaml) #temp5: # avg: !avg { index: temp5 } # min: !min { index: temp5 } # max: !max { index: temp5 } ## Uncomment to display temp6 (modify also charts.yaml) #temp6: # avg: !avg { index: temp6 } # min: !min { index: temp6 } # max: !max { index: temp6 } ## Uncomment to display temp7 (modify also charts.yaml) #temp7: # avg: !avg { index: temp7 } # min: !min { index: temp7 } # max: !max { index: temp7 } ## Uncomment to display temp8 (modify also charts.yaml) #temp8: # avg: !avg { index: temp8 } # min: !min { index: temp8 } # max: !max { index: temp8 } ## Uncomment to display temp9 (modify also charts.yaml) #temp9: # avg: !avg { index: temp9 } # min: !min { index: temp9 } # max: !max { index: temp9 } dew: avg: !avg { index: dew_point } hum: avg: !avg { index: hum } min: !min { index: hum } max: !max { index: hum } ## Uncomment to display humint (modify also charts.yaml) #humint: # avg: !avg { index: humint } # min: !min { index: humint } # max: !max { index: humint } ## Uncomment to display hum2 (modify also charts.yaml) #hum2: # avg: !avg { index: hum2 } # min: !min { index: hum2 } # max: !max { index: hum2 } ## Uncomment to display hum3 (modify also charts.yaml) #hum3: # avg: !avg { index: hum3 } # min: !min { index: hum3 } # max: !max { index: hum3 } ## Uncomment to display hum4 (modify also charts.yaml) #hum4: # avg: !avg { index: hum4 } # min: !min { index: hum4 } # max: !max { index: hum4 } ## Uncomment to display hum5 (modify also charts.yaml) #hum5: # avg: !avg { index: hum5 } # min: !min { index: hum5 } # max: !max { index: hum5 } ## Uncomment to display hum6 (modify also charts.yaml) #hum6: # avg: !avg { index: hum6 } # min: !min { index: hum6 } # max: !max { index: hum6 } ## Uncomment to display hum7 (modify also charts.yaml) #hum7: # avg: !avg { index: hum7 } # min: !min { index: hum7 } # max: !max { index: hum7 } ## Uncomment to display hum8 (modify also charts.yaml) #hum8: # avg: !avg { index: hum8 } # min: !min { index: hum8 } # max: !max { index: hum8 } ## Uncomment to display hum9 (modify also charts.yaml) #hum9: # avg: !avg { index: hum9 } # min: !min { index: hum9 } # max: !max { index: hum9 } press: avg: !avg { index: pressure } min: !min { index: pressure } max: !max { index: pressure } wind: avg: !avg { index: wind } max: !max { index: wind_gust } 'deg,dir' : !predominant { index: wind } sectors: avg: !sector-avg { index: wind } max: !sector-max { index: wind_gust } freq: !sector-freq { index: wind } rain: rate: !max { index: rain_rate } fall: !sum { index: rain } ## Uncomment to display UV Index (modify also charts.yaml) #uv: # index: !max { index: uv_index } ## Uncomment to display Solar radiation (modify also charts.yaml) #solar_rad: # min: !min { index: solar_rad } # max: !max { index: solar_rad } wfrog-0.8.2+svn953/wfrender/config/default/charts.yaml000066400000000000000000000257311213472117100225740ustar00rootroot00000000000000renderer: !multi children: ## Uncomment to display tempint (modify also chart_accumulator.yaml) #tempint: !chart # interpolate: $interpolate # series: # tempint.max: # color: wheat # area: { to: tempint.min } # max: { color: darkred, text: darkred, thickness: 0.5 } # tempint.avg: # order: 1 # tempint.min: # color: wheat # min: { color: darkslateblue, text: darkslateblue, thickness: 0.5 } # labels: tempint.lbl # zero: { color: 8D7641, thickness: 0.5 } temp: !chart interpolate: $interpolate series: temp.max: color: wheat area: { to: temp.min } max: { color: darkred, text: darkred, thickness: 0.5 } temp.avg: order: 1 temp.min: color: wheat min: { color: darkslateblue, text: darkslateblue, thickness: 0.5 } ## Uncomment to add wind chill and/or humidex to graphs (modify also chart_accumulator.yaml) #temp.wind_chill: # color: blue # thickness: 1 # dash: 3 # order: 2 #temp.humidex: # color: red # thickness: 1 # dash: 3 # order: 3 dew.avg: color: 8D7641 thickness: 1 dash: 2 labels: temp.lbl zero: { color: 8D7641, thickness: 0.5 } ## Uncomment to display temp2 (modify also chart_accumulator.yaml) #temp2: !chart # interpolate: $interpolate # series: # temp2.max: # color: wheat # area: { to: temp2.min } # max: { color: darkred, text: darkred, thickness: 0.5 } # temp2.avg: # order: 1 # temp2.min: # color: wheat # min: { color: darkslateblue, text: darkslateblue, thickness: 0.5 } # labels: temp2.lbl # zero: { color: 8D7641, thickness: 0.5 } ## Uncomment to display temp3 (modify also chart_accumulator.yaml) #temp3: !chart # interpolate: $interpolate # series: # temp3.max: # color: wheat # area: { to: temp3.min } # max: { color: darkred, text: darkred, thickness: 0.5 } # temp3.avg: # order: 1 # temp3.min: # color: wheat # min: { color: darkslateblue, text: darkslateblue, thickness: 0.5 } # labels: temp3.lbl # zero: { color: 8D7641, thickness: 0.5 } ## Uncomment to display temp4 (modify also chart_accumulator.yaml) #temp4: !chart # interpolate: $interpolate # series: # temp4.max: # color: wheat # area: { to: temp4.min } # max: { color: darkred, text: darkred, thickness: 0.5 } # temp4.avg: # order: 1 # temp4.min: # color: wheat # min: { color: darkslateblue, text: darkslateblue, thickness: 0.5 } # labels: temp4.lbl # zero: { color: 8D7641, thickness: 0.5 } ## Uncomment to display temp5 (modify also chart_accumulator.yaml) #temp5: !chart # interpolate: $interpolate # series: # temp5.max: # color: wheat # area: { to: temp5.min } # max: { color: darkred, text: darkred, thickness: 0.5 } # temp5.avg: # order: 1 # temp5.min: # color: wheat # min: { color: darkslateblue, text: darkslateblue, thickness: 0.5 } # labels: temp5.lbl # zero: { color: 8D7641, thickness: 0.5 } ## Uncomment to display temp6 (modify also chart_accumulator.yaml) #temp6: !chart # interpolate: $interpolate # series: # temp6.max: # color: wheat # area: { to: temp6.min } # max: { color: darkred, text: darkred, thickness: 0.5 } # temp6.avg: # order: 1 # temp6.min: # color: wheat # min: { color: darkslateblue, text: darkslateblue, thickness: 0.5 } # labels: temp6.lbl # zero: { color: 8D7641, thickness: 0.5 } ## Uncomment to display temp7 (modify also chart_accumulator.yaml) #temp7: !chart # interpolate: $interpolate # series: # temp7.max: # color: wheat # area: { to: temp7.min } # max: { color: darkred, text: darkred, thickness: 0.5 } # temp7.avg: # order: 1 # temp7.min: # color: wheat # min: { color: darkslateblue, text: darkslateblue, thickness: 0.5 } # labels: temp7.lbl # zero: { color: 8D7641, thickness: 0.5 } ## Uncomment to display temp8 (modify also chart_accumulator.yaml) #temp8: !chart # interpolate: $interpolate # series: # temp8.max: # color: wheat # area: { to: temp8.min } # max: { color: darkred, text: darkred, thickness: 0.5 } # temp8.avg: # order: 1 # temp8.min: # color: wheat # min: { color: darkslateblue, text: darkslateblue, thickness: 0.5 } # labels: temp8.lbl # zero: { color: 8D7641, thickness: 0.5 } ## Uncomment to display temp9 (modify also chart_accumulator.yaml) #temp9: !chart # interpolate: $interpolate # series: # temp9.max: # color: wheat # area: { to: temp9.min } # max: { color: darkred, text: darkred, thickness: 0.5 } # temp9.avg: # order: 1 # temp9.min: # color: wheat # min: { color: darkslateblue, text: darkslateblue, thickness: 0.5 } # labels: temp9.lbl # zero: { color: 8D7641, thickness: 0.5 } ## Uncomment to display humint (modify also chart_accumulator.yaml) #humint: !chart # interpolate: $interpolate # series: # humint.avg: {} # labels: humint.lbl # ymargin: [ 10, 5 ] hum: !chart interpolate: $interpolate series: hum.avg: {} labels: hum.lbl ymargin: [ 10, 5 ] ## Uncomment to display hum2 (modify also chart_accumulator.yaml) #hum2: !chart # interpolate: $interpolate # series: # hum2.avg: {} # labels: hum2.lbl # ymargin: [ 10, 5 ] ## Uncomment to display hum3 (modify also chart_accumulator.yaml) #hum3: !chart # interpolate: $interpolate # series: # hum3.avg: {} # labels: hum3.lbl # ymargin: [ 10, 5 ] ## Uncomment to display hum4 (modify also chart_accumulator.yaml) #hum4: !chart # interpolate: $interpolate # series: # hum4.avg: {} # labels: hum4.lbl # ymargin: [ 10, 5 ] ## Uncomment to display hum5 (modify also chart_accumulator.yaml) #hum5: !chart # interpolate: $interpolate # series: # hum5.avg: {} # labels: hum5.lbl # ymargin: [ 10, 5 ] ## Uncomment to display hum6 (modify also chart_accumulator.yaml) #hum6: !chart # interpolate: $interpolate # series: # hum6.avg: {} # labels: hum6.lbl # ymargin: [ 10, 5 ] ## Uncomment to display hum7 (modify also chart_accumulator.yaml) #hum7: !chart # interpolate: $interpolate # series: # hum7.avg: {} # labels: hum7.lbl # ymargin: [ 10, 5 ] ## Uncomment to display hum8 (modify also chart_accumulator.yaml) #hum8: !chart # interpolate: $interpolate # series: # hum8.avg: {} # labels: hum8.lbl # ymargin: [ 10, 5 ] ## Uncomment to display hum9 (modify also chart_accumulator.yaml) #hum9: !chart # interpolate: $interpolate # series: # hum9.avg: {} # labels: hum9.lbl # ymargin: [ 10, 5 ] press: !chart interpolate: $interpolate series: press.avg: {} labels: press.lbl rain: !chart interpolate: $interpolate accumulate: true series: rain.fall: last: { color: darkred, text: darkred, thickness: 0.5 } labels: rain.lbl ymargin: [ 0, 10 ] rain_rate: !chart interpolate: $interpolate series: rain.rate: {} labels: rain.lbl ymargin: [ 0, 10 ] ## Uncomment to display UV Index (modify also chart_accumulator.yaml) #uv: !chart # interpolate: $interpolate # series: # uv.index: {} # labels: uv.lbl # ymargin: [ 0, 1 ] ## Uncomment to display Solar radiation (modify also chart_accumulator.yaml) #solar_rad: !chart # interpolate: $interpolate # series: # solar_rad.max: # max: { color: darkred, text: darkred, thickness: 0.5 } # solar_rad.min: # order: -1 # color: wheat # area: { to: solar_rad.max } # labels: solar_rad.lbl wind: !chart interpolate: $interpolate series: wind.avg: {} wind.max: order: -1 color: tan thickness: 1 area: { to: wind.avg, color: wheat } max: { color: darkred, text: darkred, thickness: 0.5 } marks: serie: wind.dir labels: wind.lbl ymargin: [ 0, 1 ] wind_dir: !windradar width: 150 height: 150 sectors: { color: 00AF00, intensity: 1 } lines: { gust: tan, thickness: 1 } areas: { gust: wheat } wfrog-0.8.2+svn953/wfrender/config/default/check.yaml000066400000000000000000000031061213472117100223550ustar00rootroot00000000000000renderer: !template path: ../../templates/default/check.txt mime: text/plain renderer: !multi children: daily: !datatable source: !accumulator slice: day format: '%Y-%m-%d' span: 10 storage: !service { name: storage } formulas: temp: avg: !avg { index: temp } min: !min { index: temp } max: !max { index: temp } count: !count { index: temp } hum: avg: !avg { index: hum } min: !min { index: hum } max: !max { index: hum } press: min: !min { index: pressure } max: !max { index: pressure } count: !count { index: pressure } wind: max: !max { index: wind_gust } 'deg,dir' : !predominant { index: wind } count: !count { index: wind } rain: rate: !max { index: rain_rate } fall: !sum { index: rain } count: !count { index: rain } measures: count: !count { index: localtime } last: !last { index: localtime } wfrog-0.8.2+svn953/wfrender/config/default/current.yaml000066400000000000000000000020361213472117100227630ustar00rootroot00000000000000renderer: !data source: !user choices: root: !currentxml path: /var/lib/wfrog/wfrog-current.xml default: !currentxml path: data/wfrog-current.xml renderer: !multi children: temp0: !value { key: temp0 } hum0: !value { key: hum0 } temp1: !value { key: temp1 } hum1: !value { key: hum1 } press: !value { key: press } rain: !value { key: rain } timestamp: !value { key: info, value: timestamp } wind: !multi children: speed: !value { key: wind } gust: !value { key: wind, value: max } dir: !windradar thickness: 0.4 size: 5 height: 76 width: 76 arrow: { show: yes } tail: {color: tan } beaufort: { color: wheat, intensity: 0.9 } wfrog-0.8.2+svn953/wfrender/config/default/table_accumulator.yaml000066400000000000000000000100461213472117100247670ustar00rootroot00000000000000source: !accumulator slice: $slice span: $span storage: !service name: storage formulas: ## Uncomment to display tempint #tempint: # avg: !avg { index: tempint } # min: !min { index: tempint } # max: !max { index: tempint } temp: avg: !avg { index: temp } min: !min { index: temp } max: !max { index: temp } ## Uncomment to display temp2 #temp2: # avg: !avg { index: temp2 } # min: !min { index: temp2 } # max: !max { index: temp2 } ## Uncomment to display temp3 #temp3: # avg: !avg { index: temp3 } # min: !min { index: temp3 } # max: !max { index: temp3 } ## Uncomment to display temp4 #temp4: # avg: !avg { index: temp4 } # min: !min { index: temp4 } # max: !max { index: temp4 } ## Uncomment to display temp5 #temp5: # avg: !avg { index: temp5 } # min: !min { index: temp5 } # max: !max { index: temp5 } ## Uncomment to display temp6 #temp6: # avg: !avg { index: temp6 } # min: !min { index: temp6 } # max: !max { index: temp6 } ## Uncomment to display temp7 #temp7: # avg: !avg { index: temp7 } # min: !min { index: temp7 } # max: !max { index: temp7 } ## Uncomment to display temp8 #temp8: # avg: !avg { index: temp8 } # min: !min { index: temp8 } # max: !max { index: temp8 } ## Uncomment to display temp9 #temp9: # avg: !avg { index: temp9 } # min: !min { index: temp9 } # max: !max { index: temp9 } ## Uncomment to display humint #humint: # avg: !avg { index: humint } # min: !min { index: humint } # max: !max { index: humint } hum: avg: !avg { index: hum } min: !min { index: hum } max: !max { index: hum } ## Uncomment to display hum2 #hum2: # avg: !avg { index: hum2 } # min: !min { index: hum2 } # max: !max { index: hum2 } ## Uncomment to display hum2 #hum3: # avg: !avg { index: hum3 } # min: !min { index: hum3 } # max: !max { index: hum3 } ## Uncomment to display hum4 #hum4: # avg: !avg { index: hum4 } # min: !min { index: hum4 } # max: !max { index: hum4 } ## Uncomment to display hum5 #hum5: # avg: !avg { index: hum5 } # min: !min { index: hum5 } # max: !max { index: hum5 } ## Uncomment to display hum6 #hum6: # avg: !avg { index: hum6 } # min: !min { index: hum6 } # max: !max { index: hum6 } ## Uncomment to display hum7 #hum7: # avg: !avg { index: hum7 } # min: !min { index: hum7 } # max: !max { index: hum7 } ## Uncomment to display hum8 #hum8: # avg: !avg { index: hum8 } # min: !min { index: hum8 } # max: !max { index: hum8 } ## Uncomment to display hum9 #hum9: # avg: !avg { index: hum9 } # min: !min { index: hum9 } # max: !max { index: hum9 } press: avg: !avg { index: pressure } min: !min { index: pressure } max: !max { index: pressure } wind: avg: !avg { index: wind } max: !max { index: wind_gust } 'deg,dir' : !predominant { index: wind } rain: rate: !max { index: rain_rate } fall: !sum { index: rain } ## Uncomment to display UV Index (modify also charts.yaml) #uv: # index: !max { index: uv_index } ## Uncomment to display Solar radiation (modify also charts.yaml) #solar_rad: # max: !max { index: solar_rad } wfrog-0.8.2+svn953/wfrender/config/embedded.yaml000066400000000000000000000067371213472117100214220ustar00rootroot00000000000000renderer: !multi parallel: true children: ## Uncomment to activate PWS Weather for you publisher #pwsweather: !pwsweather # id: STATION_ID # password: PASSWORD # storage: !service # name: storage # period: 300 ## Uncomment to activate Wetter.com publisher # SetUp: http://www.wetter.com/community/wetternetzwerk/admin/api/ # Please use for first setup test: True to test publishing. Check logs! #wettercom: !wettercom # username: YOUR_STATION_ID # password: YOUR_STATION_PASSWORD # storage: !service # name: storage # test: False # period: 900 ## Uncomment to activate Weather Underground publisher #wunderground: !wunderground # id: STATION_ID # password: PASSWORD # storage: !service # name: storage # period: 300 ## Uncomment to publish files by ftp (compatible with http server) #ftp: !scheduler # period: 600 # in seconds # delay: 60 # in seconds, delay before start rendering # renderer: !ftp # host: HOST.COM # username: USERNAME # password: PASSWORD # directory: DIRECTORY # renderers: # 3hours.html: !file # path: /tmp/3hours.html # renderer: !include # path: default/3hours.yaml # 24hours.html: !file # path: /tmp/24hours.html # renderer: !include # path: default/24hours.yaml # 30days.html: !file # path: /tmp/30days.html # renderer: !include # path: default/30days.yaml # 365days.html: !file # path: /tmp/365days.html # renderer: !include # path: default/365days.yaml # ## Uncomment to activate www.meteoclimatic.com ftp publisher # #meteoclimatic.txt: !file # # path: /tmp/meteoclimatic.txt # # renderer: !meteoclimatic # # id: STATION_ID # # storage: !service # # name: storage # Http publishing (default) http: !http cookies: [ units ] root: !include path: default/24hours.yaml renderers: 3hours.html: !include path: default/3hours.yaml 24hours.html: !include path: default/24hours.yaml 7days.html: !include path: default/7days.yaml 30days.html: !include path: default/30days.yaml 365days.html: !include path: default/365days.yaml check: !include path: default/check.yaml ## Uncomment to activate www.meteoclimatic.com http publisher #meteoclimatic.txt: !file # path: /tmp/meteoclimatic.txt.txt # renderer: !meteoclimatic # id: STATION_ID # storage: !service # name: storage wfrog-0.8.2+svn953/wfrender/config/logo.png000066400000000000000000000074241213472117100204450ustar00rootroot00000000000000PNG  IHDR370r<sRGBbKGD pHYs  tIME #d}IDAThŚ{XT?k`*2"Bj%[h)+=îmխTHO{ҧ=ZҎ]L QWF@ 00?fuiz]뷾ׅ[۷o3Sd[Ù{$Deeehf41͔rcяPR)".SyyyÇgfG޵tCW'4&k0qXJ<6<^W<<<.+o'B kLutqk)pUfWmZƍǐ!Cvz9s挬\zYי4%6}qvPX\E]krnܬ?i%ߟA&@D%T1;|ّS&?o͙ǯ_+ 1sr7v8#?XjkkɘL&֯_/ix zQz ~a z{xXtr/FjUhCOɟ Ʊ&WB5.dl–}lZzS:f .TM~G@mE#}ꯝ4 qqص&|=9X*kzWms=Qӫɵ+AݸkYё}9oV:fp[s:vqsm0|%] N*8>sm^.8;iʪZJXv.p6'Օ]va2F:ݾcg'~89) Y?رc2f̘vS6l QQQ7a.**Y ԃSWLIXX:ćcH-եA5x:8zL8mR9rDjjj 5j\|YD]ʪU$))>Po.={H^de   Vs{#O@p\9]QHQ*" #5Ւԇ*u%_/N^Aބ 9{yot?Rb՝}Y%Y敲{, zGxoE،xDkec3b5$˾#E~p_.^D@ g |9%74]Bf2kx/&l ht-O _^9nCh$N&3o%h܂?^ϣh(+@=hؿګ4*HO!Ņ%j#0PGi //WnPy7^^Ν=(|}}z;vظ2 XCq|1/6qߑdBBиKJXh4J$mt"2aw'#dr.-`¤l*<mKVku***XtOﲠ(رgckv5 =# `☉D$5 2=y:+]ѵ><u3agĚ`!K7X՞b߹sW'sb>s|M0ra7 QHKlav/v 1|l6l2y^@̯9#i/,vY"C[ȼ#dee׋6xo@T`^˒# / z@Ɍ@`r  nG8I`ܘqv5q"p `3@i|=T3cU@ tRp (j*,"Ko߾\ܦ7O Fn@PVC0uTYEQx~l`p}HU  HL~~D>}V1bȍת٭yH`gCxˣ9sj.#qx(PNC#C}]|||h @||wDrDTE bbb/wԴag p0YU "bT_xW:֟ *&L'3vdU}:RbQ 5|{qlzsh.?~\F2%N9j(OuxުjUbՇAT2t:4ߗ#Iv__q (WͲE5;njz73E%02+Mk.-fúKHHHҥK2f5>cyQe.q;wdEٱczRZlM+7wعw'_^`V{u2U)-ŐDrr2|wܛ6U3dV#n hL6Miqf4 Z;ݵEχI&)^z|{vxn'aVhZ?Όa/ǚ0XI=jD=`@ QȼU𨪾;!7q}`7hbզ[kFwL&ӦM!$celMZ|Ԉed\8":t:)..V ׯ_ q1Q=O>D7:lsrr$tN(Ll6 .oLhhpYj^>ˠ~%>>\Ivv6f___Ə_o-M&%YqYMg{0`bP\\,& oo&ߡin|||)iRU[PYYYlH111JLLLk}}VQ# `t۷j};̓ʧrQ|h$q2eK07m#F2zh"##;cY0IENDB`wfrog-0.8.2+svn953/wfrender/config/meteoclimatic.yaml000066400000000000000000000021311213472117100224700ustar00rootroot00000000000000# To test meteoclimatic update: # python ../wfrender.py -f meteoclimatic.yaml -s ../../wfcommon/config/settings.yaml init: storage: !service name: storage instance: !include path: ../../wfcommon/config/storage.yaml renderer: !http root: !meteoclimatic id: YOUR_STATION_ID storage: !service name: storage logging: level: debug filename: !user choices: root: /var/log/wfrender.log default: wfrender.log ## By default wfrog uses a Rotating file handler. To set up different handlers ## uncomment the following section. (Warning: does not work under python > 2.7) #handlers: # default: # level: debug # # handler: !include # path: ../../wfcommon/config/loghandler.yaml # variables: # process: wfrender # # ## Uncomment to receive mail on critical events # #mail: # # level: critical # # handler: !include # # path: ../../wfcommon/config/mailloghandler.yaml wfrog-0.8.2+svn953/wfrender/config/openweathermap.yaml000066400000000000000000000024201213472117100226710ustar00rootroot00000000000000# To test Weather Underground update: # python ../wfrender.py -f openweathermap.yaml -s ../../wfcommon/config/default-settings.yaml init: storage: !service name: storage instance: !include path: ../../wfcommon/config/storage.yaml renderer: !openweathermap username: OPENWEATHERMAP-USERNAME password: PASSWORD name: STATION NAME storage: !service name: storage latitude: STATION LATITUDE (DECIMAL NUMBER) longitude: STATION LONGITUDE (DECIMAL NUMBER) altitude: STATION ALTITUDE IN M. logging: level: debug filename: !user choices: root: /var/log/wfrender.log default: wfrender.log ## By default wfrog uses a Rotating file handler. To set up different handlers ## uncomment the following section. (Warning: does not work under python > 2.7) #handlers: # default: # level: debug # # handler: !include # path: ../../wfcommon/config/loghandler.yaml # variables: # process: wfrender # # ## Uncomment to receive mail on critical events # #mail: # # level: critical # # handler: !include # # path: ../../wfcommon/config/mailloghandler.yaml wfrog-0.8.2+svn953/wfrender/config/pwsweather.yaml000066400000000000000000000021521213472117100220450ustar00rootroot00000000000000# To test PWSWeather update: # python ../wfrender.py -f wunderground.yaml -s ../../wfcommon/config/default-settings.yaml init: storage: !service name: storage instance: !include path: ../../wfcommon/config/storage.yaml renderer: !pwsweather id: YOUR_STATION_ID password: YOUR_PASSWORD storage: !service name: storage period: 300 logging: level: debug filename: !user choices: root: /var/log/wfrender.log default: wfrender.log ## By default wfrog uses a Rotating file handler. To set up different handlers ## uncomment the following section. (Warning: does not work under python > 2.7) #handlers: # default: # level: debug # # handler: !include # path: ../../wfcommon/config/loghandler.yaml # variables: # process: wfrender # # ## Uncomment to receive mail on critical events # #mail: # # level: critical # # handler: !include # # path: ../../wfcommon/config/mailloghandler.yaml wfrog-0.8.2+svn953/wfrender/config/sticker.yaml000066400000000000000000000022071213472117100213210ustar00rootroot00000000000000# To test meteoclimatic update: # python ../wfrender.py -f sticker.yaml -s ../../wfcommon/config/default-settings.yaml init: storage: !service name: storage instance: !include path: ../../wfcommon/config/storage.yaml renderer: !http renderers: sticker.png: !sticker station_name: YOUR_STATION_NAME storage: !service name: storage logging: level: debug filename: !user choices: root: /var/log/wfrender.log default: wfrender.log ## By default wfrog uses a Rotating file handler. To set up different handlers ## uncomment the following section. (Warning: does not work under python > 2.7) #handlers: # default: # level: debug # # handler: !include # path: ../../wfcommon/config/loghandler.yaml # variables: # process: wfrender # # ## Uncomment to receive mail on critical events # #mail: # # level: critical # # handler: !include # # path: ../../wfcommon/config/mailloghandler.yaml wfrog-0.8.2+svn953/wfrender/config/wettercom.yaml000066400000000000000000000022041213472117100216630ustar00rootroot00000000000000# To test Wetter.com update: # python ../wfrender.py -f wettercom.yaml -s ../../wfcommon/config/default-settings.yaml init: storage: !service name: storage instance: !include path: ../../wfcommon/config/storage.yaml renderer: !wettercom username: YOUR_STATION_ID password: YOUR_STATION_PASSWORD storage: !service name: storage test: False period: 900 logging: level: debug filename: !user choices: root: /var/log/wfrender.log default: wfrender.log ## By default wfrog uses a Rotating file handler. To set up different handlers ## uncomment the following section. (Warning: does not work under python > 2.7) #handlers: # default: # level: debug # # handler: !include # path: ../../wfcommon/config/loghandler.yaml # variables: # process: wfrender # # ## Uncomment to receive mail on critical events # #mail: # # level: critical # # handler: !include # # path: ../../wfcommon/config/mailloghandler.yaml wfrog-0.8.2+svn953/wfrender/config/wfrender.yaml000066400000000000000000000141001213472117100214640ustar00rootroot00000000000000init: storage: !service name: storage instance: !include path: ../../wfcommon/config/storage.yaml accu_3h: !service name: accu_3h instance: !include path: default/chart_accumulator.yaml variables: slice: minute span: 180 accu_24h: !service name: accu_24h instance: !include path: default/chart_accumulator.yaml variables: slice: hour span: 24 accu_7d: !service name: accu_7d instance: !include path: default/chart_accumulator.yaml variables: slice: hour span: 168 accu_30d: !service name: accu_30d instance: !include path: default/chart_accumulator.yaml variables: slice: day span: 31 accu_365d_w: !service name: accu_365d_w instance: !include path: default/chart_accumulator.yaml variables: slice: week span: 60 accu_365d_m: !service name: accu_365d_m instance: !include path: default/table_accumulator.yaml variables: slice: month span: 12 renderer: !multi parallel: true children: ## Uncomment to activate PWS Weather for you publisher #pwsweather: !pwsweather # id: STATION_ID # password: PASSWORD # storage: !service # name: storage # period: 300 ## Uncomment to activate Wetter.com publisher # SetUp: http://www.wetter.com/community/wetternetzwerk/admin/api/ # Please use for first setup test: True to test publishing. Check logs! #wettercom: !wettercom # username: YOUR_STATION_ID # password: YOUR_STATION_PASSWORD # storage: !service # name: storage # test: False # period: 900 ## Uncomment to activate Weather Underground publisher #wunderground: !wunderground # id: STATION_ID # password: PASSWORD # storage: !service # name: storage # period: 300 ## Uncomment to activate openWeatherMap publisher #openweathermap: !openweathermap # username: OPENWEATHERMAP-USERNAME # password: PASSWORD # name: STATION NAME # storage: !service # name: storage # latitude: STATION LATITUDE (DECIMAL NUMBER) # longitude: STATION LONGITUDE (DECIMAL NUMBER) # altitude: STATION ALTITUDE IN M. ## Uncomment to publish files by ftp (compatible with http server) #ftp: !scheduler # period: 600 # in seconds # delay: 60 # in seconds, delay before start rendering # renderer: !ftp # host: HOST.COM # username: USERNAME # password: PASSWORD # directory: DIRECTORY # renderers: # 3hours.html: !file # path: /tmp/3hours.html # renderer: !include # path: default/3hours.yaml # 24hours.html: !file # path: /tmp/24hours.html # renderer: !include # path: default/24hours.yaml # 30days.html: !file # path: /tmp/30days.html # renderer: !include # path: default/30days.yaml # 365days.html: !file # path: /tmp/365days.html # renderer: !include # path: default/365days.yaml # ## Uncomment to activate www.meteoclimatic.com ftp publisher # #meteoclimatic.txt: !file # # path: /tmp/meteoclimatic.txt # # renderer: !meteoclimatic # # id: STATION_ID # # storage: !service # # name: storage # Http publishing (default) http: !http cookies: [ units ] root: !include path: default/24hours.yaml renderers: 3hours.html: !include path: default/3hours.yaml 24hours.html: !include path: default/24hours.yaml 7days.html: !include path: default/7days.yaml 30days.html: !include path: default/30days.yaml 365days.html: !include path: default/365days.yaml check: !include path: default/check.yaml ## Uncomment to activate sticker (beta version) #sticker.png: !sticker # station_name: YOUR STATION NAME # storage: !service # name: storage ## Uncomment to activate www.meteoclimatic.com http publisher #meteoclimatic.txt: !meteoclimatic # id: STATION_ID # storage: !service # name: storage logging: level: info filename: !user choices: root: /var/log/wfrender.log default: wfrender.log ## By default wfrog uses a Rotating file handler. To set up different handlers ## uncomment the following section. (Warning: does not work under python > 2.7) #handlers: # default: # level: debug # # handler: !include # path: ../../wfcommon/config/loghandler.yaml # variables: # process: wfrender # # ## Uncomment to receive mail on critical events # #mail: # # level: critical # # handler: !include # # path: ../../wfcommon/config/mailloghandler.yaml wfrog-0.8.2+svn953/wfrender/config/wunderground.yaml000066400000000000000000000021551213472117100224020ustar00rootroot00000000000000# To test Weather Underground update: # python ../wfrender.py -f wunderground.yaml -s ../../wfcommon/config/settings.yaml init: storage: !service name: storage instance: !include path: ../../wfcommon/config/storage.yaml renderer: !wunderground id: YOUR_STATION_ID password: YOUR_PASSWORD storage: !service name: storage period: 300 logging: level: debug filename: !user choices: root: /var/log/wfrender.log default: wfrender.log ## By default wfrog uses a Rotating file handler. To set up different handlers ## uncomment the following section. (Warning: does not work under python > 2.7) #handlers: # default: # level: debug # # handler: !include # path: ../../wfcommon/config/loghandler.yaml # variables: # process: wfrender # # ## Uncomment to receive mail on critical events # #mail: # # level: critical # # handler: !include # # path: ../../wfcommon/config/mailloghandler.yaml wfrog-0.8.2+svn953/wfrender/datasource/000077500000000000000000000000001213472117100176555ustar00rootroot00000000000000wfrog-0.8.2+svn953/wfrender/datasource/__init__.py000066400000000000000000000025331213472117100217710ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import yaml import accumulator import database import xmlquery import simulator # YAML mappings class YamlAccumulatorDataSource(accumulator.AccumulatorDatasource, yaml.YAMLObject): yaml_tag = u'!accumulator' class YamlSimulatorDataSource(simulator.SimulatorDataSource, yaml.YAMLObject): yaml_tag = u'!simulator' class YamlDatabaseDataSource(database.DatabaseDataSource, yaml.YAMLObject): yaml_tag = u'!database' class YamlCurrentConditionsXmlDataSource(xmlquery.CurrentConditionsXmlDataSource, yaml.YAMLObject): yaml_tag = u'!currentxml' wfrog-0.8.2+svn953/wfrender/datasource/accumulator.py000066400000000000000000000303061213472117100225500ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import logging from wfcommon.formula.base import AverageFormula from wfcommon.formula.base import MinFormula from wfcommon.formula.base import MaxFormula from wfcommon.formula.base import SumFormula from wfcommon.formula.wind import PredominantWindFormula from wfcommon.formula.wind import WindSectorAverageFormula from wfcommon.formula.wind import WindSectorMaxFormula from wfcommon.formula.wind import WindSectorFrequencyFormula from wfcommon.formula.temp import WindChillMinFormula from wfcommon.formula.temp import HeatIndexMaxFormula import copy import datetime import threading class AccumulatorDatasource(object): ''' Calculates data from a storage in an iterative way by traversing only recently added data. [ Properties ] storage [storage]: The underlying storage to get samples. slice [year|month|week|day|hour|minute] (optional): The unit of grouping for the calculated series. Defaults to 'hour'' span [numeric] (optional): Number of slices in the resulting series. Defaults to 24. period [numeric] (optional): Number of seconds between two refreshes of the calculated data. DEfaults to 120. format [string or list of strings] (optional): Date/time format string for labels. See Python strftime function. It can be a single string (only 1 label) or a list of formats. First label is 'lbl' and the rest are 'lbl2', lbl3', etc. formulas [dict] (optional): Specify what and how to calculate. Defines the structure of the resulting data. Dictionary keyed by the measure names ('temp', 'hum', ...). Values are dictionaries keyed by the serie names ('avg', 'min', ...) and containing 'formula' objects. caching [true|false] (optional): Enable/disable caching for normal requests. Defaults to true. ''' storage = None slice = 'hour' span = 23 format = None formats = { 'year': ['%y', '%Y'], 'month': ['%m', '%Y/%m'], 'week': ['%d/%m', '%Y/%m/%d'], 'day': ['%d/%m', '%Y/%m/%d'], 'hour': ['%H', '%Y/%m/%d %H'], 'minute': ['%H:%M', '%Y/%m/%d %H:%M'] } period = 120 default_formulas = { 'temp': { 'avg' : AverageFormula('temp'), 'min' : MinFormula('temp'), 'max' : MaxFormula('temp') }, 'dew' : { 'avg': AverageFormula('dew_point') }, 'hum' : { 'avg' : AverageFormula('hum'), 'min' : MinFormula('hum'), 'max' : MaxFormula('hum') }, 'press' : { 'avg' : AverageFormula('pressure'), 'min' : MinFormula('pressure'), 'max' : MaxFormula('pressure') }, 'wind' : { 'avg' : AverageFormula('wind'), 'max' : MaxFormula('wind_gust'), 'deg,dir' : PredominantWindFormula('wind') }, 'sectors' : { 'avg' : WindSectorAverageFormula('wind'), 'max' : WindSectorMaxFormula('wind_gust'), 'freq' : WindSectorFrequencyFormula('wind') }, 'rain' : { 'rate' : MaxFormula('rain_rate'), 'fall' : SumFormula('rain') }, 'uv' : { 'index' : MaxFormula('uv_index') } } formulas = default_formulas caching = True logger = logging.getLogger("datasource.accumulator") last_timestamp = datetime.datetime.fromtimestamp(0) cached_slices = None cached_series = None lock = threading.Lock() class Slice(object): def __init__(self, formulas, from_time, to_time, keys): self.formulas = copy.deepcopy(formulas) self.from_time = from_time self.to_time = to_time # replace string keys with index for performance for serie in self.formulas.values(): for formula in serie.values(): if type(formula.index)==str: formula.index = keys.index(formula.index) # Index can be a list of indexes (e.g. heatIndex or WindChill) elif type(formula.index)==list: formula.index = map(lambda x: keys.index(x), formula.index) def add_sample(self, sample): for serie in self.formulas.values(): for formula in serie.values(): formula.append(sample) def get_slice_duration(self): if self.slice == 'minute': return datetime.timedelta(0, 60) elif self.slice == 'hour': return datetime.timedelta(0, 3600) elif self.slice == 'day': return datetime.timedelta(1) elif self.slice == 'week': return datetime.timedelta(7) elif self.slice == 'month': return datetime.timedelta(30) elif self.slice == 'year': return datetime.timedelta(365) def get_slice_start(self, time): if self.slice == 'minute': return datetime.datetime(time.year, time.month, time.day, time.hour, time.minute) elif self.slice == 'hour': return datetime.datetime(time.year, time.month, time.day, time.hour) elif self.slice == 'day': return datetime.datetime(time.year, time.month, time.day) elif self.slice == 'week': (year, week, dayweek) = time.isocalendar() return iso_to_gregorian(year, week, 1) elif self.slice == 'month': return datetime.datetime(time.year, time.month, 1) elif self.slice == 'year': return datetime.datetime(time.year, 1, 1) def get_next_slice_start(self, time): if self.slice == 'minute': return time+datetime.timedelta(0,60) elif self.slice == 'hour': return time+datetime.timedelta(0,3600) elif self.slice == 'day': return time+datetime.timedelta(1,0) elif self.slice == 'week': (year, week, dayweek) = time.isocalendar() return iso_to_gregorian(year, week, 1)+datetime.timedelta(7) elif self.slice == 'month': if time.month == 12: return datetime.datetime(time.year + 1, 1, 1) else: return datetime.datetime(time.year, time.month + 1, 1) elif self.slice == 'year': return datetime.datetime(time.year + 1, 1, 1) def get_labels(self, slices): if self.format is not None: if type(self.format) == str: format_list = [self.format] else: format_list = self.format else: format_list = self.formats[self.slice] return [[slice.from_time.strftime(format) for slice in slices] for format in format_list] def update_slices(self, slices, from_time, to_time, context, last_timestamp=None): if len(slices) > 0: slice_from_time = slices[-1].to_time else: slice_from_time = from_time # Create the necessary slices t = self.get_slice_start(slice_from_time) keys = self.storage.keys(context=context) while t < to_time: end = self.get_next_slice_start(t) self.logger.debug("Creating slice %s - %s", t, end) slice = self.Slice(self.formulas, t, end, keys) slices.append(slice) t = end # Fill them with samples if last_timestamp: update_from_time = max(last_timestamp + datetime.timedelta(seconds=1), from_time) # Add 1 sec to last_timestamp so that the same sample is not retrieved twice else: update_from_time = from_time self.logger.debug("Update from %s ", update_from_time) s = 0 to_delete = 0 localtime_index = keys.index('localtime') for sample in self.storage.samples(update_from_time, to_time, context=context): # find the first slice receiving the samples sample_localtime = sample[localtime_index] while slices[s].to_time < sample_localtime: if slices[s].to_time < from_time: # count of obsolete slices to delete to_delete=s s = s + 1 slices[s].add_sample(sample) last_timestamp = sample_localtime return last_timestamp, to_delete def get_series(self, slices): result = {} for k,v in self.formulas.iteritems(): result[k]={} result[k]['series']={} for key in v.keys(): subkeys = key.split(',') for subkey in subkeys: result[k]['series'][subkey]=[] i = 1 for labels in self.get_labels(slices): literal = 'lbl%d' % i if i > 1 else 'lbl' i += 1 result[k]['series'][literal]=labels for slice in slices: for k,v in slice.formulas.iteritems(): for key,formula in v.iteritems(): value = formula.value() subkeys = key.split(',') if len(subkeys) == 1: value = [ value ] for i in range(len(subkeys)): result[k]['series'][subkeys[i]].append(value[i]) return result def execute(self,data={}, context={}): if data.has_key('time_end'): to_time = parse(data['time_end']) use_cache = False else: to_time = datetime.datetime.now() use_cache = self.caching duration = self.get_slice_duration() times = (self.span - 1) delta= duration * times from_time = self.get_slice_start(to_time - delta) if use_cache: self.logger.debug("Last timestamp: %s", self.last_timestamp) self.lock.acquire() try: if self.last_timestamp < to_time - datetime.timedelta(0,self.period) or self.cached_series is None: if self.cached_slices is None: self.cached_slices = [] last_timestamp, to_delete = self.update_slices(self.cached_slices, from_time, to_time, context, self.last_timestamp) self.cached_slices = self.cached_slices[to_delete:] self.logger.debug('Deleted %s slices', to_delete) self.logger.debug("Last timestamp: %s", self.last_timestamp) self.last_timestamp = last_timestamp self.cached_series = self.get_series(self.cached_slices) finally: self.lock.release() return self.cached_series else: # use_cache == False slices = [] self.update_slices(slices, from_time, to_time, context) return self.get_series(slices) def parse(isodate): if len(isodate) == 10: return datetime.datetime.strptime(isodate, "%Y-%m-%d") else: return datetime.datetime.strptime(isodate, "%Y-%m-%d"+isodate[10]+"%H:%M:%S") def iso_year_start(iso_year): "The gregorian calendar date of the first day of the given ISO year" fourth_jan = datetime.datetime(iso_year, 1, 4) delta = datetime.timedelta(fourth_jan.isoweekday()-1) return fourth_jan - delta def iso_to_gregorian(iso_year, iso_week, iso_day): "Gregorian calendar date for the given ISO year, week and day" year_start = iso_year_start(iso_year) return year_start + datetime.timedelta(iso_day-1, 0, 0, 0, 0, 0, iso_week-1) wfrog-0.8.2+svn953/wfrender/datasource/database.py000066400000000000000000000324431213472117100220010ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . from StringIO import StringIO import sys import logging import datetime class DatabaseConfig(object): url = None username = 'sysdba' password = 'masterkey' class DatabaseDataSource(object): """ Queries the default firebird database for grouped data. Will be replaced in wfrog 0.6 by an implementation using the 'storage' abstraction. """ logger = logging.getLogger("datasource.database") timestamp_field = "TIMESTAMP_LOCAL" separator = '.' switch = False conc = chr(124)*2 BEFORE=1 AFTER=2 lpa="lpad(" lpz=",2,'0')" TEMP= ['avg(temp), min(temp), max(temp) ', ['avg', 'min', 'max'], 'temp, temp, temp', [ 'avg', 'min', 'max' ]] HUM= ['avg(hum)', ['avg'], 'hum', ['avg']] DEW= ['avg(dew_point)', ['avg'], 'dew_point', ['avg']] WIND= ['avg(wind), max(wind_gust)', ['avg', 'max'], 'wind, wind_gust', ['avg', 'max']] PRESS = ['avg(pressure)', ['avg'], 'pressure', ['avg']] RAIN= ['sum(rain), avg(rain_rate)', ['fall', 'rate'], 'rain, rain_rate', ['fall', 'rate']] UV= ['max(uv_index)', ['index'], 'uv_index', ['index']] measure_map = { 'temp': TEMP, 'hum': HUM, 'dew': DEW, 'wind': WIND, 'press': PRESS, 'rain': RAIN, 'uv': UV } url = None username = None password = None table = 'METEO' slice = 'hour' span = 23 measures = [ 'temp', 'hum', 'dew', 'wind', 'sector', 'press', 'rain', 'uv' ] holes = True def get_key(self, p, span, key='', date_format=''): (sql, sep, max_span, next, pos, df) = (p[0], p[1], p[2], p[3], p[4], p[5]) actual_sep = '' if span > max_span: (key, date_format) = self.get_key(next, span / max_span, key, date_format) actual_sep=sep if not actual_sep == '': db_sep = self.conc+"'"+actual_sep+"'"+self.conc else: db_sep='' if pos == self.AFTER: key = key + db_sep + sql date_format = date_format + actual_sep + df else: key = sql + db_sep + key date_format = df + actual_sep + date_format return (key, date_format) def execute(self,data={}, context={}): config = DatabaseConfig() if context.has_key('database'): config.__dict__.update(context['database']) config.__dict__.update(self.__dict__) conc = self.conc lpa = self.lpa lpz = self.lpz separator = self.separator timestamp_field = self.timestamp_field switch = self.switch holes = self.holes begin = parse(data['time_begin']) if data.has_key('time_begin') else None end = parse(data['time_end']) if data.has_key('time_end') else None span = data['time_span'] if data.has_key('time_span') else self.span slice = data['time_slice'] if data.has_key('time_slice') else self.slice YEAR=[ "EXTRACT(YEAR FROM "+timestamp_field+")", '', sys.maxint, None, None, "%Y"] MONTH=[ lpa+"EXTRACT(MONTH FROM "+timestamp_field+")"+lpz+conc+"'"+separator+"'"+conc+"EXTRACT(YEAR FROM "+timestamp_field+")", '', sys.maxint, None, self.BEFORE, "%m"+separator+"%Y"] DAY=[ lpa+"EXTRACT(" + ("DAY" if not switch else "MONTH")+" FROM "+timestamp_field+")"+lpz+conc+"'.'"+conc+lpa+"EXTRACT("+("MONTH" if not switch else "DAY")+" FROM "+timestamp_field+")"+lpz, separator, 363, YEAR, self.BEFORE if not switch else self.AFTER, "%d"+separator+"%m"] HOUR=[ lpa+"EXTRACT(HOUR FROM "+timestamp_field+")"+lpz+conc+"''", ' ', 23, DAY, self.AFTER, "%H"] MINUTE=[ lpa+"EXTRACT(HOUR FROM "+timestamp_field+")"+lpz+conc+"':'"+conc+lpa+"EXTRACT(MINUTE FROM "+timestamp_field+")"+lpz, ' ', 60*24-1, DAY, self.AFTER, "%H:%M"] slices = { 'minute': MINUTE, 'hour': HOUR, 'day': DAY, 'month': MONTH, 'year': YEAR } where_clause="" if begin: where_clause = " WHERE " + timestamp_field + ">='" + format(begin) + "'" if end: where_clause = where_clause + " AND " + timestamp_field + "<='" + format(end) + "'" span=sys.maxint # TODO: calculate exact span else: if span: end=delta(begin, span, slice) where_clause = where_clause + " AND " + timestamp_field + "<='" + format(end) + "'" else: end=datetime.datetime.now() span=sys.maxint # TODO: calculate exact span else: if end: if span: begin = delta(end, -span, slice) where_clause = " WHERE "+timestamp_field + ">='" + format(begin) + "' AND " where_clause = where_clause + timestamp_field + "<='" + format(end) + "'" else: span=sys.maxint # TODO: calculate exact span else: end=datetime.datetime.now() begin=delta(datetime.datetime.now(), -span, slice) if span: where_clause = " WHERE "+timestamp_field + ">='" + format(begin) + "'" else: span=sys.maxint # TODO: calculate exact span (key, date_format) = self.get_key(slices[slice], span) select = StringIO() select.write("SELECT "+key+" AS slice") if slice == "minute": measure_index = 2 else: measure_index = 0 row_length=1 for measure in self.measures: if self.measure_map.has_key(measure): select.write(", ") select.write(self.measure_map[measure][measure_index]) row_length = row_length + len(self.measure_map[measure][measure_index+1]) select.write(" FROM "+self.table + where_clause ) if slice == "minute": select.write(" ORDER BY "+self.timestamp_field) else: select.write(" GROUP BY slice") select.write(" ORDER BY MIN("+self.timestamp_field+")") if self.measures.__contains__("sector"): sector = "22.5*(round(wind_dir/22.5))" sector_select = "SELECT "+sector+", avg(wind), count(*) " + \ " FROM "+self.table + where_clause + \ " AND wind > 0 GROUP BY "+sector sector_gust = "22.5*(round(wind_gust_dir/22.5))" sector_gust_select = "SELECT "+sector_gust+", max(wind_gust)" + \ " FROM "+self.table + where_clause + \ " AND wind > 0 GROUP BY "+sector_gust db = FirebirdDB(config.url, config.username, config.password) db.connect() try: self.logger.debug(select.getvalue()) result = db.select(select.getvalue()) if self.measures.__contains__("sector"): self.logger.debug(sector_select) sector_result = db.select(sector_select) self.logger.debug(sector_gust_select) sector_gust_result = db.select(sector_gust_select) finally: db.disconnect() if holes and begin and end: map = {} for row in result: map[row[0]]=row result = [] d = begin while d <= end: df = d.strftime(date_format) if map.has_key(df): result.append(map[df]) else: r = tuple([df]+([None]*(row_length-1))) result.append(r) d = delta(d, 1, slice) result_data = {} result_data.update(data) items = [] labels = [] for measure in self.measures: if self.measure_map.has_key(measure): key = measure result_data[key]={ 'series': {} } for serie in self.measure_map[measure][measure_index+1]: result_data[key]['series'][serie] = [] result_data[key]['series']['lbl'] = labels items.append((key, serie)) for row in result: labels.append(row[0]) for item in range(0, row_length-1): result_data[items[item][0]]['series'][items[item][1]].append(row[item+1]) if self.measures.__contains__("sector"): if not result_data.has_key('wind'): result_data['wind'] = {} result_data['wind']['sectors'] = { "lbl" : ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'], "freq" : [0]*16, "avg" : [0]*16, "max" : [0]*16 } sector_map = {} for (d,v,c) in sector_result: sector_map[d]=(v,c) sector_gust_map = {} for (d,v) in sector_gust_result: sector_gust_map[d]=v for i in range(0, 16): deg = 22.5*i if sector_map.has_key(deg % 360): result_data['wind']['sectors']['avg'][i % 16] = result_data['wind']['sectors']['avg'][i % 16] + sector_map[deg % 360][0]; result_data['wind']['sectors']['freq'][i % 16] = result_data['wind']['sectors']['freq'][i % 16] + sector_map[deg % 360][1]; if sector_gust_map.has_key(deg % 360): result_data['wind']['sectors']['max'][i % 16] = result_data['wind']['sectors']['max'][i % 16] + sector_gust_map[deg % 360]; result_data['wind']['sectors']['freq'] = normalize(result_data['wind']['sectors']['freq']) return result_data def parse(isodate): if len(isodate) == 10: return datetime.datetime.strptime(isodate, "%Y-%m-%d") else: return datetime.datetime.strptime(isodate, "%Y-%m-%d"+isodate[10]+"%H:%M:%S") def format(d): return d.isoformat(' ') def delta(d, n, slice): d2=d.replace(microsecond=0) if slice == 'minute': return d2+datetime.timedelta(0,n*60) if slice == 'hour': return d2+datetime.timedelta(0,n*3600) if slice == 'day': return d2+datetime.timedelta(n) if slice == 'month': return d2+datetime.timedelta(n*31) # TODO: calculate exact start of month if slice == 'year': return d2+datetime.timedelta(n*365) # TODO: calculate exact start of year def normalize(data): s = sum(data) if s == 0: return data result = [] for d in data: result.append(float(d)/s) return result class FirebirdDB(): def __init__(self, bdd, user='sysdba', password='masterkey', charset='ISO8859_1'): self._bdd = bdd self._user = user self._password = password self._charset = charset def connect(self): import kinterbasdb try: kinterbasdb.init(type_conv=0) except: pass self._db = kinterbasdb.connect(dsn=self._bdd, user=self._user, password=self._password, charset=self._charset) try: self._db.cursor().execute("DECLARE EXTERNAL FUNCTION lpad \ CSTRING(255) NULL, INTEGER, CSTRING(1) NULL \ RETURNS CSTRING(255) FREE_IT \ ENTRY_POINT 'IB_UDF_lpad' MODULE_NAME 'ib_udf'") except: pass try: self._db.cursor().execute("DECLARE EXTERNAL FUNCTION Round \ INT BY DESCRIPTOR, INT BY DESCRIPTOR \ RETURNS PARAMETER 2 \ ENTRY_POINT 'fbround' MODULE_NAME 'fbudf'") except: pass def select(self, sql): cursor = self._db.cursor() cursor.execute(sql) l = [] for e in cursor.fetchall(): l.append(e) cursor.close() self._db.commit() return l def execute(self, sql): cursor = self._db.cursor() cursor.execute(sql) cursor.close() self._db.commit() def disconnect(self): try: self._db.close() except: pass if __name__ == "__main__": logging.basicConfig(level=logging.DEBUG) ds = DatabaseDataSource() ds.url = 'localhost:/var/lib/firebird/2.0/data/wfrog.db' ds.slice = 'day' ds.span = 4 data = {} data['time_begin'] = '2009-11-01' ds.execute(data) wfrog-0.8.2+svn953/wfrender/datasource/simulator.py000066400000000000000000000061071213472117100222520ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . class SimulatorDataSource(object): """ Return a simulated harcoded data. """ query=None renderer=None def __init__(self, query, renderer): self.query=query self.renderer=renderer def execute(self,data={}, context={}): return { "temp" : { "value" : 3, "min" : 1, "max" : 6, "series" : { "lbl" : [ "7:00", "8:00", "9:00", "10:00", "11:00", "12:00", "13:00", "14:00", "15:00", "16:00" ], "avg" : [ -1, -0.6, 1, 2.5, None, 4.2, 3.5, 1, 3.2, 3 ], "min" : [ -3, -3.2, -2, 1, None, 3, 1, 0.2, 2.4, 2.8 ], "max" : [ 2, 1.4, 3, 2.8, None, 4.5, 4.6, 4.3, 5, 5 ] } }, "dew" : { "value" : 3, "series" : { "lbl" : [ "7:00", "8:00", "9:00", "10:00", "11:00", "12:00", "13:00", "14:00", "15:00", "16:00" ], "avg" : [ -4, -3.8, -3.5, -2, None, 1.2, .3, -0.2, 0, 1], } }, "wind" : { "value" : 2.2, "max" : 6, "deg" : 43, "dir" : "NE", "series" : { "lbl" : [ "7:00", "8:00", "9:00", "10:00", "11:00", "12:00", "13:00", "14:00", "15:00", "16:00" ], "avg" : [ 4, 3.8, None, 1, 1.2, .3, 0.2, 0, 1 , 1], "max" : [ 5, 6, None, 1.3, 1.3, .4, 0.2, 0, 1.2 , 1], "deg" : [ 318, None, 300, 310, 300, 300, 300, 345, 12, 60 ], "dir": [ 'NNW', None, 'NW', 'NW', 'NW', 'NW', 'N', 'NNE', 'NE', 'NE'] }, "sectors" : { "lbl" : ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'], "freq" : [ 0.2, 0.1, 0.2, 0.05, 0.05, 0.0, 0.0, 0.0,0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1, 0.3 ], "avg" : [ 0.3, 1, 0.3, 0.1, 0.1, 0.0, 0.0, 0.0, 0.0,0.0, 0.0, 0.0, 0.0, 0.0, 2, 3 ], "max" : [ 2.1, 2, 0.3, 0.1, 0.1, 0.0, 0.0, 0.0, 0.0,0.0, 0.0, 0.0, 0.0, 0.0, 2.4, 6 ] } } } wfrog-0.8.2+svn953/wfrender/datasource/xmlquery.py000066400000000000000000000100611213472117100221130ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import xml.dom.minidom from datetime import datetime import logging class CurrentConditionsXmlDataSource(object): """ Reads data from a Current Conditions XML file. [ Properties ] path [string]: The location of the XML file. """ logger = logging.getLogger('data.currentxml') path = None def execute(self,data={}, context={}): result = {} result['temp1'] = {} result['temp1']['value'] = 99 result['temp1']['unit'] = "C" result['hum1'] = {} result['hum1']['value'] = 0 result['hum1']['unit'] = "%" result['temp0'] = {} result['temp0']['value'] = 99 result['temp0']['unit'] = "C" result['hum0'] = {} result['hum0']['value'] = 0 result['hum0']['unit'] = "%" result['press'] = {} result['press']['value'] = 0 result['press']['unit'] = "mb" result['rain'] = {} result['rain']['value'] = 9999 result['rain']['unit'] = "mm/h" result['wind'] = {} result['wind']['value'] = 9999 result['wind']['max'] = 9999 result['wind']['deg'] = 0 result['wind']['dir'] = 'N' result['wind']['unit'] = "m/s" result['info'] = {} result['info']['timestamp'] = datetime(2001, 01, 01) try: dom = xml.dom.minidom.parse(self.path) except: self.logger.exception("Could not parse "+self.path) try: result['temp1']['value'] = float(dom.getElementsByTagName('th1')[0].getElementsByTagName('temp')[0].childNodes[0].data) except: pass try: result['hum1']['value'] = float(dom.getElementsByTagName('th1')[0].getElementsByTagName('humidity')[0].childNodes[0].data) except: pass try: result['temp0']['value'] = float(dom.getElementsByTagName('thInt')[0].getElementsByTagName('temp')[0].childNodes[0].data) except: pass try: result['hum0']['value'] = float(dom.getElementsByTagName('thInt')[0].getElementsByTagName('humidity')[0].childNodes[0].data) except: pass try: result['press']['value'] = float(dom.getElementsByTagName('barometer')[0].getElementsByTagName('pressure')[0].childNodes[0].data) except: pass try: result['rain']['value'] = float(dom.getElementsByTagName('rain')[0].getElementsByTagName('rate')[0].childNodes[0].data) except: pass try: result['wind']['value'] = float(dom.getElementsByTagName('wind')[0].getElementsByTagName('avgSpeed')[0].childNodes[0].data) result['wind']['max'] = float(dom.getElementsByTagName('wind')[0].getElementsByTagName('gustSpeed')[0].childNodes[0].data) result['wind']['deg'] = float(dom.getElementsByTagName('wind')[0].getElementsByTagName('dirDeg')[0].childNodes[0].data) result['wind']['dir'] = round(result['wind']['deg'] / 22.5 ) result['wind']['unit'] = "m/s" except: pass try: result['info']['timestamp'] = datetime.strptime(dom.getElementsByTagName('time')[0].childNodes[0].data, "%Y-%m-%d %H:%M:%S") except: pass return result wfrog-0.8.2+svn953/wfrender/renderer/000077500000000000000000000000001213472117100173315ustar00rootroot00000000000000wfrog-0.8.2+svn953/wfrender/renderer/__init__.py000066400000000000000000000054161213472117100214500ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import yaml import chart import data import datatable import file import staticfile import ftp import http import scheduler import template import value import meteoclimatic import wunderground import pwsweather import wettercom import sticker import openweathermap # YAML mappings class YamlGoogleChartRenderer(chart.GoogleChartRenderer, yaml.YAMLObject): yaml_tag = u'!chart' class YamlGoogleWindRadarChartRenderer(chart.GoogleChartWindRadarRenderer, yaml.YAMLObject): yaml_tag = u'!windradar' class YamlDataRenderer(data.DataRenderer, yaml.YAMLObject): yaml_tag = u'!data' class YamlDataRenderer(datatable.DataTableRenderer, yaml.YAMLObject): yaml_tag = u'!datatable' class YamlFileRenderer(file.FileRenderer, yaml.YAMLObject): yaml_tag = u'!file' class YamlStaticFileRenderer(staticfile.StaticFileRenderer, yaml.YAMLObject): yaml_tag = u'!staticfile' class YamlFtpRenderer(ftp.FtpRenderer, yaml.YAMLObject): yaml_tag = u'!ftp' class YamlHttpRenderer(http.HttpRenderer, yaml.YAMLObject): yaml_tag = u'!http' class YamlSchedulerRenderer(scheduler.SchedulerRenderer, yaml.YAMLObject): yaml_tag = u'!scheduler' class YamlTemplateRenderer(template.TemplateRenderer, yaml.YAMLObject): yaml_tag = u'!template' class YamlValueRenderer(value.ValueRenderer, yaml.YAMLObject): yaml_tag = u'!value' class YamlMeteoclimaticRenderer(meteoclimatic.MeteoclimaticRenderer, yaml.YAMLObject): yaml_tag = u'!meteoclimatic' class YamlWundergroundRenderer(wunderground.WeatherUndergroundPublisher, yaml.YAMLObject): yaml_tag = u'!wunderground' class PwsWeatherRenderer(pwsweather.PwsWeatherPublisher, yaml.YAMLObject): yaml_tag = u'!pwsweather' class WetterComRenderer(wettercom.WetterComPublisher, yaml.YAMLObject): yaml_tag = u'!wettercom' class StickerRenderer(sticker.StickerRenderer, yaml.YAMLObject): yaml_tag = u'!sticker' class OpenWeatherMapPublisher(openweathermap.OpenWeatherMapPublisher, yaml.YAMLObject): yaml_tag = u'!openweathermap' wfrog-0.8.2+svn953/wfrender/renderer/chart.py000066400000000000000000001032671213472117100210150ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . if __name__=="__main__": import os.path import sys sys.path.append(os.path.abspath(sys.path[0] + '/../..')) from pygooglechart import Chart from pygooglechart import _check_colour from pygooglechart import Axis from pygooglechart import RadarChart from pygooglechart import SimpleLineChart from math import log import math import wfcommon.units import webcolors import re import copy import logging import sys # Limit of the text density on x-axis to start removing label LABEL_DENSITY_THRESHOLD = 1.78 # Make min robust to None def rmin(a,b): result = min(a,b) if result == None: result = max(a,b) if result == None: result = 0 return result def amin(a): m=sys.maxint for i in a: if not i == None and i < m: m=i return m # Default configuration class ChartConfig(object): # Graph attributes width = 250 height = 150 bgcolor = '00000000' ymargin = [ 1, 1 ] axes = True ticks = True legend = None legend_pos = 'b' nval = 100 # Drawing color = '008000' thickness = 1.5 text = '8d7641' size = 10 # for text and markers fill = None # fill color of the arrow style = 'v' # for markers dash = None accumulate = False # draw accumulated values interpolate = False # line series interpolation of missing values. # Series area = None zero = None max = None min = None last = None marks = None space = 1.5 # Spacing between marks # Drawing order order = 0 # Wind Radar radius = 18 # max value for logarithmic scaling median = 2.5 # value in the middle of the graph intensity = 0.5 # ratio of intensity for wind radar charts tail = None arrow = { } trace = None ratio = 3 # Size i length = 5 # Number of traces sectors = None gust = None bars = None lines = None areas = None beaufort = None def __missing__(item): return None # avoid exception when item is missing class GoogleChartRenderer(object): """ Renders the data as a google chart. render result [string]: The google chart URL. [ Properties ] series [dict]: Defines which series data are rendered on the chart and their options. Keys follow the format 'measure.serie', e.g. 'temp.avg'. Value contains a dictionary of rendering options. See below the available options and their scope. axes [true|false] (optional): Display or not the axes. Defaults to 'true'. height [numeric] (optional): Height in pixels of the generated graph image. Defaults to 125. labels [string] (optional): Defines which data must be used to render the chart labels on the X axis. The value has the format 'measure.lbl', e.g. 'temp.label' to specify which data to use. nval [numeric] (optional): Maximum resolution of the graph, in order to avoid too much values in the Google Chart URL. Defaults to 100. ticks [true|false] (optional): Display or not the tick along the axes. Defaults to 'true'. width [numeric] (optional): Width in pixels of the generated graph image. Defaults to 250. ymargin [pair of numeric] (optional): Space to keep above and below the graph extreme values, in graph units. Defaults to [1, 1]. zero [dict] (optional): Draw an horizontal line at y=0. Makes sense when graph values can be negative. Contains rendering options. For default rendering (a thin gray line), specify an empty dictionary '{}'. [ Markers ] In the scope of a data serie, one can specify markers highlighting some data points: min [dict] (optional): The minimum value of the serie. max [dict] (optional): The maximum value of the serie. last [dict] (optional): The last value of the serie. [ Rendering Options ] Some options are of type 'color'. They can take either - an hexadecimal triplet string, e.g A3F500 (without dash and in upper case. - or a named color defined in the CSS specification, e.g 'blue', 'red', 'wheat', ... Rendering options are always optional and can be used in different scope: - (G) the whole graph - (S) a given data serie - (M) a marker bgcolor (G) [color]: Background color of the graph. color (G, S, M) [color]: Foreground color of the line or bar. dash (G, S) [pair of numeric]: If present, write a dashed line for the serie. The first value represents the length of dash elements, the second the space between them. accumulate (G, S) [true|false]: If 'true' the data is transformed to write its accumulated value. Useful when drawing accumulated rain. interpolate (G, S) [true|false]: If 'true', draw a continuous line instead of blank for missing values. This is useful when the chart resolution is finer than the sampling period. Ignored when accumulate = 'true'. order (S) [numeric]: Defines in which orders series are drawn on the graph. size (G, S, M) [numeric]: Text size. Defaults to 10. text (G, S, M) [color]: Foreground color of the text. thickness (G, S, M) [numeric]: Pixel thickness of the line. """ series = None labels = None logger = logging.getLogger("renderer.chart") def render(self,data={}, context={}): assert self.series is not None, "'chart.series' must be set" converter = wfcommon.units.Converter(context["units"]) # merge builtin defaults, context and renderer config config = ChartConfig() if context.has_key('chart'): config.__dict__.update(context['chart']) config.__dict__.update(self.__dict__) # create the chart chart = SimpleLineChart(config.width, config.height) colors = [] legend_set = False legend = [] chart_min = sys.maxint chart_max = -sys.maxint # Prepare series config ordered_series = [] for key, serie in self.series.iteritems(): serie_config = ChartConfig() serie_config.__dict__.update(config.__dict__) serie_config.__dict__.update(serie) ordered_series.append( (serie_config.order, key, serie) ) ordered_series.sort( cmp=lambda x,y: cmp(x[0],y[0]) ) ordered_keys = [] for order, key, serie in ordered_series: ordered_keys.append(key) # Draws for each serie index=0 for order, key, serie in ordered_series: serie_config = ChartConfig() serie_config.__dict__.update(config.__dict__) serie_config.__dict__.update(serie) serie_data = copy.copy(data[key.split('.')[0]]['series'][key.split('.')[1]]) measure = key.split('.')[0] if flat(serie_data): # Series with all data = None continue if serie_config.accumulate: serie_data = accumulate(serie_data) elif serie_config.interpolate: serie_data = interpolate(serie_data) # Compute min and max value for the serie and the whole chart min_data = amin(serie_data) chart_min = rmin(chart_min, min_data) if serie_data.__contains__(min_data): min_index = serie_data.index(min_data) else: min_index = None max_data = max(serie_data) chart_max = max(chart_max, max_data) if serie_data.__contains__(max_data): max_index = serie_data.index(max_data) else: max_index = None (serie_data, min_index, max_index) = compress_to(serie_data, config.nval, min_index, max_index) chart.add_data(serie_data) colors.append(_valid_color(serie_config.color)) if serie_config.max and not max_index == None : max_config = ChartConfig() max_config.__dict__.update(serie_config.__dict__) max_config.__dict__.update(serie_config.max) str_max_data = str(round(converter.convert(measure, max_data), 1)) chart.add_marker(index, max_index, 't'+str_max_data, _valid_color(max_config.text), max_config.size) chart.add_marker(index, max_index, max_config.style, _valid_color(max_config.color), max_config.thickness) if serie_config.min and not min_index == None: min_config = ChartConfig() min_config.__dict__.update(serie_config.__dict__) min_config.__dict__.update(serie_config.min) str_min_data = str(round(converter.convert(measure, min_data), 1)) chart.add_marker(index, min_index, 't'+str_min_data, _valid_color(min_config.text), min_config.size) chart.add_marker(index, min_index, min_config.style, _valid_color(min_config.color), min_config.thickness) if serie_config.last: last_config = ChartConfig() last_config.__dict__.update(serie_config.__dict__) last_config.__dict__.update(serie_config.last) last_index=len(serie_data)-1 last_data = serie_data[last_index] if last_data: str_last_data = str(round(converter.convert(measure, last_data), 1)) chart.add_marker(index, last_index, 't'+str(last_data), _valid_color(last_config.text), last_config.size) chart.add_marker(index, last_index, last_config.style, _valid_color(last_config.color), last_config.thickness) if serie_config.area: fill_config = ChartConfig() fill_config.__dict__.update(serie_config.__dict__) fill_config.__dict__.update(serie_config.area) to = ordered_keys.index(fill_config.to) chart.add_fill_range(_valid_color(fill_config.color), index, to) if serie_config.dash: chart.set_line_style(index, serie_config.thickness, serie_config.dash, serie_config.dash) else: chart.set_line_style(index, serie_config.thickness) if serie_config.legend: legend.append(serie_config.legend) legend_set = True else: legend.append('') if serie_config.marks: mark_config = ChartConfig() mark_config.__dict__.update(serie_config.__dict__) mark_config.__dict__.update(serie_config.marks) mark_data = copy.copy(data[mark_config.serie.split('.')[0]]['series'][mark_config.serie.split('.')[1]]) mark_data = compress_to(mark_data, config.nval, min_index, max_index)[0] for i, m in enumerate(mark_data): if not m: mark_data[i] = " " density = max(1.0, 1.0 * mark_config.space * len("".join(mark_data))*mark_config.size / config.width) for i, v in enumerate(mark_data): if (i +1) % round(density) == 0: if serie_data[i] != 0: text = str(mark_data[i]) else: text = " " chart.add_marker(index, i, 't'+text, _valid_color(mark_config.color), mark_config.size) index = index + 1 # Compute vertical range if config.axes: range_min_ref_units = 0 if not chart_min == sys.maxint and not chart_max == -sys.maxint: range_min = chart_min-config.ymargin[0] range_max = chart_max+config.ymargin[1] range_min_target_units = math.floor(converter.convert(measure, range_min)) range_max_target_units = math.ceil(converter.convert(measure, range_max)) range_min_ref_units = converter.convert_back(measure, range_min_target_units) range_max_ref_units = converter.convert_back(measure, range_max_target_units) self.logger.debug("Y range: "+str(range_min_target_units) +" "+str(range_max_target_units)) chart.set_axis_range(Axis.LEFT, range_min_target_units, range_max_target_units+1) chart.add_data([range_min_ref_units, range_max_ref_units]) colors.append("00000000") else: chart.set_axis_range(Axis.LEFT, 0, 100) chart.set_axis_style(0, _valid_color(config.text), config.size, 0, Axis.BOTH if config.ticks else Axis.AXIS_LINES) else: chart.set_axis_labels(Axis.LEFT, []) chart.set_axis_style(0, _valid_color(config.text), config.size, 0, Axis.TICK_MARKS, _valid_color(config.bgcolor)) if config.zero and config.axes and range_min_ref_units < 0 and range_max_ref_units > 0: zero_config = ChartConfig() zero_config.__dict__.update(config.__dict__) zero_config.__dict__.update(config.zero) chart.add_data([0]*2) colors.append(_valid_color(zero_config.color)) chart.set_line_style(index, zero_config.thickness) chart.set_colours(colors) chart.fill_solid(Chart.BACKGROUND, _valid_color(config.bgcolor)) if legend_set: chart.set_legend(legend) chart.set_legend_position(config.legend_pos) if self.labels: labels_data = copy.copy(data[self.labels.split('.')[0]]['series'][self.labels.split('.')[1]]) labels_data = compress_to(labels_data, config.nval, None, None)[0] if config.axes: density = 1.0 * len("".join(labels_data))*config.size / config.width if density > LABEL_DENSITY_THRESHOLD: for i, v in enumerate(labels_data): if i % round(density) != 0: labels_data[i] = ' ' chart.set_axis_labels(Axis.BOTTOM, labels_data) chart.set_axis_style(1, _valid_color(config.text), config.size, 0, Axis.BOTH if config.ticks else Axis.AXIS_LINES) else: chart.set_axis_labels(Axis.BOTTOM, []) chart.set_axis_style(1, _valid_color(config.text), config.size, 0, Axis.TICK_MARKS, _valid_color(config.color)) try: return chart.get_url()+"&chma=10,10,10,10" # add a margin except: self.logger.exception("Could not render chart") return "http://chart.apis.google.com/chart?cht=lc&chs="+str(config.width)+"x"+str(config.height) class GoogleChartWindRadarRenderer(object): """ Renders wind data as a radar google chart URL. render result [string]: The google chart URL. [ Properties ] height [numeric] (optional): Height in pixels of the generated graph image. Defaults to 125. width [numeric] (optional): Width in pixels of the generated graph image. Defaults to 250. [ Rendering Options ] Roughly, the same rendering options as !chart are available. Additionally, the following options are specific to this renderer: sectors [dict] (optional): If present, shows pie sectors shaded according to the frequency of wind in each direction. The dict contain the rendering options for the sector painting, i.e. 'color' and 'intensity'. Default to None. radius [numeric] (optional): Asymptotic limit value corresponding to maximum wind. This value is used to compute the logarithmic scale. median [numeric] (optional): Value of wind speed that will be represented as a being in the middle of the radar chart range. Together with 'radius', this value defines the logarithmic scale for the wind speed. arrow [dict] (optional): If present, draws the wind direction arrow. The dict can contain options 'color', 'thickness' and 'text'. The 'text' options, is set to a color, shows the current wind value on the graph. tail [dict] (optional): Rendering options for the arrow tail. The dict can contain options 'color' and 'thickness'. trace [dict] (optional): If present, draws crown bullets showing the last direction of the wind. The dict can contain options 'color', 'size', 'length' and 'ratio'. The length corresponds to the number of trace bullets to draw. The ratio is proportional to the way bullet size fades out. bars [dict] (optional): Show bars for the wind values. The dict can contain 'color', 'gust' and 'thickness'. The value 'color' is for average wind values and 'gust' is the color for maximum wind values. lines [dict] (optional): Show lines for the wind values. The dict can contain 'color', 'gust' and 'thickness'. The value 'color' is for average wind values and 'gust' is the color for maximum wind values. areas [dict] (optional): Show shaded area for the wind values. The dict can contain 'color', 'gust'. The value 'color' is for average wind values and 'gust' is the color for maximum wind values. beaufort [dict] (optional): Show a the beaufort value in the radar chart as a large digit. Options are 'color' and 'intensity'. max [dict] (optional): Show a marker for the maximum wind value on the chart. Options are 'color', 'thickness', 'text' and 'size'. """ key = 'wind' sector_key = 'sectors' # 'new' sector calculation from accumulator def render(self,data={}, context={}): # Prepare config config = ChartConfig() if context.has_key('chart'): config.__dict__.update(context['chart']) config.__dict__.update(self.__dict__) tail_config = ChartConfig() tail_config.__dict__.update(config.__dict__) if config.tail: tail_config.__dict__.update(config.tail) else: tail_config.color = "00000000" arrow_config = ChartConfig() arrow_config.__dict__.update(config.__dict__) arrow_config.text = "00000000" if config.arrow: arrow_config.__dict__.update(config.arrow) else: arrow_config.color = "00000000" gust_config = ChartConfig() gust_config.__dict__.update(config.__dict__) gust_config.text = "00000000" if config.max: gust_config.__dict__.update(config.max) else: gust_config.color = "00000000" trace_config = ChartConfig() trace_config.__dict__.update(config.__dict__) if config.trace: trace_config.__dict__.update(config.trace) sectors_config = ChartConfig() sectors_config.__dict__.update(config.__dict__) if config.sectors: sectors_config.__dict__.update(config.sectors) bars_config = ChartConfig() bars_config.__dict__.update(config.__dict__) if config.bars: bars_config.__dict__.update(config.bars) lines_config = ChartConfig() lines_config.__dict__.update(config.__dict__) if config.lines: lines_config.__dict__.update(config.lines) else: lines_config.color = "00000000" lines_config.gust = "00000000" areas_config = ChartConfig() areas_config.__dict__.update(config.__dict__) if config.areas: areas_config.__dict__.update(config.areas) beaufort_config = ChartConfig() beaufort_config.__dict__.update(config.__dict__) if config.beaufort: beaufort_config.__dict__.update(config.beaufort) # Prepare data max = config.median * 2 if data[self.key].has_key('sectors'): sector_data = data[self.key]['sectors'] # 'old' sector from db datasource else: if data.has_key(self.sector_key): # 'new' sector data from accumulator sector_data = self.calculate_accumulated_sector_data(data[self.sector_key]['series']) else: if data[self.key].has_key('series'): sector_data = self.calculate_cheap_sector_data(data[self.key]['series']) else: sector_data = None if data[self.key].has_key('value'): current_noscale = data[self.key]['value'] last_gust_noscale = data[self.key]['max'] pos = int(round(data[self.key]['deg'] * 16 / 360.0)) if pos > 15: pos = 0 current = self.scale(current_noscale, config.median, config.radius) last_gust_scaled = self.scale(last_gust_noscale, config.median, config.radius) arrow_thickness = 0.3+3.0*arrow_config.thickness*current/max if config.bars or config.areas or config.sectors: avg = [] for val in sector_data['avg']: avg.append(self.scale(val, config.median, config.radius)) avg.append(avg[0]) gust = [] for val in sector_data['max']: gust.append(self.scale(val, config.median, config.radius)) gust.append(gust[0]) else: avg = [0] * 16 gust = [0] * 16 line = [0] * 16 crown = [max] * 16 tail = [0] * 16 last_gust = [0] * 16 head = [0] * 16 if data[self.key].has_key('value'): line[pos] = max if current > 0 else 0 tail[pos] = current last_gust[pos] = last_gust_scaled head[ (pos - 1 + 16) % 16 ] = current*0.6 head[ (pos + 16) % 16 ] = current*0.3 head[ (pos + 1) % 16 ] = current*0.6 chart = RadarChart(config.width, config.height, y_range=(0,max) ) chart.add_data([0] * 2) chart.add_data(line) chart.add_data(gust) chart.add_data(avg) chart.add_data(crown) chart.add_data(tail) chart.add_data(head) chart.add_data(last_gust) if config.bars: chart.add_marker(2, -1, "v", _valid_color(bars_config.gust), bars_config.thickness, -1) chart.add_marker(3, -1, "v", _valid_color(bars_config.color), bars_config.thickness, -1) if config.beaufort: chart.add_marker(0, "220:0.9", "@t"+str(int(round(wfcommon.units.MpsToBft(current_noscale)))), _valid_color(beaufort_config.color) + "%02x" % (beaufort_config.intensity*255), rmin(config.height, config.width)-config.size*5, -1) colors = ["00000000", _valid_color(tail_config.color), _valid_color(lines_config.gust), _valid_color(lines_config.color), "00000000", _valid_color(arrow_config.color), _valid_color(arrow_config.color), "00000000"] if config.sectors: for i in range(0,16): sec = [0] * 16 avg = self.scale(sector_data['avg'][i], config.median, config.radius) freq_value = sector_data['freq'][i]*255 freq_value = rmin(255, (1+2*sectors_config.intensity) * freq_value) freq = "%02x" % int(freq_value) start = i-0.5 stop = i+0.5 chart.add_vertical_range(_valid_color(sectors_config.color)+freq, start, stop) if config.trace: nval = len(data[self.key]['series']['deg']) nbullet = rmin(trace_config.length, nval) minsize = trace_config.size / float(trace_config.ratio) maxsize = trace_config.size size = float(maxsize) inc = (maxsize-minsize) / nbullet n = 0 for p in reversed(data[self.key]['series']['deg']): chart.add_marker(4, int(p/22.5), 'o', _valid_color(trace_config.color), size) size = size - inc n = n + 1 if n == nbullet: break if config.areas: chart.add_fill_range(_valid_color(areas_config.gust), 3, 2) chart.add_fill_range(_valid_color(areas_config.color), 3, 0) if config.max: chart.add_marker(7, pos, 'o', _valid_color(gust_config.color), gust_config.thickness) chart.add_marker(7, pos, 't'+str(round(last_gust_noscale,1)), _valid_color(gust_config.text), gust_config.size) if config.arrow: chart.add_marker(0, 0, 'o', _valid_color(arrow_config.color), arrow_thickness) chart.add_marker(5, pos, 't'+str(round(current_noscale,1)), _valid_color(arrow_config.text), arrow_config.size) chart.add_fill_range(_valid_color(arrow_config.fill), 6, 0) chart.set_colours( colors ) if config.axes: chart.set_axis_labels(Axis.BOTTOM, ['N', '', 'NE', '', 'E', '', 'SE', '', 'S', '', 'SW', '', 'W', '', 'NW', '']) chart.set_axis_style(0, _valid_color(config.text), config.size, 0, 'l', _valid_color(config.bgcolor)); if data[self.key].has_key('value'): chart.set_line_style(1, tail_config.thickness) else: chart.set_line_style(1, 0) chart.set_line_style(2, lines_config.thickness) chart.set_line_style(3, lines_config.thickness) chart.set_line_style(4, 0) if data[self.key].has_key('value'): chart.set_line_style(5, arrow_thickness) chart.set_line_style(6, arrow_thickness) else: chart.set_line_style(5, 0) chart.set_line_style(6, 0) chart.fill_solid(Chart.BACKGROUND, _valid_color(config.bgcolor)) return chart.get_url() def scale(self, value, mean, max): if value < 0: value=0 if mean == max / 2: return value else: return (mean/log((max/mean-2)+1))*log(((max/mean-2)/mean)*value+1) def calculate_cheap_sector_data(self, serie_data): sector_data = {} sector_data['lbl'] = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'] sector_data['avg'] = 16 * [ 0.0 ] sector_data['freq'] = 16 * [ 0.0 ] sector_data['max'] = 16 * [ 0.0 ] total_count=0 for i in range(len(serie_data['avg'])): avg = serie_data['avg'][i] max = serie_data['max'][i] dir = serie_data['dir'][i] if avg > 0 and dir is not None: index = sector_data['lbl'].index(dir) sector_data['freq'][index] = sector_data['freq'][index] + 1 total_count = total_count + 1 sector_data['avg'][index] = sector_data['avg'][index] + avg if max > sector_data['max'][index]: sector_data['max'][index] = max # divide sums to calculate averages for i in range(len(sector_data['avg'])): if sector_data['freq'][i] > 0: sector_data['avg'][i] = sector_data['avg'][i] / sector_data['freq'][i] # normalize frequencies if total_count > 0: for i in range(len(sector_data['freq'])): sector_data['freq'][i] = sector_data['freq'][i] / total_count return sector_data def calculate_accumulated_sector_data(self, serie_data): sector_data = {} sector_data['lbl'] = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'] d_avg = sector_data['avg'] = 16 * [ 0.0 ] d_freq = sector_data['freq'] = 16 * [ 0.0 ] d_max = sector_data['max'] = 16 * [ 0.0 ] avg_counts = 16 * [0.0] count = len(serie_data['avg']) s_avg=serie_data['avg'] s_max=serie_data['max'] s_freq=serie_data['freq'] for i in range(count): for j in range(16): if s_avg[i][j] > 0: d_avg[j] = d_avg[j]+s_avg[i][j] avg_counts[j] = avg_counts[j] + 1 if s_max[i][j] > d_max[j]: d_max[j] = s_max[i][j] d_freq[j] = d_freq[j] + s_freq[i][j] for i in range(16): if avg_counts[i] > 0: d_avg[i] = float(d_avg[i]) / avg_counts[i] sum_freq = sum(d_freq) if sum_freq > 0: for i in range(16): d_freq[i] = d_freq[i] / sum_freq return sector_data def _axis_set_style(self, colour, font_size=None, alignment=None, drawing_control=None, tick_colour=None): _check_colour(colour) self.colour = colour self.font_size = font_size self.alignment = alignment self.drawing_control = drawing_control self.tick_colour = tick_colour if tick_colour is not None: _check_colour(tick_colour) self.has_style = True def _axis_style_to_url(self): bits = [] bits.append(str(self.axis_index)) bits.append(self.colour) if self.font_size is not None: bits.append(str(self.font_size)) if self.alignment is not None: bits.append(str(self.alignment)) if self.drawing_control is not None: assert(self.drawing_control in Axis.DRAWING_CONTROL) bits.append(self.drawing_control) if self.tick_colour is not None: bits.append(self.tick_colour) return ','.join(bits) Axis.AXIS_LINES = 'l' Axis.TICK_MARKS = 't' Axis.BOTH = 'lt' Axis.DRAWING_CONTROL = (Axis.AXIS_LINES, Axis.TICK_MARKS, Axis.BOTH) def _chart_set_axis_style(self, axis_index, colour, font_size=None, \ alignment=None, drawing_control=None, tick_colour=None): try: self.axis[axis_index].set_style(colour, font_size, alignment, drawing_control, tick_colour) except IndexError: raise InvalidParametersException('Axis index %i has not been created' % axis) Axis.set_style = _axis_set_style Axis.style_to_url = _axis_style_to_url Chart.set_axis_style = _chart_set_axis_style def _valid_color(color): if color == None or color == "None": return "00000000" if re.match("[A-F0-9]+", color): return color else: return webcolors.name_to_hex(color)[1:] def flat(data): if len(data)==0: return true for d in data: if d != None: return False return True def interpolate(data): result = copy.copy(data) (last, index, count) = (None, None, 0) for i,val in enumerate(data): if val is None: if last is not None: # ignore leading None(s) if not index: # if first None index = i count = count + 1 else: if index: # there has been None(s) before for j in range(index, i): result[j] = last + (j - index + 1)*(val - last)/float(count+1) (index, count) = (None, 0) last = val return result def accumulate(data): result = [] acc = None for d in data: if d is not None: if acc is None: acc = 0 else: acc += d result.append(acc) return result def compress_to(data, n, min_index, max_index): new_min_index = min_index new_max_index = max_index while len(data) > n: l = len(data) d = l-n # how many values to remove r = l / d # each r-th must be removed #print "compress "+str(l)+" to "+str(n)+" by "+str(r) if r < 2: r = 2 (data, new_min_index, new_max_index) = compress(data, r, new_min_index, new_max_index) #print "compressed to "+str(len(data)) return (data, new_min_index, new_max_index) def compress(data, ratio, min_index, max_index): result = [] r = ratio last=None new_min_index=0 new_max_index=0 min = None max = None for i, v in enumerate(data): if i == max_index: max=v if i == min_index: min=v if v is not None: last=v if not i % r == 0: if not min == None: new_min_index = len(result) result.append(min) min = None elif not max == None: new_max_index = len(result) result.append(max) max = None else: result.append(v if v is not None else last) last=None return (result, new_min_index, new_max_index) if __name__=="__main__": serie_data = { "lbl" : [ "7:00", "8:00", "9:00", "10:00", "11:00", "12:00", "13:00", "14:00", "15:00", "16:00" ], "avg" : [ 4, 3.8, None, 1, 1.2, .3, 0.2, 0, 1 , 1], "max" : [ 5, 6, None, 1.3, 1.3, .4, 0.2, 0, 1.2 , 1], "deg" : [ 318, None, 300, 310, 300, 300, 300, 345, 12, 60 ], "dir": [ 'NNW', None, 'NW', 'NW', 'NW', 'NW', 'N', 'NNE', 'NE', 'NE'] } r = GoogleChartWindRadarRenderer() print r.calculate_cheap_sector_data(serie_data) wfrog-0.8.2+svn953/wfrender/renderer/data.py000066400000000000000000000032011213472117100206100ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . from wfcommon.dict import merge class DataRenderer(object): """ Executes a data query and pass the result to a wrapped renderer. render result [any]: The result of the wrapped renderer. [ Properties ] source [datasource]: A data source performing the query and returning a data structure. renderer [renderer]: A renderer called after the query was performed. The data structure is passed as parameter. """ source=None renderer=None def render(self,data={}, context={}): assert self.source is not None, "'data.source' must be set" assert self.renderer is not None, "'data.renderer' must be set" new_data = self.source.execute(data=data, context=context) merge(new_data, data) return self.renderer.render(data=new_data, context=context) wfrog-0.8.2+svn953/wfrender/renderer/datatable.py000066400000000000000000000051001213472117100216200ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . from wfcommon.dict import merge import wfcommon.units class DataTableRenderer(object): """ Executes a data query and renders the result in a data table. render result [any]: The data table corresponding to the data query. [ Properties ] source [datasource] (optional): A data source performing the query and returning a data structure. When using !datatable inside a !data it is not necessary to inform (by default it takes the datasource of the !data element). label [integer] (optional): Label to be used when creating the table. By default "1". """ source=None label=1 def render(self,data={}, context={}): if self.source != None: new_data = self.source.execute(data=data, context=context) else: new_data = data result = {} converter = wfcommon.units.Converter(context["units"]) label_key = 'lbl' + str(self.label) if self.label > 1 else 'lbl' for measure in new_data.keys(): if measure != 'sectors' and type(new_data[measure]) == dict: lbls = new_data[measure]['series'][label_key] for lbl in lbls: if not lbl in result: result[lbl] = {} for (serie, values) in new_data[measure]['series'].iteritems(): if serie[:3] != 'lbl': for i in range(len(lbls)): if serie == 'count': # Unit conversion cannot be applied to count formula result[lbls[i]][measure + '_' + serie] = values[i] else: result[lbls[i]][measure + '_' + serie] = converter.convert(measure, values[i]) return result wfrog-0.8.2+svn953/wfrender/renderer/file.py000066400000000000000000000036111213472117100206230ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import os, time class FileRenderer(object): """ Writes the result of the wrapped renderer to a file. Currently supports only text output. render result [string]: The path to the generated file. [ Properties ] path [string]: The absolute or relative path to the file to create. suffix [string] (optional): If present, specifies that the filename is generated by the path, a generated unique id and the provided suffix. Useful for generating temporary files. """ renderer = None path = None suffix = None def render(self, data={}, context={}): assert self.path is not None, "'file.path' must be set" [ mime, content ] = self.renderer.render(data=data, context=context) if self.suffix: filename=self.path+"-"+str(os.getpid())+"-"+ \ str(int(( time.time() % 1000 ) * 10000))+"." + self.suffix else: filename=self.path f = open(filename, "w") try: f.write(content) finally: f.close() return filename wfrog-0.8.2+svn953/wfrender/renderer/ftp.py000066400000000000000000000074641213472117100205070ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import ftplib import logging import time import socket import os.path # Set up socket timeout to prevent hangs when ftp sites fail socket.setdefaulttimeout(30) # 30 seconds class FtpRenderer(object): """ Send rendered files by FTP. Typically used with TemplateRenderer. render result [none]: Nothing is returned by this renderer. [ Properties ] renderers [dict]: Renderers providing the filenames to upload. host [string]: FTP site hostname. port [numeric] (optional): FTP site port. Defaults to 21. directory [string] (optional): Location on the FTP site where the files will be uploaded. username [string]: FTP site username. password [string]: FTP site password. """ renderers = None host = None port = 21 directory = None username = None password = None logger = logging.getLogger("renderer.ftp") def render(self, data={}, context={}): assert self.host is not None, "'ftp.host' must be set" assert self.username is not None, "'ftp.username' must be set" assert self.password is not None, "'ftp.password' must be set" files= {} for key in self.renderers.keys(): self.logger.info("Rendering %s" % key) files[key] = self.renderers[key].render(data=data, context=context) errors = 0 while True: try: ftp = ftplib.FTP() self.logger.debug("Connecting to %s:%d" % (self.host, self.port)) ftp.connect(self.host, self.port) self.logger.debug("Authenticating...") ftp.login(self.username, self.password) self.logger.info("Connected to %s@%s:%d" % (self.username, self.host, self.port)) if self.directory is not None: self.logger.debug("Moving to directory %s" % self.directory) ftp.cwd(self.directory) for remote_file, local_file in files.iteritems(): self.logger.debug("Sending %s to %s" % (local_file, remote_file)) if os.path.exists(local_file): f = open(local_file, 'r') ftp.storbinary("STOR %s" % remote_file, f) f.close() self.logger.info("Sent %s to %s" % (local_file, remote_file)) else: self.logger.error("Local file %s does not exist, skipping..." % local_file) ftp.quit() break except Exception, e: try: ftp.close() except: pass errors += 1 if errors < 3: self.logger.warning("Error sending files by FTP (retrying in 5 secs.): %s" % str(e)) time.sleep(5) else: self.logger.error("Error sending files by FTP (aborting): %s" % str(e)) break wfrog-0.8.2+svn953/wfrender/renderer/http.py000066400000000000000000000245171213472117100206730ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import string,cgi,time import threading import socket import select from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer from SimpleHTTPServer import SimpleHTTPRequestHandler import copy import urlparse, cgi, Cookie import logging import posixpath import urllib import os class HttpRenderer(object): """ Renderer starting an embedded HTTP server and serves the content results from the wrapped renderers. The URL corresponds to the wrapped renderer names. Only one such renderer must be configured in one process. This renderer runs indefinitely until close() is called. render result [none]: Nothing is returned by this renderer. [ Properties ] root [renderer] (optional): A renderer providing a result served on the base URI. renderers [dict] (optional): Must be set if root is not set. A key/value dictionary of renderers providing a results served on URIs corresponding to the key names. port [numeric] (optional): The listening TCP port. Defaults to 7680. cookies [list] (optional): List of context sections overridable with a cookie using the -set- uri. static [string] (optional): Activates the serving of static files and specify under which URL path they are served. Disabled by default. docroot [string] (optional): Root directory served when static content is enabled. Defaults to /var/wwww. """ renderers = None port = 7680 root = None cookies = [] static = None docroot = "/var/www" logger = logging.getLogger("renderer.http") def render(self, data={}, context={}): self.context = copy.deepcopy(context) self.context["http"] = True # Put in the context that we use the http render. It may be useful to know that in templates. self.data = data try: global _HttpRendererSingleton _HttpRendererSingleton = self self.server = StoppableHTTPServer(('', self.port), HttpRendererHandler) self.server.allow_reuse_address self.logger.info('Started server on port ' + str(self.port)) self.server.serve_forever() self.server.socket.close() self.logger.debug('Stopped listening') except KeyboardInterrupt: self.logger.info('^C received, shutting down server') self.server.shutdown() raise KeyboardInterrupt() except Exception, e: self.logger.exception(e) raise def close(self): self.logger.debug('Close requested') self.server.shutdown() class HttpRendererHandler(BaseHTTPRequestHandler): def do_GET(self): global _HttpRendererSingleton renderers = _HttpRendererSingleton.renderers root = _HttpRendererSingleton.root context = copy.deepcopy(_HttpRendererSingleton.context) cookie_sections = _HttpRendererSingleton.cookies data = copy.deepcopy(_HttpRendererSingleton.data) try: params = cgi.parse_qsl(urlparse.urlsplit(self.path).query) for p in params: data[p[0]] = p[1] content = None name = urlparse.urlsplit(self.path).path.strip('/') if name == "-set-": if data.has_key("s") and data.has_key("k") and data.has_key("v"): section = data["s"] key = data["k"] value = data["v"] if not cookie_sections.__contains__(section): self.send_error(403,"Permission Denied") return context[section][key]=value cookie = Cookie.SimpleCookie() cookie[section+"."+key]=value self.send_response(302) self.send_header('Location', self.headers["Referer"] if self.headers.has_key("Referer") else "/") self.wfile.write(cookie) self.end_headers() return else: self.send_error(500,"Missing parameters") return cookie_str = self.headers.get('Cookie') if cookie_str: try: cookie = Cookie.SimpleCookie(cookie_str) except Exception: cookie = Cookie.SimpleCookie(cookie_str.replace(':', '')) for i in cookie: parts = i.split('.') if len(parts) == 2: section = parts[0] key = parts[1] if cookie_sections.__contains__(section) and context[section].has_key(key): context[section][key]=cookie[i].value if _HttpRendererSingleton.static and self.path == "/"+_HttpRendererSingleton.static: self.send_response(301); self.send_header("Location", self.path + "/") self.end_headers() return if _HttpRendererSingleton.static and self.path.startswith("/"+_HttpRendererSingleton.static+"/"): h = StaticFileRequestHandler() h.requestline = self.requestline h.request_version = self.request_version h.client_address = self.client_address h.command = self.command h.path = self.path[len(_HttpRendererSingleton.static)+1:] h.wfile = self.wfile h.rfile = self.rfile h.do_GET() return if name == "": if not root: mime = "text/html" content = "wfrender" for renderer in renderers.keys(): content += ""+renderer+"
      " content += "" else: [ mime, content ] = root.render(data=data, context=context) else: if renderers is not None and renderers.has_key(name): [ mime, content ] = renderers[name].render(data=data, context=context) except Exception, e: _HttpRendererSingleton.logger.exception(e) self.send_error(500, "Internal Server Error") return if content: self.send_response(200) self.send_header('Content-type', mime) self.end_headers() self.wfile.write(content) else: self.send_error(404,"File Not Found: '%s'" % self.path) def log_message(self, format, *args): global _HttpRendererSingleton _HttpRendererSingleton.logger.info(str(self.client_address[0]) + " - "+ format % args) class StoppableHTTPServer(HTTPServer): def __init__(self, *args, **kwargs): HTTPServer.__init__(self, *args, **kwargs) self.__serving = False self.__is_shut_down = threading.Event() def serve_forever(self, poll_interval=0.1): """Handle one request at a time until shutdown. Polls for shutdown every poll_interval seconds. Ignores self.timeout. If you need to do periodic tasks, do them in another thread. """ self.__serving = True self.__is_shut_down.clear() while self.__serving: # XXX: Consider using another file descriptor or # connecting to the socket to wake this up instead of # polling. Polling reduces our responsiveness to a # shutdown request and wastes cpu at all other times. r, w, e = select.select([self], [], [], poll_interval) if r: self._handle_request_noblock() self.__is_shut_down.set() def shutdown(self): """Stops the serve_forever loop. Blocks until the loop has finished. This must be called while serve_forever() is running in another thread, or it will deadlock. """ self.__serving = False def _handle_request_noblock(self): """Handle one request, without blocking. I assume that select.select has returned that the socket is readable before this function was called, so there should be no risk of blocking in get_request(). """ try: request, client_address = self.get_request() except socket.error: return if self.verify_request(request, client_address): try: self.process_request(request, client_address) except: self.handle_error(request, client_address) self.close_request(request) class StaticFileRequestHandler(SimpleHTTPRequestHandler): def __init__(self): pass def translate_path(self, path): """Translate a /-separated PATH to the local filename syntax. Components that mean special things to the local file system (e.g. drive or directory names) are ignored. (XXX They should probably be diagnosed.) """ print "IN: "+path path = path.split('?',1)[0] path = path.split('#',1)[0] path = posixpath.normpath(urllib.unquote(path)) words = path.split('/') words = filter(None, words) path = _HttpRendererSingleton.docroot for word in words: drive, word = os.path.splitdrive(word) head, word = os.path.split(word) if word in (os.curdir, os.pardir): continue path = os.path.join(path, word) return path wfrog-0.8.2+svn953/wfrender/renderer/meteoclimatic.py000066400000000000000000000312321213472117100225230ustar00rootroot00000000000000# -*- coding: latin-1 -*- ## Copyright 2010 Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import math import logging import sys import time import wfcommon.database from wfcommon.formula.base import LastFormula from wfcommon.formula.base import SumFormula from wfcommon.formula.base import MinFormula from wfcommon.formula.base import MaxFormula try: from wfrender.datasource.accumulator import AccumulatorDatasource except ImportError, e: from datasource.accumulator import AccumulatorDatasource from wfcommon.units import MpsToKmh class MeteoclimaticRenderer(object): """ Renders the data chunk to send to the meteoclimatic website using local time. See www.meteoclimatic.com. Meteoclimatic has not disclosed its direct upload protocol. Therefore it is necessary to upload (FTP) or publish (HTTP) the data. render result [string]: The generated chunk. [ Properties ] id [string]: meteoclimatic station ID. storage: The storage service. """ id = None storage = None accuY = None accuM = None accuD = None lastTemplate = None logger = logging.getLogger("renderer.meteoclimatic") def render(self, data={}, context={}): try: assert self.id is not None, "'meteoclimatic.id' must be set" assert self.storage is not None, "'meteoclimatic.storage' must be set" if self.accuD == None: self.logger.info("Initializing accumulators") # Accumulator for yearly data self.accuY = AccumulatorDatasource() self.accuY.slice = 'year' self.accuY.span = 1 self.accuY.caching = True self.accuY.storage = self.storage self.accuY.formulas = {'data': { 'max_temp' : MaxFormula('temp'), 'min_temp' : MinFormula('temp'), 'max_hum' : MaxFormula('hum'), 'min_hum' : MinFormula('hum'), 'max_pressure' : MaxFormula('pressure'), 'min_pressure' : MinFormula('pressure'), 'max_gust' : MaxFormula('wind_gust'), 'rain_fall' : SumFormula('rain') } } # Accumulator for monthly data self.accuM = AccumulatorDatasource() self.accuM.slice = 'month' self.accuM.span = 1 self.accuM.storage = self.storage self.accuM.caching = True self.accuM.formulas = {'data': { 'max_temp' : MaxFormula('temp'), 'min_temp' : MinFormula('temp'), 'max_hum' : MaxFormula('hum'), 'min_hum' : MinFormula('hum'), 'max_pressure' : MaxFormula('pressure'), 'min_pressure' : MinFormula('pressure'), 'max_gust' : MaxFormula('wind_gust'), 'rain_fall' : SumFormula('rain') } } # Accumulator for daily and current data self.accuD = AccumulatorDatasource() self.accuD.slice = 'day' self.accuD.span = 1 self.accuD.storage = self.storage self.accuD.caching = True self.accuD.formulas = { 'data': { 'max_temp' : MaxFormula('temp'), 'min_temp' : MinFormula('temp'), 'max_hum' : MaxFormula('hum'), 'min_hum' : MinFormula('hum'), 'max_pressure' : MaxFormula('pressure'), 'min_pressure' : MinFormula('pressure'), 'max_gust' : MaxFormula('wind_gust'), 'rain_fall' : SumFormula('rain') }, 'current': { 'temp' : LastFormula('temp'), 'hum' : LastFormula('hum'), 'pressure' : LastFormula('pressure'), 'gust' : LastFormula('wind_gust'), 'wind_deg' : LastFormula('wind_dir'), 'time' : LastFormula('localtime') } } if 'solar_rad' in self.storage.keys(): self.accuD.formulas['current']['solar_rad'] = LastFormula('solar_rad') self.logger.info("Calculating ...") template = "*VER=DATA2*COD=%s*%s*%s*%s*%s*EOT*" % ( self.id, self._calculateCurrentData(self.accuD), self._calculateAggregData('D', self.accuD), self._calculateAggregData('M', self.accuM), self._calculateAggregData('Y', self.accuY)) self.lastTemplate = template self.logger.info("Template calculated: %s" % template) return ['text/plain', template] except Exception, e: self.logger.warning("Error rendering meteoclimatic data: %s" % str(e)) if self.lastTemplate == None: return ['text/plain', "*VER=DATA2*COD=%s*EOT*" % self.id] else: return ['text/plain', self.lastTemplate] def _calculateCurrentData(self, accu): data = accu.execute()['current']['series'] index = len(data['lbl'])-1 template = "UPD=%s*TMP=%s*WND=%s*AZI=%s*BAR=%s*HUM=%s*SUN=%s" % ( self._format(data['time'][index].strftime("%d/%m/%Y %H:%M")), self._format(data['temp'][index]), self._format(MpsToKmh(data['gust'][index])), self._format(data['wind_deg'][index]), self._format(data['pressure'][index]), self._format(data['hum'][index]), self._format(data['solar_rad'][index], '') if 'solar_rad' in self.storage.keys() else '' ) self.logger.debug("Calculating current data (index: %d): %s" % (index, template)) return template def _calculateAggregData(self, time_span, accu): data = accu.execute()['data']['series'] index = len(data['lbl'])-1 template = "%sHTM=%s*%sLTM=%s*%sHHM=%s*%sLHM=%s*%sHBR=%s*%sLBR=%s*%sGST=%s*%sPCP=%s" % ( time_span, self._format(data['max_temp'][index]), time_span, self._format(data['min_temp'][index]), time_span, self._format(data['max_hum'][index]), time_span, self._format(data['min_hum'][index]), time_span, self._format(data['max_pressure'][index]), time_span, self._format(data['min_pressure'][index]), time_span, self._format(MpsToKmh(data['max_gust'][index])), time_span, self._format(data['rain_fall'][index]) ) self.logger.debug("Calculating %s data (index: %d): %s" % (time_span, index, template)) return template def _format(self, value, default='-'): return value if value != None else default # Information on the data template can be found at # http://www.meteoclimatic.com/index/wp/plantilla_es.html # # Formato del fichero de datos # ############################ # El fichero de datos está compuesto por una serie de campos. Cada campo está compuesto por # una etiqueta identificativa y el valor correspondiente. La etiqueta se indicará en caracteres # en mayúscula, está precedida por un asterisco y finaliza con un signo de igual. Esta etiqueta # identifica el valor que lo sigue. # A grandes rasgos, este fichero se divide en: # # * Cabecera # * Datos actuales # * Datos diarios (máximas, mínimas y precipitación) # * Datos mensuales (máximas, mínimas y precipitación) # * Datos anuales (máximas, mínimas y precipitación) # * Final de fichero # # Importante: El objetivo de este fichero es que sea comprendido por un programa y no mostrado # a un usuario. Por ello, es muy importante que no se formatee la información para hacerla bonita # visualmente. Es necesario que sea un fichero de texto neto, sin marcas ni etiquetas de # hipertexto entre sus marcas de cabecera y final de fichero. # # Cabecera # ######## # Tiene como objetivo marcar el inicio del bloque de datos y la identificación de la estación. # Contiene los siguientes campos: # *VER=DATA2 # Marca de inicio del fichero. Versión del fichero de datos. Valor constante. # *COD=[código_estación] # Identificación del código de la estación # *UPD=[actualización] # Fecha y hora de los datos. La fecha se ha de indicar en formato normalizado de # día/mes/año. No se entiende el formato anglosajón de tipo mes/día/año. A continuación # de la fecha se debe indicar la hora, preferiblemente en formato de 24 horas. # Excepción: Si el usuario requiere específicamente que el formato de fecha sea el # anglosajón, se precederá esta fecha por los caracteres US_ # Ejemplo: *UPD=US_03/14/2007 14:25 # # Datos actuales # ############## # Contiene los datos del momento de envío del fichero y lo forman los siguientes campos: # *TMP=[temperatura] # Temperatura en grados Celsius # *WND=[velocidad_viento] # Velocidad del viento en km/h # *AZI=[dirección] # Dirección del viento. Se puede indicar en grados, donde los 0º o 360º son # el norte y 90º, 180º y 270º son el este, sur y oeste, respectivamente. También # se pueden indicar en el formato geográfico o literal (N, NNE, NE, ENE, E, ESE, # SE, SSE, S, SSW, SSO, SW, SO, WSW, OSO, W, O, WNW, ONO, NW, NO, NNW, NNO) # *BAR=[presión] # Presión en hPa o mb. # *HUM=[humedad] # Humedad relativa # *SUN=[radiación_solar] # Radiación solar W/m2. En blanco si no se proporciona. # Datos diarios # Contiene las máximas y mínimas diarias y el total de precipitación del día, según # los siguientes campos: # DHTM=[temperatura] # Temperatura máxima del día en grados Celsius # DLTM=[temperatura] # Temperatura mínima del día en grados Celsius # DHHM=[humedad] # Humedad relativa máxima del día # DLHM=[humedad] # Humedad relativa mínima del día # DHBR=[presión] # Presión máxima del día # DLBR=[presión] # Presión mínima del día # DGST=[velocidad_viento] # Racha de viento máxima del día # DPCP=[precipitación] # Precipitación total del día en mm # # Datos mensuales # ############### # Contiene las máximas y mínimas mensuales y el total de precipitación del mes, según # los siguientes campos: # MHTM=[temperatura] # Temperatura máxima del mes en grados Celsius # MLTM=[temperatura] # Temperatura mínima del mes en grados Celsius # MHHM=[humedad] # Humedad relativa máxima del mes # MLHM=[humedad] # Humedad relativa mínima del mes # MHBR=[presión] # Presión máxima del mes # MLBR=[presión] # Presión mínima del mes # MGST=[velocidad_viento] # Racha de viento máxima del mes # MPCP=[precipitación] # Precipitación total del mes en mm # # Datos anuales # ############# # Contiene las máximas y mínimas anuales y el total de precipitación del año, según # los siguientes campos: # YHTM=[temperatura] # Temperatura máxima del año en grados Celsius # YLTM=[temperatura] # Temperatura mínima del año en grados Celsius # YHHM=[humedad] # Humedad relativa máxima del año # YLHM=[humedad] # Humedad relativa mínima del año # YHBR=[presión] # Presión máxima del año # YLBR=[presión] # Presión mínima del año # YGST=[velocidad_viento] # Racha de viento máxima del año # YPCP=[precipitación] # Precipitación total del año en mm # # Final del fichero # ################# # El fichero de datos finaliza con una marca de fin de texto *EOT* # Debido a que muchos servicios gratuítos de alojamiento de pàginas web basan su # financiación en la inserción de publicidad, a menudo modifican las páginas que # envían los usuarios para hacer que esta aparezca. Para que se pueda discriminar # entre la publicidad y los datos, se marca el inicio y final del fichero de datos # con estas etiquetas. wfrog-0.8.2+svn953/wfrender/renderer/openweathermap.py000066400000000000000000000217311213472117100227260ustar00rootroot00000000000000## Copyright 2013 Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import math import logging import sys import time import httplib import urllib import base64 from wfcommon.formula.base import LastFormula from wfcommon.formula.base import SumFormula try: from wfrender.datasource.accumulator import AccumulatorDatasource except ImportError, e: from datasource.accumulator import AccumulatorDatasource class OpenWeatherMapPublisher(object): """ Render and publisher for www.openweathermap.org. Sends every record published to the storage to openweathermap API. [ Properties ] username [string]: Your openweathermap.org username. password [string]: Your openweathermap.org password. name[string]: Station name. latitude[numeric]: Station latitude (signed degrees format) longitude[numeric]: Station longitude (signed degrees format) altitude [numeric]: Station altitude in m. storage: The storage service. send_uv[boolean] (optional): Send UV data (by default false). send_radiation[boolean] (optional): Send radiation data (by default false). """ username = None password = None name = None longitude = None latitude = None altitude = None storage = None alive = False send_uv = False send_radiation = False logger = logging.getLogger("renderer.openweathermap") def render(self, data={}, context={}): try: assert self.username is not None, "'openweathermap.id' must be set" assert self.password is not None, "'openweathermap.password' must be set" assert self.name is not None, "'openweathermap.name' must be set" assert self.latitude is not None, "'openweathermap.latitude' must be set" assert self.longitude is not None, "'openweathermap.longitude' must be set" assert self.altitude is not None, "'openweathermap.altitude' must be set" self.logger.info("Initializing openweathermap.com (user %s)" % self.username) self.alive = True accu = AccumulatorDatasource() accu.slice = 'day' accu.span = 1 accu.storage = self.storage accu.formulas = {'current': { 'temp' : LastFormula('temp'), 'hum' : LastFormula('hum'), 'pressure' : LastFormula('pressure'), 'dew_point' : LastFormula('dew_point'), 'wind' : LastFormula('wind'), 'wind_gust' : LastFormula('wind_gust'), 'wind_deg' : LastFormula('wind_dir'), 'rain' : SumFormula('rain'), 'utctime' : LastFormula('utctime') } } if self.send_uv: accu.formulas['current']['uv'] = LastFormula('uv') if self.send_radiation: accu.formulas['current']['solar_rad'] = LastFormula('solar_rad') accu24h = AccumulatorDatasource() accu24h.slice = 'hour' accu24h.span = 24 accu24h.storage = self.storage accu24h.formulas = {'current': {'rain': SumFormula('rain')} } accu60min = AccumulatorDatasource() accu60min.slice = 'minute' accu60min.span = 60 accu60min.storage = self.storage accu60min.formulas = {'current': {'rain': SumFormula('rain')} } last_timestamp = None while self.alive: try: data = accu.execute()['current']['series'] index = len(data['lbl'])-1 rain_1h = sum(map(lambda x: x if x is not None else 0, accu60min.execute()['current']['series']['rain'][:60])) rain_24h = sum(map(lambda x: x if x is not None else 0, accu24h.execute()['current']['series']['rain'][:24])) if last_timestamp == None or last_timestamp < data['utctime'][index]: last_timestamp = data['utctime'][index] args = { 'wind_dir': int(round(data['wind_deg'][index])), # grad 'wind_speed': str(data['wind'][index]), # mps 'wind_gust': str(data['wind_gust'][index]), # mps 'temp': str(data['temp'][index]), # grad C #'dewpoint': str(data['dew_point'][index]), # NOT WORKING PROPERLY 'humidity': int(round(data['hum'][index])), # relative humidity % 'pressure': str(data['pressure'][index]), # mb 'rain_1h': rain_1h, # mm 'rain_24h': rain_24h, # mm 'rain_today': str(data['rain'][index]), # mm 'lat': self.latitude, 'long': self.longitude, 'alt': self.altitude, 'name': self.name } if self.send_uv: args['uv'] = str(data['uv'][index]) if self.send_radiation: args['lum'] = str(data['solar_rad'][index]) self.logger.debug("Publishing openweathermap data: %s " % urllib.urlencode(args)) response = self._publish(args, 'openweathermap.org', '/data/post') if response[0] == 200: self.logger.info('Data published successfully') self.logger.debug('Code: %s Status: %s Answer: %s' % response) else: self.logger.error('Error publishing data. Code: %s Status: %s Answer: %s' % response) except Exception, e: if (str(e) == "'NoneType' object has no attribute 'strftime'") or (str(e) == "a float is required"): self.logger.error('Could not publish: no valid values at this time. Retry next run...') else: self.logger.exception(e) time.sleep(60) # each minute we check for new records to send to openweathermap except Exception, e: self.logger.exception(e) raise def close(self): self.alive = False def _publish(self, args, server, uri): uri = uri + "?" + urllib.urlencode(args) self.logger.debug('Connect to: http://%s' % server) self.logger.debug('GET %s' % uri) auth = base64.encodestring("%s:%s" % (self.username, self.password)) conn = httplib.HTTPConnection(server) if not conn: raise Exception, 'Remote server connection timeout!' conn.request("GET", uri, headers = {"Authorization" : "Basic %s" % auth}) conn.sock.settimeout(30.0) # 30 seconds timeout http = conn.getresponse() data = (http.status, http.reason, http.read()) conn.close() if not (data[0] == 200 and data[1] == 'OK'): raise Exception, 'Server returned invalid status: %d %s %s' % data return data # http://openweathermap.org/wiki/API/data_upload # # Data upload API # # The protocol of weather station data transmission Version 1.0 # # This protocol is used for transmission of one measurement from a weather station. # The data is transmitted by HTTP POST request. The http basic authentication is used for authentication. # The server address is http://openweathermap.org/data/post # To connect your station, please register at http://OpenWeatherMap.org/login # The following parameters can be transmitted in POST: # # wind_dir - wind direction, grad # wind_speed - wind speed, mps # temp - temperature, grad C # humidity - relative humidity, % # pressure - atmosphere pressure # wind_gust - speed of wind gust, mps # rain_1h - rain in recent hour, mm # rain_24h - rain in recent 24 hours, mm # rain_today - rain today, mm # snow - snow in recent 24 hours, mm # lum - illumination, W/M2 # lat - latitude # long - longitude # alt - altitude, m # radiation - radiation # dewpoint - dewpoint # uv - UV index # name - station name wfrog-0.8.2+svn953/wfrender/renderer/pwsweather.py000066400000000000000000000146201213472117100220770ustar00rootroot00000000000000## Copyright 2010 Jordi Puigsegur ## derived from PyWeather by Patrick C. McGinty ## ## This file is part of wfrog ## ## wfrog 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 . import math import logging import sys import time import wfcommon.database from wfcommon.formula.base import LastFormula from wfcommon.formula.base import SumFormula try: from wfrender.datasource.accumulator import AccumulatorDatasource except ImportError, e: from datasource.accumulator import AccumulatorDatasource from wfcommon.units import HPaToInHg from wfcommon.units import CToF from wfcommon.units import MmToIn from wfcommon.units import MpsToMph class PwsWeatherPublisher(object): """ Render and publisher for pwsweather.com. It is a wrapper around PyWeather, thus needs this package installed on your system, version 0.9.1 or superior. (sudo easy_install weather) [ Properties ] id [string]: PWS station ID. password [string]: password. period [numeric]: The update period in seconds. storage: The storage service. """ id = None password = None publisher = None storage = None alive = False logger = logging.getLogger("renderer.pwsweather") def render(self, data={}, context={}): try: assert self.id is not None, "'pws.id' must be set" assert self.password is not None, "'pws.password' must be set" assert self.period is not None, "'pws.period' must be set" self.logger.info("Initializing PWS publisher (station %s)" % self.id) import weather.services self.publisher = weather.services.PwsWeather(self.id, self.password) self.alive = True accu = AccumulatorDatasource() accu.slice = 'day' accu.span = 1 accu.storage = self.storage accu.formulas = {'current': { 'temp' : LastFormula('temp'), 'dew_point': LastFormula('dew_point'), 'hum' : LastFormula('hum'), 'pressure' : LastFormula('pressure'), 'wind' : LastFormula('wind'), 'wind_deg' : LastFormula('wind_dir'), 'gust' : LastFormula('wind_gust'), 'gust_deg' : LastFormula('wind_gust_dir'), 'rain_rate' : LastFormula('rain_rate'), 'rain_fall' : SumFormula('rain'), 'utctime' : LastFormula('utctime') } } accu_month = AccumulatorDatasource() accu_month.slice = 'month' accu_month.span = 1 accu_month.storage = self.storage accu_month.formulas = {'current': {'rain_fall' : SumFormula('rain') } } accu_year = AccumulatorDatasource() accu_year.slice = 'year' accu_year.span = 1 accu_year.storage = self.storage accu_year.formulas = {'current': {'rain_fall' : SumFormula('rain') } } while self.alive: try: data = accu.execute()['current']['series'] index = len(data['lbl'])-1 data_month= accu_month.execute()['current']['series'] index_month = len(data_month['lbl'])-1 data_year = accu_year.execute()['current']['series'] index_year = len(data_year['lbl'])-1 params = { # pressure: in inches of Hg 'pressure' : HPaToInHg(data['pressure'][index]), # dewpoint: in Fahrenheit 'dewpoint' : CToF(data['dew_point'][index]), # humidity: between 0.0 and 100.0 inclusive 'humidity' : data['hum'][index], # tempf: in Fahrenheit 'tempf' : CToF(data['temp'][index]), # rainin: inches/hour of rain 'rainin' : MmToIn(data['rain_rate'][index]), # rainday: total rainfall in day (localtime) 'rainday' : MmToIn(data['rain_fall'][index]), # rainmonth: total rainfall for month (localtime) 'rainmonth' : MmToIn(data_month['rain_fall'][index_month]), # rainyear: total rainfall for year (localtime) 'rainyear' : MmToIn(data_year['rain_fall'][index_year]), # dateutc: date "YYYY-MM-DD HH:MM:SS" in GMT timezone 'dateutc' : data['utctime'][index].strftime('%Y-%m-%d %H:%M:%S'), # windgust: in mph 'windgust' : MpsToMph(data['gust'][index]), # windspeed: in mph 'windspeed' : MpsToMph(data['wind'][index]), # winddir: in degrees, between 0.0 and 360.0 'winddir' : data['wind_deg'][index] } # Do not send parameters that are null (None). # from above only dateutc is a mandatory parameter. params = dict(filter(lambda (p,v): v, [(p,v) for p,v in params.iteritems()])) self.logger.info("Publishing PWS data: %s " % str(params)) self.publisher.set(**params) response = self.publisher.publish() self.logger.info('Result PWS publisher: %s' % str(response)) except Exception, e: self.logger.exception(e) time.sleep(self.period) except Exception, e: self.logger.exception(e) raise def close(self): self.alive = False wfrog-0.8.2+svn953/wfrender/renderer/scheduler.py000066400000000000000000000036321213472117100216650ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import time import logging class SchedulerRenderer(object): """ Schedules a renderer to be called periodically. This renderer runs indefinitely until 'close()' is called. render result [none]: Nothing is returned by this renderer. [ Properties ] renderer [renderer]: The renderer to call periodically. period [numeric]: The period in seconds. delay [numeric] (optional): Delay before first execution. By default 60 seconds. """ renderers = None period = None delay = 60 alive = True logger = logging.getLogger("renderer.scheduler") def render(self, data={}, context={}): assert self.period is not None, "'scheduler.period' must be set" time.sleep(self.delay) self.logger.info("Started scheduler") while self.alive: self.logger.debug("Rendering.") try: self.renderer.render(data=data, context=context) except Exception, e: self.logger.exception(e) time.sleep(self.period) def close(self): self.alive = False wfrog-0.8.2+svn953/wfrender/renderer/staticfile.py000066400000000000000000000022311213472117100220300ustar00rootroot00000000000000## Copyright 2011 Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . class StaticFileRenderer(object): """ Passes an existing static file to the renderer render result [string]: The path to the generated file. [ Properties ] path [string]: The absolute or relative path to the file to create. """ path = None def render(self, data={}, context={}): assert self.path is not None, "'staticfile.path' must be set" return self.path wfrog-0.8.2+svn953/wfrender/renderer/sticker.py000066400000000000000000000210771213472117100213560ustar00rootroot00000000000000# -*- coding: latin-1 -*- ## Copyright 2012 Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import math import logging import sys import time import wfcommon.database from wfcommon.formula.base import LastFormula from wfcommon.formula.base import SumFormula from wfcommon.formula.base import MinFormula from wfcommon.formula.base import MaxFormula try: from wfrender.datasource.accumulator import AccumulatorDatasource except ImportError, e: from datasource.accumulator import AccumulatorDatasource from wfcommon.units import MpsToKmh class StickerRenderer(object): """ Renders a wfrog sticker, to be served via http or uploaded with ftp. (currently beta version and only using metric units) needs python-pil library render result [string]: The path to the generated file. [ Properties ] filename [optional]: Sticker temporal filename. By default "/tmp/sticker.png". station_name [optional]: The name of the station (to appear in sticker). By default "wfrog weather station". storage: The storage service. logo_file [optional]: Location and name of the logo file. By default "/etc/wfrog/wfrender/config/logo.png". """ id = None storage = None accuY = None accuM = None accuD = None filename = "/tmp/sticker.png" station_name = "wfrog weather station" logo_file = "/etc/wfrog/wfrender/config/logo.png" logger = logging.getLogger("renderer.sticker") def render(self, data={}, context={}): try: import Image import ImageDraw import ImageColor assert self.storage is not None, "'sticker.storage' must be set" # Initialize accumulators if self.accuD == None: self.logger.info("Initializing accumulators") # Accumulator for yearly data self.accuY = AccumulatorDatasource() self.accuY.slice = 'year' self.accuY.span = 1 self.accuY.caching = True self.accuY.storage = self.storage self.accuY.formulas = {'data': { 'max_temp' : MaxFormula('temp'), 'min_temp' : MinFormula('temp'), 'max_gust' : MaxFormula('wind_gust'), 'rain_fall' : SumFormula('rain') } } # Accumulator for monthly data self.accuM = AccumulatorDatasource() self.accuM.slice = 'month' self.accuM.span = 1 self.accuM.storage = self.storage self.accuM.caching = True self.accuM.formulas = {'data': { 'max_temp' : MaxFormula('temp'), 'min_temp' : MinFormula('temp'), 'max_gust' : MaxFormula('wind_gust'), 'rain_fall' : SumFormula('rain') } } # Accumulator for daily and current data self.accuD = AccumulatorDatasource() self.accuD.slice = 'day' self.accuD.span = 1 self.accuD.storage = self.storage self.accuD.caching = True self.accuD.formulas = { 'data': { 'max_temp' : MaxFormula('temp'), 'min_temp' : MinFormula('temp'), 'max_gust' : MaxFormula('wind_gust'), 'rain_fall' : SumFormula('rain') }, 'current': { 'temp' : LastFormula('temp'), 'hum' : LastFormula('hum'), 'pressure' : LastFormula('pressure'), 'gust' : LastFormula('wind_gust'), 'wind_deg' : LastFormula('wind_dir'), 'time' : LastFormula('localtime') } } # Calculate data self.logger.info("Calculating ...") current = self._calculateCurrentData(self.accuD) # Create Sticker green = ImageColor.getrgb("#007000") wheat = ImageColor.getrgb("#F5DEB3") dark_wheat = ImageColor.getrgb("#8D7641") black = ImageColor.getrgb("#000000") width = 260 height = 100 corner = 10 im = Image.new('RGBA', (width, height), wheat) draw = ImageDraw.Draw(im) # 1) Transparency mask=Image.new('L', im.size, color=0) mdraw=ImageDraw.Draw(mask) mdraw.rectangle((corner, 0, width-corner, height), fill=255) mdraw.rectangle((0, corner, width, height-corner), fill=255) mdraw.chord((0, 0, corner*2, corner*2), 0, 360, fill=255) mdraw.chord((0, height-corner*2-1, corner*2, height-1), 0, 360, fill=255) mdraw.chord((width-corner*2-1, 0, width-1, corner*2), 0, 360, fill=255) mdraw.chord((width-corner*2-1, height-corner*2-1, width-1, height-1), 0, 360, fill=255) im.putalpha(mask) # 2) Borders draw.arc((0, 0, corner*2, corner*2), 180, 270, fill=dark_wheat) draw.arc((0, height-corner*2-1, corner*2, height-1), 90, 180, fill=dark_wheat) draw.arc((width-corner*2-1, 0, width, corner*2), 270, 360, fill=dark_wheat) draw.arc((width-corner*2-1, height-corner*2-1, width-1, height-1), 0, 90, fill=dark_wheat) draw.line((corner, 0, width-corner-1,0), fill=dark_wheat) draw.line((corner, height-1, width-corner-1, height-1), fill=dark_wheat) draw.line((0, corner, 0, height-corner-1), fill=dark_wheat) draw.line((width-1, corner, width-1, height-corner-1), fill=dark_wheat) # 3) Logo logo = Image.open(self.logo_file) im.paste(logo, (4, 3), logo) # using the same image with transparencies as mask # 4) Current data draw.text((65,5), self.station_name, fill=green) draw.text((65,25), "%0.1fC %d%% %0.1fKm/h %dmb" % current[0], fill=black) draw.text((65,38), current[1], fill=dark_wheat) draw.text((6,60), " Today: %4.1fC-%4.1fC %4.1fKm/h %5.1fl." % self._calculateAggregData(self.accuD), fill=dark_wheat) draw.text((6,72), " Monthly: %4.1fC-%4.1fC %4.1fKm/h %5.1fl." % self._calculateAggregData(self.accuM), fill=dark_wheat) draw.text((6,84), " Yearly: %4.1fC-%4.1fC %4.1fKm/h %5.1fl." % self._calculateAggregData(self.accuY), fill=dark_wheat) # Save sticker im.save(self.filename) self.logger.info("Sticker generated") f = open(self.filename, "rb") d = f.read() f.close() return ['image/png', d] except Exception, e: self.logger.warning("Error rendering sticker: %s" % str(e)) return None def _calculateCurrentData(self, accu): data = accu.execute()['current']['series'] index = len(data['lbl'])-1 return ( ( self._format(data['temp'][index]), self._format(data['hum'][index]), self._format(MpsToKmh(data['gust'][index])), #'wind_deg': self._format(data['wind_deg'][index]) self._format(data['pressure'][index]) ), self._format(data['time'][index].strftime("%d/%m/%Y %H:%M")) ) def _calculateAggregData(self, accu): data = accu.execute()['data']['series'] index = len(data['lbl'])-1 return (self._format(data['min_temp'][index]), self._format(data['max_temp'][index]), self._format(MpsToKmh(data['max_gust'][index])), self._format(data['rain_fall'][index]) ) def _format(self, value, default='-'): return value if value != None else default wfrog-0.8.2+svn953/wfrender/renderer/template.py000066400000000000000000000053461213472117100215260ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . from Cheetah.Template import Template import logging from pprint import pformat import os.path def rnd(value, dec=0): if value == round(value): return int(round(value)) else: return round(value, dec) class TemplateRenderer(object): """ Executes a wrapped renderer and fills a Cheetah template with the resulting data. render result [list]: The result of rendering is a list '[ mime, document ]'. where document is a string containing the generated document. [ Properties ] renderer [renderer]: The underlying renderer providing the data to render. path [string]: Path to the template file. mime [string] (optional): The mime type of the generated document. Defaults to 'text/plain'. debug: [true|false] (optional): If true, dumps the rendered data to the log output. Default to false. """ path = None renderer = None debug = False mime = "text/plain" compiled_template = None logger = logging.getLogger("renderer.template") def render(self,data={}, context={}): content = {} if self.renderer: content = self.renderer.render(data=data, context=context) if self.debug: self.logger.debug(pformat(content)) if context.has_key('_yaml_config_file'): dir_name = os.path.dirname(context['_yaml_config_file'] ) abs_path=os.path.join(dir_name, self.path) else: abs_path = self.path self.logger.debug("Rendering with template "+abs_path) content["rnd"]=rnd # 1st time compile template if not self.compiled_template: self.logger.debug("Compiling template "+abs_path) self.compiled_template = Template.compile(file=file(abs_path, "r")) return [ self.mime, str(self.compiled_template(searchList=[content, context])) ] wfrog-0.8.2+svn953/wfrender/renderer/value.py000066400000000000000000000040131213472117100210150ustar00rootroot00000000000000## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . import wfcommon.units import logging class ValueRenderer(object): """ Returns the main value as a string in the right units according to the context. render result [numeric]: The selected value. [ Properties ] key [string]: Refers to the dictionary key of the data part to render, e.g. 'temp'. select [value|last] (optional): Chooses what to render: - value: the 'value' item under the data part. Default. - last: the last element of the chosen serie (see the 'serie' property). serie [string] (optional): The chosen serie name (e.g. 'avg') if 'select' is set to 'last'. """ key = None select = 'value' value = None serie = None logger = logging.getLogger('renderer.value') def render(self,data,context={}): if self.select == "last": return data[self.key]['series'][self.serie][len(data[self.key]['series'][self.serie])-1] elif self.select == "value": val_key = self.value if self.value else 'value' self.logger.debug("Getting value for '"+self.key+"."+val_key+"'") return wfcommon.units.Converter(context["units"]).convert(self.key, data[self.key][val_key]) wfrog-0.8.2+svn953/wfrender/renderer/webcolors.py000066400000000000000000000627141213472117100217140ustar00rootroot00000000000000# Copyright (c) 2008-2009, James Bennett # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of the author nor the names of other # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ Utility functions for working with the color names and color value formats defined by the HTML and CSS specifications for use in documents on the Web. What this module supports ------------------------- This module supports the following methods of specifying sRGB colors, and conversions between them: * Six-digit hexadecimal. * Three-digit hexadecimal. * Integer ``rgb()`` triplet. * Percentage ``rgb()`` triplet. * Varying selections of predefined color names. This module does not support ``hsl()`` triplets, nor does it support opacity/alpha-channel information via ``rgba()`` or ``hsla()``. If you need to convert between RGB-specified colors and HSL-specified colors, or colors specified via other means, consult `the colorsys module`_ in the Python standard library, which can perform conversions amongst several common color systems. .. _the colorsys module: http://docs.python.org/library/colorsys.html Normalization and conventions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ For colors specified via hexadecimal values, this module will accept input in the following formats: * A hash mark (#) followed by three hexadecimal digits, where digits A-F may be upper- or lower-case. * A hash mark (#) followed by six hexadecimal digits, where digits A-F may be upper- or lower-case. For output which consists of a color specified via hexadecimal values, and for functions which perform intermediate conversion to hexadecimal before returning a result in another format, this module always normalizes such values to the following format: * A hash mark (#) followed by six hexadecimal digits, with digits A-F forced to lower-case. The function :func:`normalize_hex` in this module can be used to perform this normalization manually if desired. For colors specified via predefined names, this module will accept input in the following formats: * An entirely lower-case name, such as ``aliceblue``. * A name using CamelCase, such as ``AliceBlue``. For output which consists of a color specified via a predefined name, and for functions which perform intermediate conversion to a predefined name before returning a result in another format, this module always normalizes such values to be entirely lower-case. For purposes of identifying the specification from which to draw the selection of defined color names, this module recognizes the following identifiers: ``html4`` The HTML 4 named colors. ``css2`` The CSS 2 named colors. ``css21`` The CSS 2.1 named colors. ``css3`` The CSS 3/SVG named colors. The CSS 1 specification is not represented here, as it merely "suggested" a set of color names, and declined to provide values for them. Mappings of color names ----------------------- For each set of defined color names -- HTML 4, CSS 2, CSS 2.1 and CSS 3 -- this module exports two mappings: one of normalized color names to normalized hexadecimal values, and one of normalized hexadecimal values to normalized color names. These eight mappings are as follows: ``html4_names_to_hex`` Mapping of normalized HTML 4 color names to normalized hexadecimal values. ``html4_hex_to_names`` Mapping of normalized hexadecimal values to normalized HTML 4 color names. ``css2_names_to_hex`` Mapping of normalized CSS 2 color names to normalized hexadecimal values. Because CSS 2 defines the same set of named colors as HTML 4, this is merely an alias for ``html4_names_to_hex``. ``css2_hex_to_names`` Mapping of normalized hexadecimal values to normalized CSS 2 color nams. For the reasons described above, this is merely an alias for ``html4_hex_to_names``. ``css21_names_to_hex`` Mapping of normalized CSS 2.1 color names to normalized hexadecimal values. This is identical to ``html4_names_to_hex``, except for one addition: ``orange``. ``css21_hex_to_names`` Mapping of normalized hexadecimal values to normalized CSS 2.1 color names. As above, this is identical to ``html4_hex_to_names`` except for the addition of ``orange``. ``css3_names_to_hex`` Mapping of normalized CSS3 color names to normalized hexadecimal values. ``css3_hex_to_names`` Mapping of normalized hexadecimal values to normalized CSS3 color names. """ import math import re def _reversedict(d): """ Internal helper for generating reverse mappings; given a dictionary, returns a new dictionary with keys and values swapped. """ return dict(zip(d.values(), d.keys())) HEX_COLOR_RE = re.compile(r'^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6})$') SUPPORTED_SPECIFICATIONS = ('html4', 'css2', 'css21', 'css3') # Mappings of color names to normalized hexadecimal color values. ################################################################# html4_names_to_hex = {'aqua': '#00ffff', 'black': '#000000', 'blue': '#0000ff', 'fuchsia': '#ff00ff', 'green': '#008000', 'grey': '#808080', 'lime': '#00ff00', 'maroon': '#800000', 'navy': '#000080', 'olive': '#808000', 'purple': '#800080', 'red': '#ff0000', 'silver': '#c0c0c0', 'teal': '#008080', 'white': '#ffffff', 'yellow': '#ffff00'} css2_names_to_hex = html4_names_to_hex css21_names_to_hex = dict(html4_names_to_hex, orange='#ffa500') css3_names_to_hex = {'aliceblue': '#f0f8ff', 'antiquewhite': '#faebd7', 'aqua': '#00ffff', 'aquamarine': '#7fffd4', 'azure': '#f0ffff', 'beige': '#f5f5dc', 'bisque': '#ffe4c4', 'black': '#000000', 'blanchedalmond': '#ffebcd', 'blue': '#0000ff', 'blueviolet': '#8a2be2', 'brown': '#a52a2a', 'burlywood': '#deb887', 'cadetblue': '#5f9ea0', 'chartreuse': '#7fff00', 'chocolate': '#d2691e', 'coral': '#ff7f50', 'cornflowerblue': '#6495ed', 'cornsilk': '#fff8dc', 'crimson': '#dc143c', 'cyan': '#00ffff', 'darkblue': '#00008b', 'darkcyan': '#008b8b', 'darkgoldenrod': '#b8860b', 'darkgray': '#a9a9a9', 'darkgrey': '#a9a9a9', 'darkgreen': '#006400', 'darkkhaki': '#bdb76b', 'darkmagenta': '#8b008b', 'darkolivegreen': '#556b2f', 'darkorange': '#ff8c00', 'darkorchid': '#9932cc', 'darkred': '#8b0000', 'darksalmon': '#e9967a', 'darkseagreen': '#8fbc8f', 'darkslateblue': '#483d8b', 'darkslategray': '#2f4f4f', 'darkslategrey': '#2f4f4f', 'darkturquoise': '#00ced1', 'darkviolet': '#9400d3', 'deeppink': '#ff1493', 'deepskyblue': '#00bfff', 'dimgray': '#696969', 'dimgrey': '#696969', 'dodgerblue': '#1e90ff', 'firebrick': '#b22222', 'floralwhite': '#fffaf0', 'forestgreen': '#228b22', 'fuchsia': '#ff00ff', 'gainsboro': '#dcdcdc', 'ghostwhite': '#f8f8ff', 'gold': '#ffd700', 'goldenrod': '#daa520', 'gray': '#808080', 'grey': '#808080', 'green': '#008000', 'greenyellow': '#adff2f', 'honeydew': '#f0fff0', 'hotpink': '#ff69b4', 'indianred': '#cd5c5c', 'indigo': '#4b0082', 'ivory': '#fffff0', 'khaki': '#f0e68c', 'lavender': '#e6e6fa', 'lavenderblush': '#fff0f5', 'lawngreen': '#7cfc00', 'lemonchiffon': '#fffacd', 'lightblue': '#add8e6', 'lightcoral': '#f08080', 'lightcyan': '#e0ffff', 'lightgoldenrodyellow': '#fafad2', 'lightgray': '#d3d3d3', 'lightgrey': '#d3d3d3', 'lightgreen': '#90ee90', 'lightpink': '#ffb6c1', 'lightsalmon': '#ffa07a', 'lightseagreen': '#20b2aa', 'lightskyblue': '#87cefa', 'lightslategray': '#778899', 'lightslategrey': '#778899', 'lightsteelblue': '#b0c4de', 'lightyellow': '#ffffe0', 'lime': '#00ff00', 'limegreen': '#32cd32', 'linen': '#faf0e6', 'magenta': '#ff00ff', 'maroon': '#800000', 'mediumaquamarine': '#66cdaa', 'mediumblue': '#0000cd', 'mediumorchid': '#ba55d3', 'mediumpurple': '#9370d8', 'mediumseagreen': '#3cb371', 'mediumslateblue': '#7b68ee', 'mediumspringgreen': '#00fa9a', 'mediumturquoise': '#48d1cc', 'mediumvioletred': '#c71585', 'midnightblue': '#191970', 'mintcream': '#f5fffa', 'mistyrose': '#ffe4e1', 'moccasin': '#ffe4b5', 'navajowhite': '#ffdead', 'navy': '#000080', 'oldlace': '#fdf5e6', 'olive': '#808000', 'olivedrab': '#6b8e23', 'orange': '#ffa500', 'orangered': '#ff4500', 'orchid': '#da70d6', 'palegoldenrod': '#eee8aa', 'palegreen': '#98fb98', 'paleturquoise': '#afeeee', 'palevioletred': '#d87093', 'papayawhip': '#ffefd5', 'peachpuff': '#ffdab9', 'peru': '#cd853f', 'pink': '#ffc0cb', 'plum': '#dda0dd', 'powderblue': '#b0e0e6', 'purple': '#800080', 'red': '#ff0000', 'rosybrown': '#bc8f8f', 'royalblue': '#4169e1', 'saddlebrown': '#8b4513', 'salmon': '#fa8072', 'sandybrown': '#f4a460', 'seagreen': '#2e8b57', 'seashell': '#fff5ee', 'sienna': '#a0522d', 'silver': '#c0c0c0', 'skyblue': '#87ceeb', 'slateblue': '#6a5acd', 'slategray': '#708090', 'slategrey': '#708090', 'snow': '#fffafa', 'springgreen': '#00ff7f', 'steelblue': '#4682b4', 'tan': '#d2b48c', 'teal': '#008080', 'thistle': '#d8bfd8', 'tomato': '#ff6347', 'turquoise': '#40e0d0', 'violet': '#ee82ee', 'wheat': '#f5deb3', 'white': '#ffffff', 'whitesmoke': '#f5f5f5', 'yellow': '#ffff00', 'yellowgreen': '#9acd32'} # Mappings of normalized hexadecimal color values to color names. ################################################################# html4_hex_to_names = _reversedict(html4_names_to_hex) css2_hex_to_names = html4_hex_to_names css21_hex_to_names = _reversedict(css21_names_to_hex) css3_hex_to_names = _reversedict(css3_names_to_hex) # Normalization routines. ################################################################# def normalize_hex(hex_value): """ Normalize a hexadecimal color value to the following form and return the result:: #[a-f0-9]{6} In other words, the following transformations are applied as needed: * If the value contains only three hexadecimal digits, it is expanded to six. * The value is normalized to lower-case. If the supplied value cannot be interpreted as a hexadecimal color value, ``ValueError`` is raised. Examples: >>> normalize_hex('#0099cc') '#0099cc' >>> normalize_hex('#0099CC') '#0099cc' >>> normalize_hex('#09c') '#0099cc' >>> normalize_hex('#09C') '#0099cc' >>> normalize_hex('0099cc') Traceback (most recent call last): ... ValueError: '0099cc' is not a valid hexadecimal color value. """ try: hex_digits = HEX_COLOR_RE.match(hex_value).groups()[0] except AttributeError: raise ValueError("'%s' is not a valid hexadecimal color value." % hex_value) if len(hex_digits) == 3: hex_digits = ''.join(map(lambda s: 2 * s, hex_digits)) return '#%s' % hex_digits.lower() # Conversions from color names to various formats. ################################################################# def name_to_hex(name, spec='css3'): """ Convert a color name to a normalized hexadecimal color value. The optional keyword argument ``spec`` determines which specification's list of color names will be used; valid values are ``html4``, ``css2``, ``css21`` and ``css3``, and the default is ``css3``. The color name will be normalized to lower-case before being looked up, and when no color of that name exists in the given specification, ``ValueError`` is raised. Examples: >>> name_to_hex('white') '#ffffff' >>> name_to_hex('navy') '#000080' >>> name_to_hex('goldenrod') '#daa520' >>> name_to_hex('goldenrod', spec='html4') Traceback (most recent call last): ... ValueError: 'goldenrod' is not defined as a named color in html4. """ if spec not in SUPPORTED_SPECIFICATIONS: raise TypeError("'%s' is not a supported specification for color name lookups; supported specifications are: %s." % (spec, ', '.join(SUPPORTED_SPECIFICATIONS))) normalized = name.lower() try: hex_value = globals()['%s_names_to_hex' % spec][normalized] except KeyError: raise ValueError("'%s' is not defined as a named color in %s." % (name, spec)) return hex_value def name_to_rgb(name, spec='css3'): """ Convert a color name to a 3-tuple of integers suitable for use in an ``rgb()`` triplet specifying that color. The optional keyword argument ``spec`` determines which specification's list of color names will be used; valid values are ``html4``, ``css2``, ``css21`` and ``css3``, and the default is ``css3``. The color name will be normalized to lower-case before being looked up, and when no color of that name exists in the given specification, ``ValueError`` is raised. Examples: >>> name_to_rgb('white') (255, 255, 255) >>> name_to_rgb('navy') (0, 0, 128) >>> name_to_rgb('goldenrod') (218, 165, 32) """ return hex_to_rgb(name_to_hex(name, spec=spec)) def name_to_rgb_percent(name, spec='css3'): """ Convert a color name to a 3-tuple of percentages suitable for use in an ``rgb()`` triplet specifying that color. The optional keyword argument ``spec`` determines which specification's list of color names will be used; valid values are ``html4``, ``css2``, ``css21`` and ``css3``, and the default is ``css3``. The color name will be normalized to lower-case before being looked up, and when no color of that name exists in the given specification, ``ValueError`` is raised. Examples: >>> name_to_rgb_percent('white') ('100%', '100%', '100%') >>> name_to_rgb_percent('navy') ('0%', '0%', '50%') >>> name_to_rgb_percent('goldenrod') ('85.49%', '64.71%', '12.5%') """ return rgb_to_rgb_percent(name_to_rgb(name, spec=spec)) # Conversions from hexadecimal color values to various formats. ################################################################# def hex_to_name(hex_value, spec='css3'): """ Convert a hexadecimal color value to its corresponding normalized color name, if any such name exists. The optional keyword argument ``spec`` determines which specification's list of color names will be used; valid values are ``html4``, ``css2``, ``css21`` and ``css3``, and the default is ``css3``. The hexadecimal value will be normalized before being looked up, and when no color name for the value is found in the given specification, ``ValueError`` is raised. Examples: >>> hex_to_name('#ffffff') 'white' >>> hex_to_name('#fff') 'white' >>> hex_to_name('#000080') 'navy' >>> hex_to_name('#daa520') 'goldenrod' >>> hex_to_name('#daa520', spec='html4') Traceback (most recent call last): ... ValueError: '#daa520' has no defined color name in html4. """ if spec not in SUPPORTED_SPECIFICATIONS: raise TypeError("'%s' is not a supported specification for color name lookups; supported specifications are: %s." % (spec, ', '.join(SUPPORTED_SPECIFICATIONS))) normalized = normalize_hex(hex_value) try: name = globals()['%s_hex_to_names' % spec][normalized] except KeyError: raise ValueError("'%s' has no defined color name in %s." % (hex_value, spec)) return name def hex_to_rgb(hex_value): """ Convert a hexadecimal color value to a 3-tuple of integers suitable for use in an ``rgb()`` triplet specifying that color. The hexadecimal value will be normalized before being converted. Examples: >>> hex_to_rgb('#fff') (255, 255, 255) >>> hex_to_rgb('#000080') (0, 0, 128) """ hex_digits = normalize_hex(hex_value) return tuple(map(lambda s: int(s, 16), (hex_digits[1:3], hex_digits[3:5], hex_digits[5:7]))) def hex_to_rgb_percent(hex_value): """ Convert a hexadecimal color value to a 3-tuple of percentages suitable for use in an ``rgb()`` triplet representing that color. The hexadecimal value will be normalized before converting. Examples: >>> hex_to_rgb_percent('#ffffff') ('100%', '100%', '100%') >>> hex_to_rgb_percent('#000080') ('0%', '0%', '50%') """ return rgb_to_rgb_percent(hex_to_rgb(hex_value)) # Conversions from integer rgb() triplets to various formats. ################################################################# def rgb_to_name(rgb_triplet, spec='css3'): """ Convert a 3-tuple of integers, suitable for use in an ``rgb()`` color triplet, to its corresponding normalized color name, if any such name exists. The optional keyword argument ``spec`` determines which specification's list of color names will be used; valid values are ``html4``, ``css2``, ``css21`` and ``css3``, and the default is ``css3``. If there is no matching name, ``ValueError`` is raised. Examples: >>> rgb_to_name((255, 255, 255)) 'white' >>> rgb_to_name((0, 0, 128)) 'navy' """ return hex_to_name(rgb_to_hex(rgb_triplet), spec=spec) def rgb_to_hex(rgb_triplet): """ Convert a 3-tuple of integers, suitable for use in an ``rgb()`` color triplet, to a normalized hexadecimal value for that color. Examples: >>> rgb_to_hex((255, 255, 255)) '#ffffff' >>> rgb_to_hex((0, 0, 128)) '#000080' """ return '#%02x%02x%02x' % rgb_triplet def rgb_to_rgb_percent(rgb_triplet): """ Convert a 3-tuple of integers, suitable for use in an ``rgb()`` color triplet, to a 3-tuple of percentages suitable for use in representing that color. This function makes some trade-offs in terms of the accuracy of the final representation; for some common integer values, special-case logic is used to ensure a precise result (e.g., integer 128 will always convert to '50%', integer 32 will always convert to '12.5%'), but for all other values a standard Python ``float`` is used and rounded to two decimal places, which may result in a loss of precision for some values. Examples: >>> rgb_to_rgb_percent((255, 255, 255)) ('100%', '100%', '100%') >>> rgb_to_rgb_percent((0, 0, 128)) ('0%', '0%', '50%') >>> rgb_to_rgb_percent((218, 165, 32)) ('85.49%', '64.71%', '12.5%') """ # In order to maintain precision for common values, # 256 / 2**n is special-cased for values of n # from 0 through 4, as well as 0 itself. specials = {255: '100%', 128: '50%', 64: '25%', 32: '12.5%', 16: '6.25%', 0: '0%'} return tuple(map(lambda d: specials.get(d, '%.02f%%' % ((d / 255.0) * 100)), rgb_triplet)) # Conversions from percentage rgb() triplets to various formats. ################################################################# def rgb_percent_to_name(rgb_percent_triplet, spec='css3'): """ Convert a 3-tuple of percentages, suitable for use in an ``rgb()`` color triplet, to its corresponding normalized color name, if any such name exists. The optional keyword argument ``spec`` determines which specification's list of color names will be used; valid values are ``html4``, ``css2``, ``css21`` and ``css3``, and the default is ``css3``. If there is no matching name, ``ValueError`` is raised. Examples: >>> rgb_percent_to_name(('100%', '100%', '100%')) 'white' >>> rgb_percent_to_name(('0%', '0%', '50%')) 'navy' >>> rgb_percent_to_name(('85.49%', '64.71%', '12.5%')) 'goldenrod' """ return rgb_to_name(rgb_percent_to_rgb(rgb_percent_triplet), spec=spec) def rgb_percent_to_hex(rgb_percent_triplet): """ Convert a 3-tuple of percentages, suitable for use in an ``rgb()`` color triplet, to a normalized hexadecimal color value for that color. Examples: >>> rgb_percent_to_hex(('100%', '100%', '0%')) '#ffff00' >>> rgb_percent_to_hex(('0%', '0%', '50%')) '#000080' >>> rgb_percent_to_hex(('85.49%', '64.71%', '12.5%')) '#daa520' """ return rgb_to_hex(rgb_percent_to_rgb(rgb_percent_triplet)) def _percent_to_integer(percent): """ Internal helper for converting a percentage value to an integer between 0 and 255 inclusive. """ num = float(percent.split('%')[0]) / 100.0 * 255 e = num - math.floor(num) return e < 0.5 and int(math.floor(num)) or int(math.ceil(num)) def rgb_percent_to_rgb(rgb_percent_triplet): """ Convert a 3-tuple of percentages, suitable for use in an ``rgb()`` color triplet, to a 3-tuple of integers suitable for use in representing that color. Some precision may be lost in this conversion. See the note regarding precision for ``rgb_to_rgb_percent()`` for details; generally speaking, the following is true for any 3-tuple ``t`` of integers in the range 0...255 inclusive:: t == rgb_percent_to_rgb(rgb_to_rgb_percent(t)) Examples: >>> rgb_percent_to_rgb(('100%', '100%', '100%')) (255, 255, 255) >>> rgb_percent_to_rgb(('0%', '0%', '50%')) (0, 0, 128) >>> rgb_percent_to_rgb(('85.49%', '64.71%', '12.5%')) (218, 165, 32) """ return tuple(map(_percent_to_integer, rgb_percent_triplet)) if __name__ == '__main__': import doctest doctest.testmod() wfrog-0.8.2+svn953/wfrender/renderer/wettercom.py000066400000000000000000000146171213472117100217250ustar00rootroot00000000000000## Copyright 2011 Robin Kluth ## ## This file is part of wfrog ## ## wfrog 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 . import math import logging import sys import time import hashlib from wfcommon.formula.base import LastFormula from httplib import HTTPConnection from urllib import urlencode try: from wfrender.datasource.accumulator import AccumulatorDatasource except ImportError, e: from datasource.accumulator import AccumulatorDatasource class WetterComPublisher(object): """ Render and publisher for wetter.com. [ Properties ] username [string]: Your wetter.com username. password [string]: password - wfrog ALWAYS send your password md5-decrypted to wetter.com! period [numeric]: The update period in seconds. storage: The storage service. test: true if test-publishing """ username = None password = None publisher = None storage = None alive = False test = False logger = logging.getLogger("renderer.wettercom") def render(self, data={}, context={}): try: assert self.username is not None, "'wettercom.id' must be set" assert self.password is not None, "'wettercom.password' must be set" assert self.period is not None, "'wettercom.period' must be set" self.logger.info("Initializing Wetter.com (user %s)" % self.username) self.alive = True accu = AccumulatorDatasource() accu.slice = 'hour' accu.span = 1 accu.storage = self.storage accu.formulas = {'current': { 'temp' : LastFormula('temp'), 'hum' : LastFormula('hum'), 'pressure' : LastFormula('pressure'), 'wind' : LastFormula('wind'), 'wind_deg' : LastFormula('wind_dir'), 'rain' : LastFormula('rain'), 'localtime' : LastFormula('localtime') } } while self.alive: try: data = accu.execute()['current']['series'] index = len(data['lbl'])-1 try: # try, if date is NoneType, if yes, we need to wait for a new value in wfrog.csv args = { 'benutzername': str(self.username), 'passwortmd5': hashlib.md5(str(self.password)).hexdigest(), 'datum': data['localtime'][index].strftime('%Y%m%d%H%M'), 'feuchtigkeit': int(round(data['hum'][index])), 'temperatur': str(data['temp'][index]).replace('.', ','), 'windrichtung': int(round(data['wind_deg'][index])), 'windstaerke': str(data['wind'][index]).replace('.', ','), 'luftdruck': str(data['pressure'][index]).replace('.', ','), 'niederschlagsmenge': str(data['rain'][index]).replace('.', ',') } if self.test: args['test'] = "true" self.logger.info('Running in test-mode!! The data wont be stored.') else: # If test is not true, we must send &test=false. If the GET string ends with rain-value, wetter.com returns an error args['test'] = "false" self.logger.debug("Publishing wettercom data: %s " % urlencode(args)) response = self._publish(args, 'www.wetterarchiv.de', '/interface/http/input.php') self.logger.debug('Server response: Code: %s Status: %s API-Answer: %s' % response) # Split response to determine if the request was ok or not. answer = response[2].split('=') allOk = False try: if (answer[1] == 'SUCCESS'): allOk=True except: if (answer[5] == 'SUCCESS'): allOk=True if allOk: self.logger.info('Data published successfully!') else: try: self.logger.error('Data publishing fails! Response: %s' % answer[5]) except: self.logger.error('Data publishing fails! Response: %s' % answer[2]) except Exception, e: if (str(e) == "'NoneType' object has no attribute 'strftime'") or (str(e) == "a float is required"): self.logger.error('Could not publish: no valid values at this time. Retry next run...') else: self.logger.error('Got unexpected error. Retry next run. Error: %s' % e) raise except Exception, e: self.logger.exception(e) time.sleep(self.period) except Exception, e: self.logger.exception(e) raise def close(self): self.alive = False def _publish(self, args, server, uri): uri = uri + "?" + urlencode(args) self.logger.debug('Connect to: http://%s' % server) self.logger.debug('GET %s' % uri) conn = HTTPConnection(server) if not conn: raise Exception, 'Remote server connection timeout!' conn.request("GET", uri) conn.sock.settimeout(5.0) http = conn.getresponse() data = (http.status, http.reason, http.read()) conn.close() if not (data[0] == 200 and data[1] == 'OK'): raise Exception, 'Server returned invalid status: %d %s %s' % data return datawfrog-0.8.2+svn953/wfrender/renderer/wunderground.py000066400000000000000000000146551213472117100224410ustar00rootroot00000000000000## Copyright 2010 Jordi Puigsegur ## derived from PyWeather by Patrick C. McGinty ## ## This file is part of wfrog ## ## wfrog 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 . import math import logging import sys import time import wfcommon.database from wfcommon.formula.base import LastFormula from wfcommon.formula.base import SumFormula try: from wfrender.datasource.accumulator import AccumulatorDatasource except ImportError, e: from datasource.accumulator import AccumulatorDatasource from wfcommon.units import HPaToInHg from wfcommon.units import CToF from wfcommon.units import MmToIn from wfcommon.units import MpsToMph class WeatherUndergroundPublisher(object): """ Render and publisher for Weather Underground. It is a wrapper around PyWeather, thus needs this package installed on your system, version 0.9.1 or superior. (sudo easy_install weather) [ Properties ] id [string]: Weather Underground station ID. password [string]: Weather Underground password. period [numeric]: The update period in seconds. storage: The storage service. real_time [boolean] (optional): If true then uses real time server. period must be < 30 secs. Default value is false. """ id = None password = None publisher = None real_time = False storage = None alive = False logger = logging.getLogger("renderer.wunderground") def render(self, data={}, context={}): try: assert self.id is not None, "'wunderground.id' must be set" assert self.password is not None, "'wunderground.password' must be set" assert self.period is not None, "'wunderground.period' must be set" self.real_time = self.real_time and self.period < 30 rtfreq = None if self.real_time: rtfreq = self.period self.logger.info("Initializing Wunderground publisher (station %s)" % self.id) import weather.services self.publisher = weather.services.Wunderground(self.id, self.password, rtfreq) self.alive = True if not self.real_time: accu = AccumulatorDatasource() accu.slice = 'day' accu.span = 1 accu.storage = self.storage accu.formulas = {'current': { 'temp' : LastFormula('temp'), 'dew_point': LastFormula('dew_point'), 'hum' : LastFormula('hum'), 'pressure' : LastFormula('pressure'), 'wind' : LastFormula('wind'), 'wind_deg' : LastFormula('wind_dir'), 'gust' : LastFormula('wind_gust'), 'gust_deg' : LastFormula('wind_gust_dir'), 'rain_rate' : LastFormula('rain_rate'), 'rain_fall' : SumFormula('rain'), 'utctime' : LastFormula('utctime') } } while self.alive: try: data = accu.execute()['current']['series'] index = len(data['lbl'])-1 params = { # pressure: in inches of Hg 'pressure' : HPaToInHg(data['pressure'][index]), # dewpoint: in Fahrenheit 'dewpoint' : CToF(data['dew_point'][index]), # humidity: between 0.0 and 100.0 inclusive 'humidity' : data['hum'][index], # tempf: in Fahrenheit 'tempf' : CToF(data['temp'][index]), # rainin: inches/hour of rain 'rainin' : MmToIn(data['rain_rate'][index]), # rainday: total rainfall in day (localtime) 'rainday' : MmToIn(data['rain_fall'][index]), # dateutc: date "YYYY-MM-DD HH:MM:SS" in GMT timezone 'dateutc' : data['utctime'][index].strftime('%Y-%m-%d %H:%M:%S'), # windspeed: in mph 'windspeed' : MpsToMph(data['wind'][index]), # winddir: in degrees, between 0.0 and 360.0 'winddir' : data['wind_deg'][index], # windgust: in mph 'windgust' : MpsToMph(data['gust'][index]), # windgustdir: in degrees, between 0.0 and 360.0 'windgustdir' : data['gust_deg'][index] } # Do not send parameters that are null (None). # from above only dateutc is a mandatory parameter. params = dict(filter(lambda (p,v): v, [(p,v) for p,v in params.iteritems()])) self.logger.info("Publishing Wunderground data (normal server): %s " % str(params)) self.publisher.set(**params) response = self.publisher.publish() self.logger.info('Result Wunderground publisher: %s' % str(response)) except Exception, e: self.logger.exception(e) time.sleep(self.period) else: self.logger.error("Wunderground real time server not yet supported") # while self.alive: # self.logger.debug("Publishing weather underground data (real time server).") # # time.sleep(self.period) except Exception, e: self.logger.exception(e) raise def close(self): self.alive = False wfrog-0.8.2+svn953/wfrender/templates/000077500000000000000000000000001213472117100175215ustar00rootroot00000000000000wfrog-0.8.2+svn953/wfrender/templates/default/000077500000000000000000000000001213472117100211455ustar00rootroot00000000000000wfrog-0.8.2+svn953/wfrender/templates/default/check.txt000066400000000000000000000037261213472117100227730ustar00rootroot00000000000000## To display use: wget -qO- http://localhost:8080 #set $days = $daily.keys() #silent $days.sort() #silent $days.reverse() wfrog $version (http://www.wfrog.org) #set $last_measure = $daily[$days[0]].measures_last Last measure stored: ${$last_measure if $last_measure != None else "None"} Number of measures collected (last ${len($days)} days) --------------------------------------------- Date Total Temp/hum Press. Rain Wind #for $day in $days #set $value = $daily[$day] $day#slurp ${"%7d" % $value.measures_count if $value.measures_count != None else " -"}#slurp ${"%7d" % $value.temp_count if $value.temp_count != None else " -"}#slurp ${"%7d" % $value.press_count if $value.press_count != None else " -"}#slurp ${"%7d" % $value.rain_count if $value.rain_count != None else " -"}#slurp ${"%7d" % $value.wind_count if $value.wind_count != None else " -"} #end for Meteorological data (last ${len($days)} days) -------------------------------------------------------------------------- Date Temp. Humidity Pressure Rain Wind max min max min max min fall rate max dir #for $day in $days #set $value = $daily[$day] $day#slurp ${"%6.1f" % $value.temp_max if $value.temp_max != None else " -"}#slurp ${"%6.1f" % $value.temp_min if $value.temp_min != None else " -"}#slurp ${"%6.1f%%" % $value.hum_max if $value.hum_max != None else " - "}#slurp ${"%6.1f%%" % $value.hum_min if $value.hum_min != None else " - "}#slurp ${"%6.0f" % $value.press_max if $value.press_max != None else " -"}#slurp ${"%6.0f" % $value.press_min if $value.press_min != None else " -"}#slurp ${"%7.1f" % $value.rain_fall if $value.rain_fall != None else " -"}#slurp ${"%7.1f" % $value.rain_rate if $value.rain_rate != None else " -"}#slurp ${"%7.1f" % $value.wind_max if $value.wind_max != None else " -"}#slurp ${"%5s" % $value.wind_dir if $value.wind_dir != None else " -"} #end for wfrog-0.8.2+svn953/wfrender/templates/default/main.html000066400000000000000000001052751213472117100227710ustar00rootroot00000000000000#from wfcommon.units import unit_roll #def unitstr(m,u) #if $getVar('http', False) $u #else $u #end if #end def Weather Dashboard - wfrog $version #if $getVar('http', False) #end if
      wfrog $version - Weather Dashboard
      $current.timestamp
      wfrog
      $rnd($current.temp1, 1)°$unitstr('temp',$units.temp)
      Humidity $rnd($current.hum1) %
      Pressure $rnd($current.press, 1) $unitstr('press',$units.press)
      ⌂  
      $rnd($current.temp0,1)°$unitstr('temp',$units.temp)
      $rnd($current.hum0) %

      Wind $rnd($current.wind.speed, 1) $unitstr('wind',$units.wind)
      Gust $rnd($current.wind.gust, 1) $unitstr('wind',$units.wind)
      Rain $rnd($current.rain, 1) $unitstr('rain',$units.rain)/h
       
      #if $getVar('http', False)
      3 Hours
      24 Hours
      7 Days
      30 Days
      365 Days
       
       
           
       
      #else #end if
      numbers
      charts
      #if $chart.has_key("tempint")
      Inside Temperature
      °$unitstr('temp',$units.temp)
      #end if #if $chart.has_key("humint")
      Inside Humidity
      %
      #end if
      Temperature and Dew Point
      °$unitstr('temp',$units.temp)
      Humidity
      %
      Pressure
      $unitstr('press',$units.press)
      #if $chart.has_key("temp2")
      Temperature (sensor 2)
      °$unitstr('temp',$units.temp)
      #end if #if $chart.has_key("hum2")
      Humidity (sensor 2)
      %
      #end if
      #if $chart.has_key("temp3")
      Temperature (sensor 3)
      °$unitstr('temp',$units.temp)
      #end if #if $chart.has_key("hum3")
      Humidity (sensor 3)
      %
      #end if
      #if $chart.has_key("temp4")
      Temperature (sensor 4)
      °$unitstr('temp',$units.temp)
      #end if #if $chart.has_key("hum4")
      Humidity (sensor 4)
      %
      #end if
      #if $chart.has_key("temp5")
      Temperature (sensor 5)
      °$unitstr('temp',$units.temp)
      #end if #if $chart.has_key("hum5")
      Humidity (sensor 5)
      %
      #end if
      #if $chart.has_key("temp6")
      Temperature (sensor 6)
      °$unitstr('temp',$units.temp)
      #end if #if $chart.has_key("hum6")
      Humidity (sensor 6)
      %
      #end if
      #if $chart.has_key("temp7")
      Temperature (sensor 7)
      °$unitstr('temp',$units.temp)
      #end if #if $chart.has_key("hum7")
      Humidity (sensor 7)
      %
      #end if
      #if $chart.has_key("temp8")
      Temperature (sensor 8)
      °$unitstr('temp',$units.temp)
      #end if #if $chart.has_key("hum8")
      Humidity (sensor 8)
      %
      #end if
      #if $chart.has_key("temp9")
      Temperature (sensor 9)
      °$unitstr('temp',$units.temp)
      #end if #if $chart.has_key("hum9")
      Humidity (sensor 9)
      %
      #end if
      Wind and Gusts
      $unitstr('wind',$units.wind)
      Wind Direction

      Rain
      $unitstr('rain',$units.rain)
      Rain rate
      $unitstr('rain',$units.rain)/h
      #if $chart.has_key("uv")
      UV Index

      #end if #if $chart.has_key("solar_rad")
      Solar radiation

      #end if

      wfrog-0.8.2+svn953/wfrender/test/000077500000000000000000000000001213472117100165025ustar00rootroot00000000000000wfrog-0.8.2+svn953/wfrender/test/__init__.py000066400000000000000000000001401213472117100206060ustar00rootroot00000000000000import test import yaml class TestElement(test.Test1, yaml.YAMLObject): yaml_tag = "!test" wfrog-0.8.2+svn953/wfrender/test/current-stats.html000066400000000000000000000016261213472117100222130ustar00rootroot00000000000000 Weather Dashboard
      #filter WebSafe Current temp: $days.temp_current °C #end filter

      Temperature and Dew Point
      Wind and Gusts
      Current Wind
      Wind Statistics
      wfrog-0.8.2+svn953/wfrender/test/current.html000066400000000000000000000010341213472117100210500ustar00rootroot00000000000000 wfrender Outside temperature: $temp1_current C
      Outside relative humidity: $hum1_current %

      Inside temperature: $temp0_current C
      Inside relative humidity: $hum0_current %

      Pressure: $pressure_current mb

      Rain rate: $rain_current_rate mm/h

      Wind

      $wind.wind_speed m/s wfrog-0.8.2+svn953/wfrender/test/data.xml000066400000000000000000000024721213472117100201420ustar00rootroot00000000000000 0 PartlyCloudy 968 True 2/1/2007 7:26 True True True 0.0 20.32 0.0 20.32 2007/1/1 12:0 1 Good 15.0 69 20.6 0 Steady 1 Good 14.0 61 22.0 0 Steady 2009.09.19.09.26.48 1.5 True 6 135 SE 3.0 wfrog-0.8.2+svn953/wfrender/test/datagenerator.py000066400000000000000000000046571213472117100217100ustar00rootroot00000000000000import kinterbasdb import time from random import random import logging kinterbasdb.init(type_conv=0) TIME_FORMAT = "%Y-%m-%d %H:%M:%S" def main(): db = FirebirdDB("localhost:/var/lib/firebird/2.0/data/test.fdb") now = time.time() for i in range (0, 10000): writeDB(db, time.localtime(now+(600*i))) print i def writeDB(db, t): sql = """ INSERT INTO METEO (TIMESTAMP_UTC, TIMESTAMP_LOCAL, TEMP, TEMP_MIN, TEMP_MIN_TIME, TEMP_MAX, TEMP_MAX_TIME, HUM, WIND, WIND_DIR, WIND_GUST, WIND_GUST_DIR, WIND_GUST_TIME, DEW_POINT, RAIN, RAIN_RATE, RAIN_RATE_TIME, PRESSURE, UV_INDEX) VALUES (%s, %s, %g, %g, %s, %g, %s, %g, %g, %s, %g, %s, %s, %g, %g, %g, %s, %g, %s) """ % ("'%s'" % time.strftime(TIME_FORMAT, t), "'%s'" % time.strftime(TIME_FORMAT, t), random()*20-5, random()*10-10, "'%s'" % time.strftime(TIME_FORMAT, t), random()*30+10, "'%s'" % time.strftime(TIME_FORMAT, t), random()*100, random()*10, int(random()*360), random()*20, int(random()*360), "'%s'" % time.strftime(TIME_FORMAT, t), random()*20, random()*1000, random()*5, "'%s'" % time.strftime(TIME_FORMAT, t), random()*100+950, random()*5) try: bdd = db bdd.connect() bdd.execute(sql) bdd.disconnect() logging.debug("SQL executed: %s", sql) except: logging.exception("Error writting current data to database") return False return True class FirebirdDB(): def __init__(self, bdd, user='sysdba', password='masterkey', charset='ISO8859_1'): self._bdd = bdd self._user = user self._password = password self._charset = charset def connect(self): self._db = kinterbasdb.connect(dsn=self._bdd, user=self._user, password=self._password, charset=self._charset) def select(self, sql): cursor = self._db.cursor() cursor.execute(sql) l = [] for e in cursor.fetchall(): l.append(e) cursor.close() self._db.commit() return l def execute(self, sql): cursor = self._db.cursor() cursor.execute(sql) cursor.close() self._db.commit() def disconnect(self): try: self._db.close() except: pass if __name__ == "__main__": main() wfrog-0.8.2+svn953/wfrender/test/hello.txt000066400000000000000000000000061213472117100203420ustar00rootroot00000000000000hello wfrog-0.8.2+svn953/wfrender/test/hello1.txt000066400000000000000000000000121213472117100204200ustar00rootroot00000000000000hello one wfrog-0.8.2+svn953/wfrender/test/hello2.txt000066400000000000000000000000121213472117100204210ustar00rootroot00000000000000hello two wfrog-0.8.2+svn953/wfrender/test/image-urls.html000066400000000000000000000006321213472117100214360ustar00rootroot00000000000000
      wfrog-0.8.2+svn953/wfrender/test/parallel-test.yaml000066400000000000000000000004351213472117100221410ustar00rootroot00000000000000renderer: !multi parallel: true children: one: !scheduler period: 1 renderer: !template path: hello1.txt two: !scheduler period: 3 renderer: !template path: hello2.txt wfrog-0.8.2+svn953/wfrender/test/symbols.txt000066400000000000000000000005221213472117100207320ustar00rootroot00000000000000⬓ ⚐ Wind ☂ Rain ⚖ Pressure ✵ Direction ⌂ Inside ⌀ Mean ⌚ Time ┅ Humidity ░ Humidity ☀ UV ☔ Rain ♨ temp ⌯ Humidity ⬓ ⚐ Wind ☂ Rain ⚖ Pressure ✵ Direction ⌂ Inside ⌀ Mean ⌚ Time ┅ Humidity ░ Humidity ☀ UV ☔ Rain ♨ temp ⌯ F wfrog-0.8.2+svn953/wfrender/test/test-accumulator.py000066400000000000000000000013361213472117100223530ustar00rootroot00000000000000import os,sys import logging logging.basicConfig(level=logging.DEBUG) if __name__ == "__main__": sys.path.append(os.path.abspath(sys.path[0] + '/../..')) import wfrender.datasource.accumulator import wfcommon.storage.csvfile import wfcommon.storage.simulator a = wfrender.datasource.accumulator.AccumulatorDatasource() a.slice = 'day' a.span = 4 #a.storage = wfcommon.storage.csvfile.CsvStorage() #a.storage.path = '../../wflogger/test/wfrog.csv' a.storage = wfcommon.storage.simulator.SimulatorStorage() a.storage.seed = 4 print repr(a.execute( data={ 'time_end': '2010-03-25Z12:00:00' })) print repr(a.execute( data={ 'time_end': '2010-03-26Z12:00:00' })) print repr(a.execute( data={ 'time_end': '2010-03-26Z12:00:00' })) wfrog-0.8.2+svn953/wfrender/test/test.py000066400000000000000000000053031213472117100200340ustar00rootroot00000000000000from pygooglechart import Chart from pygooglechart import _check_colour from pygooglechart import Axis from pygooglechart import RadarChart from pygooglechart import SimpleLineChart import random, yaml class Test1(object): """ This is a test extension """ pass def random_data(points=16, maximum=100): return [random.random() * maximum for a in xrange(points)] def _axis_set_style(self, colour, font_size=None, alignment=None, drawing_control=None, tick_colour=None): _check_colour(colour) self.colour = colour self.font_size = font_size self.alignment = alignment self.drawing_control = drawing_control self.tick_colour = tick_colour if tick_colour is not None: _check_colour(tick_colour) self.has_style = True def _axis_style_to_url(self): bits = [] bits.append(str(self.axis_index)) bits.append(self.colour) if self.font_size is not None: bits.append(str(self.font_size)) if self.alignment is not None: bits.append(str(self.alignment)) if self.drawing_control is not None: assert(self.drawing_control in Axis.DRAWING_CONTROL) bits.append(self.drawing_control) if self.tick_colour is not None: bits.append(self.tick_colour) return ','.join(bits) Axis.AXIS_LINES = 'l' Axis.TICK_MARKS = 't' Axis.BOTH = 'lt' Axis.DRAWING_CONTROL = (Axis.AXIS_LINES, Axis.TICK_MARKS, Axis.BOTH) def _chart_set_axis_style(self, axis_index, colour, font_size=None, \ alignment=None, drawing_control=None, tick_colour=None): try: self.axis[axis_index].set_style(colour, font_size, alignment, drawing_control, tick_colour) except IndexError: raise InvalidParametersException('Axis index %i has not been created' % axis) Axis.set_style = _axis_set_style Axis.style_to_url = _axis_style_to_url Chart.set_axis_style = _chart_set_axis_style def simple_random(): color ='000000' bg='FFFFFF' size=60 chart = RadarChart(120, 120, y_range=(0,100) ) chart.add_data([0] * 2) chart.add_data([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100]) chart.add_data([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,size*1.5]) chart.add_data([size,0,0,0,0,0,0,0,0,0,0,0,0,0,size,0]) chart.add_data([100] * 2) #chart.add_fill_range(color, 0, 2) chart.set_colours( [bg, 'BBBBBB', color, color, bg] ) chart.set_axis_labels(Axis.BOTTOM, ['N', '', 'NE', '', 'E', '', 'SE', '', 'S', '', 'SW', '', 'W', '', 'NW', '']) chart.set_axis_style(0, 'BBBBBB', 10, 0, 'l', bg); chart.set_line_style(1, 1) chart.set_line_style(2, 1+size/30) chart.set_line_style(3, 1+size/30) print chart.get_url() chart.download('test.png') #simple_random() wfrog-0.8.2+svn953/wfrender/test/test_meteoclimatic.yaml000066400000000000000000000003021213472117100232370ustar00rootroot00000000000000context: database: { url: 'localhost:/var/lib/firebird/2.0/data/wfrog.db' } renderer: !http cookies: [ ] root: !meteoclimatic id: XXXXXXXXXXXXXXXXXXX wfrog-0.8.2+svn953/wfrender/test/wfrender2.yaml000066400000000000000000000063651213472117100212760ustar00rootroot00000000000000renderer: !http root: !template path: templates/current-stats.html mime: text/html renderer: !multi renderers: days: !data source: !simulator {} renderer: !multi renderers: temp_chart: !chart series: temp.max: color: peachpuff area: { to: temp.min } max: { color: darkred, text: darkred, thickness: 0.5 } temp.avg: last: { thickness: 5, style: 'o' } order: 1 temp.min: color: None min: { color: blue, text: blue, thickness: 0.5 } dew.avg: color: darkcyan dash: 5 labels: temp.lbl zero: { color: gray, thickness: 0.5 } temp_current: !value key: temp serie: avg select: last hours: !data source: !simulator {} renderer: !multi renderers: wind_chart: !chart series: wind.avg: marks: {serie: wind.dir, color: gray, size: 12 } wind.max: order: -1 color: EEAAAA area: { color: peachpuff, to: wind.avg } max: { color: darkred, text: darkred, thickness: 0.5 } labels: wind.lbl y_margin: [ 0, 1 ] wind_now: !windradar width: 125 height: 125 arrow: { show: yes } max: { color: EEAAAA, thickness: 4 } trace: { color: lightgray, size: 10, ratio: 3, length: 3 } beaufort: { color: lightgray, intensity: 0.3 } wind_stat: !windradar width: 200 height: 200 sectors: { color: lightgray } lines: { gust: EEAAAA, thickness: 1 } areas: { gust: peachpuff } wfrog-0.8.2+svn953/wfrender/test/wfrender3.yaml000066400000000000000000000004441213472117100212670ustar00rootroot00000000000000context: {} renderer: !multi parallel: true children: one: !scheduler period: 2 renderer: !template path: test/hello.txt two: !scheduler period: 3 renderer: !template path: test/hello.txt wfrog-0.8.2+svn953/wfrender/test/wfrender4.yaml000066400000000000000000000075441213472117100213000ustar00rootroot00000000000000context: database: { url: 'localhost:/var/lib/firebird/2.0/data/wfrog.db' } chart: bgcolor: 70BDF5 color: indigo text: indigo renderer: !http root: !template path: templates/current-stats.html mime: text/html renderer: !multi renderers: days: !data source: !database slice: hour span: 365 renderer: !multi renderers: temp_chart: !chart series: temp.max: color: '9BCDF1' area: { to: temp.min } max: { color: darkred, text: darkred, thickness: 0.5 } temp.avg: last: { thickness: 5, style: 'o' } order: 1 interpolate: true temp.min: color: '9BCDF1' min: { color: blue, text: blue, thickness: 0.5 } dew.avg: color: darkcyan thickness: 1 dash: 5 labels: temp.lbl zero: { color: gray, thickness: 0.5 } temp_current: !value key: temp serie: avg select: last hours: !data source: !database slice: hour span: 60 renderer: !multi renderers: wind_chart: !chart series: wind.avg: #marks: {serie: wind.dir, size: 10 } interpolate: true wind.max: interpolate: true order: -1 color: '9BCDF1' area: { to: wind.avg } max: { color: darkred, text: darkred, thickness: 0.5 } labels: wind.lbl y_margin: [ 0, 1 ] wind_now: !windradar width: 125 height: 125 # arrow: { show: true } # max: { color: EEAAAA, thickness: 4 } # trace: { color: lightgray, size: 10, ratio: 3, length: 3 } # beaufort: { color: lightgray, intensity: 0.3 } wind_stat: !windradar width: 200 height: 200 sectors: { color: lightgray } lines: { gust: EEAAAA, thickness: 1 } areas: { gust: peachpuff } wfrog-0.8.2+svn953/wfrender/test/wfrender5.yaml000066400000000000000000000001241213472117100212640ustar00rootroot00000000000000 renderer: !include path: wfrender6.yaml variables: filename: hello wfrog-0.8.2+svn953/wfrender/test/wfrender6.yaml000066400000000000000000000000631213472117100212670ustar00rootroot00000000000000renderer: !template path: test/${filename}.txt wfrog-0.8.2+svn953/wfrender/test/wfrender7.yaml000066400000000000000000000015611213472117100212740ustar00rootroot00000000000000context: database: { url: 'file://database.db' } renderer: !http root: !template path: templates/current.html mime: text/html renderer: !data source: !wxdataxml path: test/data.xml renderer: !multi renderers: temp0_current: !value { key: temp0 } hum0_current: !value { key: hum0 } temp1_current: !value { key: temp1 } hum1_current: !value { key: hum1 } pressure_current: !value { key: pressure } rain_current_rate: !value { key: rain } wind: !multi renderers: wind_speed: !value { key: wind } wind_radar: !windradar arrow: { show: yes } wfrog-0.8.2+svn953/wfrender/test/xmlstream.py000066400000000000000000000070711213472117100210750ustar00rootroot00000000000000import sys import optparse from StringIO import StringIO from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer from Queue import Queue from threading import Thread # The event queue decoupling the logger thread and the listener receiving events # from the driver event_queue = Queue(500) def main(): # Configure the example opt_parser = optparse.OptionParser() opt_parser.add_option("-s", "--source", dest="source", default="stdin", help="Where to read the events from. \ Can be 'local' (simulates an embedded driver), \ 'http' (listening to XML events submitted by HTTP POST) or \ 'stdin' (reading events on the standard output, separated by an empty line). Defaults to 'stdin'") opt_parser.add_option("-p", "--port", dest="port", default="8080", help="If source is HTTP, listen on this port for HTTP POST events. Defaults to 8080") (options, args) = opt_parser.parse_args() # Start the logger thread logger_thread = Thread(target=logger_loop) logger_thread.setDaemon(True) logger_thread.start() # Start the chosen listener if options.source == 'http': listen_http(int(options.port)) if options.source == 'stdin': listen_stdin() if options.source == 'local': listen_local() # Wait logger_thread.join() # The logger thread body, picking events from the queue and "logging" them to the console def logger_loop(): while True: event = event_queue.get(block=True) if(event == "stop"): break print event.__dict__ ############################################## # Example listening on stdin, e.g. for pipe communication def listen_stdin(): end = False buffer = StringIO() while not end: line = sys.stdin.readline() if line.strip() == "": message = buffer.getvalue().strip() if not message == "": # skip additional emtpy lines process_message(buffer.getvalue()) buffer.close() buffer = StringIO() else: buffer.write(line) end = (line == "") enqueue_event("stop") # special event to stop the logger ############################################## # Example listening on HTTP, good for remote communication class HTTPEventHandler(BaseHTTPRequestHandler): protocol_version = 'HTTP/1.1' def do_POST(self): clen = self.headers.getheader('content-length') if clen: clen = int(clen) else: self.send_error(411) message = self.rfile.read(clen) process_message(message) self.send_response(200) self.send_header('Content-length', 0) self.end_headers() def listen_http(port): HTTPServer(('', port), HTTPEventHandler).serve_forever() ############################################## # Example simulating the case where the driver runs in the same python process class empty_event(object): pass def listen_local(): event = empty_event() # create "native" events event.type = "temp" event.sensor = 2 event.value = 24 enqueue_event(event) event = empty_event() event.type = "temp" event.sensor = 3 event.value = 25 enqueue_event(event) enqueue_event("stop") ############################################## # Transform XML messages to events def process_message(message): from lxml import objectify event = objectify.XML(message) event.type = event.tag enqueue_event(event) # Put an event on the queue def enqueue_event(event): event_queue.put(event, block=True, timeout=60) main() wfrog-0.8.2+svn953/wfrender/wfrender.py000077500000000000000000000113201213472117100177110ustar00rootroot00000000000000#!/usr/bin/python ## Copyright 2009 Laurent Bovet ## Jordi Puigsegur ## ## This file is part of wfrog ## ## wfrog 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 . # Before loading other modules add wfrog directory to sys.path to be able to use wfcommon import os.path import sys if __name__ == "__main__": sys.path.append(os.path.abspath(sys.path[0] + '/..')) import config import copy import optparse import logging import logging.handlers import wfcommon.units from wfcommon.config import wfrog_version class RenderEngine(object): ''' Root Elements ------------- context [dict] (optional): Contains context values propagated to all renderers during traversal. renderer [renderer]: Root renderer executed at wfrender execution. logging [logging configuration] (optional): See below the Logging Configuration section. ''' logger = logging.getLogger('wfrender') root_renderer = None configurer = None initial_context = { "version": wfrog_version, "units" : wfcommon.units.reference } initial_data = {} daemon = False output = False config_file = None opt_parser = None embedded = False def __init__(self, opt_parser=optparse.OptionParser()): """Creates the engine using a specific configurer or a yaml configurer if none specified""" self.configurer = config.RendererConfigurer(opt_parser) opt_parser.add_option("-D", "--data", dest="data_string", help="Passes specific data value/pairs to renderers", metavar="key1=value1,key2=value2") opt_parser.add_option("-O", dest="output", action="store_true", help="Outputs the renderer result (if any) on standard output") self.opt_parser = opt_parser def configure(self, embedded): (options, args) = self.opt_parser.parse_args() self.configurer.embedded = embedded if options.data_string: pairs = options.data_string.split(',') for pair in pairs: pieces = pair.split('=') assert len(pieces) == 2, "Key-value pair not in the form key=value: %s" % pair self.initial_data[pieces[0].strip()] = pieces[1].strip() if options.output: self.output=True self.reconfigure(options, args, embedded) def reconfigure(self, options=None, args=[], embedded=False): self.configurer.configure_engine(self,options, args, embedded, self.config_file, self.settings_file) def process(self, config_file, settings_file=None, embedded=False, data=initial_data, context={}): self.config_file = config_file self.settings_file = settings_file self.configure(embedded) try: if self.daemon: self.logger.info("Running as daemon") while self.daemon: self.logger.debug("Starting root rendering.") current_context = copy.deepcopy(self.initial_context) current_context.update(context) self.root_renderer.render(data=data, context=current_context) else: self.logger.debug("Starting root rendering.") current_context = copy.deepcopy(self.initial_context) current_context.update(context) return self.root_renderer.render(data=data, context=current_context) except KeyboardInterrupt: self.logger.info("Stopping daemon...") return except AssertionError, e: if logging.root.level > logging.DEBUG: self.logger.exception(e) return else: raise except Exception, e: self.logger.exception(e) raise finally: self.daemon = False def run(self, config_file='config/wfrender.yaml', settings_file=None, embedded=False): self.process(config_file, settings_file, embedded) if __name__ == "__main__": engine = RenderEngine() result = engine.process("config/wfrender.yaml", embedded=False) if engine.output: print str(result) engine.logger.debug("Finished main()")