pax_global_header 0000666 0000000 0000000 00000000064 12134721171 0014511 g ustar 00root root 0000000 0000000 52 comment=a08faa3f7b7347208cfec142e8aa5f29236c94c3
wfrog-0.8.2+svn953/ 0000775 0000000 0000000 00000000000 12134721171 0013707 5 ustar 00root root 0000000 0000000 wfrog-0.8.2+svn953/LICENSE.txt 0000664 0000000 0000000 00000104513 12134721171 0015536 0 ustar 00root root 0000000 0000000 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/ 0000775 0000000 0000000 00000000000 12134721171 0014457 5 ustar 00root root 0000000 0000000 wfrog-0.8.2+svn953/bin/wfrog 0000775 0000000 0000000 00000012560 12134721171 0015535 0 ustar 00root root 0000000 0000000 #!/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/ 0000775 0000000 0000000 00000000000 12134721171 0015453 5 ustar 00root root 0000000 0000000 wfrog-0.8.2+svn953/database/conversion_script-firebird-0.1-0.2.sql 0000664 0000000 0000000 00000005524 12134721171 0024330 0 ustar 00root root 0000000 0000000 -- 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/csv2sql 0000775 0000000 0000000 00000005111 12134721171 0016774 0 ustar 00root root 0000000 0000000 #!/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.sql 0000664 0000000 0000000 00000002647 12134721171 0020652 0 ustar 00root root 0000000 0000000 -- 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.sql 0000664 0000000 0000000 00000005302 12134721171 0020642 0 ustar 00root root 0000000 0000000 -- 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.sql 0000664 0000000 0000000 00000007103 12134721171 0020652 0 ustar 00root root 0000000 0000000 -- 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.sql 0000664 0000000 0000000 00000000636 12134721171 0020227 0 ustar 00root root 0000000 0000000 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,
PRIMARY KEY (TIMESTAMP_UTC),
KEY METEO_IDX (TIMESTAMP_LOCAL));
wfrog-0.8.2+svn953/database/db-mysql-0.9.sql 0000664 0000000 0000000 00000004005 12134721171 0020227 0 ustar 00root root 0000000 0000000 -- 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.sql 0000664 0000000 0000000 00000003414 12134721171 0017562 0 ustar 00root root 0000000 0000000 -- 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/ 0000775 0000000 0000000 00000000000 12134721171 0015131 5 ustar 00root root 0000000 0000000 wfrog-0.8.2+svn953/debian/changelog 0000664 0000000 0000000 00000002535 12134721171 0017010 0 ustar 00root root 0000000 0000000 wfrog (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/compat 0000664 0000000 0000000 00000000002 12134721171 0016327 0 ustar 00root root 0000000 0000000 7
wfrog-0.8.2+svn953/debian/control 0000664 0000000 0000000 00000002415 12134721171 0016536 0 ustar 00root root 0000000 0000000 Source: 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/copyright 0000664 0000000 0000000 00000007155 12134721171 0017074 0 ustar 00root root 0000000 0000000 This 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/docs 0000664 0000000 0000000 00000000014 12134721171 0015777 0 ustar 00root root 0000000 0000000 LICENSE.txt
wfrog-0.8.2+svn953/debian/rules 0000775 0000000 0000000 00000002227 12134721171 0016214 0 ustar 00root root 0000000 0000000 #!/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/ 0000775 0000000 0000000 00000000000 12134721171 0015074 5 ustar 00root root 0000000 0000000 wfrog-0.8.2+svn953/init.d/wflogger 0000775 0000000 0000000 00000006264 12134721171 0016646 0 ustar 00root root 0000000 0000000 #! /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/wfrender 0000775 0000000 0000000 00000006343 12134721171 0016644 0 ustar 00root root 0000000 0000000 #! /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/ 0000775 0000000 0000000 00000000000 12134721171 0015534 5 ustar 00root root 0000000 0000000 wfrog-0.8.2+svn953/wfcommon/__init__.py 0000664 0000000 0000000 00000001452 12134721171 0017647 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000015656 12134721171 0017370 0 ustar 00root root 0000000 0000000 ## 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/ 0000775 0000000 0000000 00000000000 12134721171 0017001 5 ustar 00root root 0000000 0000000 wfrog-0.8.2+svn953/wfcommon/config/default-settings.yaml 0000664 0000000 0000000 00000000144 12134721171 0023146 0 ustar 00root root 0000000 0000000 station:
driver: auto
altitude: 0
units:
temp: C
wind: km/h
rain: mm
press: hPa
wfrog-0.8.2+svn953/wfcommon/config/fileloghandler.yaml 0000664 0000000 0000000 00000000232 12134721171 0022641 0 ustar 00root root 0000000 0000000 handler: !!python/object/new:logging.handlers.RotatingFileHandler
kwds:
filename: ${filename}
maxBytes: 262144
backupCount: 3
wfrog-0.8.2+svn953/wfcommon/config/loghandler.yaml 0000664 0000000 0000000 00000000516 12134721171 0022006 0 ustar 00root root 0000000 0000000 handler: !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.yaml 0000664 0000000 0000000 00000000524 12134721171 0022650 0 ustar 00root root 0000000 0000000 handler: !!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.yaml 0000664 0000000 0000000 00000001350 12134721171 0023652 0 ustar 00root root 0000000 0000000 - 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.yaml 0000664 0000000 0000000 00000001637 12134721171 0021340 0 ustar 00root root 0000000 0000000 storage: !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.yaml 0000664 0000000 0000000 00000000555 12134721171 0022231 0 ustar 00root root 0000000 0000000 from: !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.py 0000664 0000000 0000000 00000003432 12134721171 0020132 0 ustar 00root root 0000000 0000000 #!/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.py 0000664 0000000 0000000 00000020461 12134721171 0017655 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000002451 12134721171 0017033 0 ustar 00root root 0000000 0000000 ## 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/ 0000775 0000000 0000000 00000000000 12134721171 0017201 5 ustar 00root root 0000000 0000000 wfrog-0.8.2+svn953/wfcommon/formula/__init__.py 0000664 0000000 0000000 00000004104 12134721171 0021311 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000006302 12134721171 0020466 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000005712 12134721171 0020525 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000010265 12134721171 0020520 0 ustar 00root root 0000000 0000000 ## 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/ 0000775 0000000 0000000 00000000000 12134721171 0016773 5 ustar 00root root 0000000 0000000 wfrog-0.8.2+svn953/wfcommon/gendoc/gendoc.sh 0000775 0000000 0000000 00000002451 12134721171 0020573 0 ustar 00root root 0000000 0000000 #!/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.py 0000664 0000000 0000000 00000012747 12134721171 0021521 0 ustar 00root root 0000000 0000000 import 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/ 0000775 0000000 0000000 00000000000 12134721171 0017150 5 ustar 00root root 0000000 0000000 wfrog-0.8.2+svn953/wfcommon/generic/__init__.py 0000664 0000000 0000000 00000002554 12134721171 0021267 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000004717 12134721171 0021156 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000005524 12134721171 0020662 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000004231 12134721171 0021162 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000004233 12134721171 0021540 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000004016 12134721171 0020501 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000002365 12134721171 0021210 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000010312 12134721171 0016664 0 ustar 00root root 0000000 0000000 #!/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.py 0000664 0000000 0000000 00000007336 12134721171 0017543 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000050503 12134721171 0017222 0 ustar 00root root 0000000 0000000 ## 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/ 0000775 0000000 0000000 00000000000 12134721171 0017200 5 ustar 00root root 0000000 0000000 wfrog-0.8.2+svn953/wfcommon/storage/__init__.py 0000664 0000000 0000000 00000002560 12134721171 0021314 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000007246 12134721171 0020475 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000013157 12134721171 0021214 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000005405 12134721171 0021344 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000005214 12134721171 0020721 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000006625 12134721171 0021602 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000004301 12134721171 0021134 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000005516 12134721171 0020454 0 ustar 00root root 0000000 0000000 #!/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/ 0000775 0000000 0000000 00000000000 12134721171 0016513 5 ustar 00root root 0000000 0000000 wfrog-0.8.2+svn953/wfcommon/test/csv.yaml 0000664 0000000 0000000 00000000072 12134721171 0020171 0 ustar 00root root 0000000 0000000 storage: !csv
path: wfrog.csv
#storage: !firebird {}
wfrog-0.8.2+svn953/wfcommon/units.py 0000664 0000000 0000000 00000010771 12134721171 0017256 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000004611 12134721171 0017250 0 ustar 00root root 0000000 0000000 ## 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/ 0000775 0000000 0000000 00000000000 12134721171 0015537 5 ustar 00root root 0000000 0000000 wfrog-0.8.2+svn953/wfdriver/__init__.py 0000664 0000000 0000000 00000000000 12134721171 0017636 0 ustar 00root root 0000000 0000000 wfrog-0.8.2+svn953/wfdriver/config/ 0000775 0000000 0000000 00000000000 12134721171 0017004 5 ustar 00root root 0000000 0000000 wfrog-0.8.2+svn953/wfdriver/config/embedded.yaml 0000664 0000000 0000000 00000000241 12134721171 0021416 0 ustar 00root root 0000000 0000000 station: !${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.yaml 0000664 0000000 0000000 00000001654 12134721171 0021526 0 ustar 00root root 0000000 0000000 station: !${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.py 0000664 0000000 0000000 00000004053 12134721171 0017234 0 ustar 00root root 0000000 0000000 ## 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]) + '' + child + '>')
if self.__dict__.keys().__contains__('_type'):
result.write('' + self._type + '>')
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/ 0000775 0000000 0000000 00000000000 12134721171 0017077 5 ustar 00root root 0000000 0000000 wfrog-0.8.2+svn953/wfdriver/output/__init__.py 0000664 0000000 0000000 00000002022 12134721171 0021204 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000003525 12134721171 0020435 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000002040 12134721171 0020567 0 ustar 00root root 0000000 0000000 ## 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/ 0000775 0000000 0000000 00000000000 12134721171 0017220 5 ustar 00root root 0000000 0000000 wfrog-0.8.2+svn953/wfdriver/station/__init__.py 0000664 0000000 0000000 00000004335 12134721171 0021336 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000005314 12134721171 0020545 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000005137 12134721171 0020512 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000007672 12134721171 0021625 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000006671 12134721171 0021752 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000047107 12134721171 0022033 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000007324 12134721171 0020527 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000007641 12134721171 0020533 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000075405 12134721171 0020634 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000036072 12134721171 0021220 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000043604 12134721171 0021013 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000007753 12134721171 0020764 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000013274 12134721171 0020764 0 ustar 00root root 0000000 0000000 ## 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/ 0000775 0000000 0000000 00000000000 12134721171 0016516 5 ustar 00root root 0000000 0000000 wfrog-0.8.2+svn953/wfdriver/test/call-test.py 0000664 0000000 0000000 00000000417 12134721171 0020762 0 ustar 00root root 0000000 0000000 import 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.yaml 0000664 0000000 0000000 00000000057 12134721171 0021274 0 ustar 00root root 0000000 0000000 station: !random-simulator {}
output: !call {}
wfrog-0.8.2+svn953/wfdriver/test/csv.yaml 0000664 0000000 0000000 00000000102 12134721171 0020166 0 ustar 00root root 0000000 0000000 station: !random-simulator {}
output: !service { name: events }
wfrog-0.8.2+svn953/wfdriver/test/multi-test.yaml 0000664 0000000 0000000 00000000270 12134721171 0021510 0 ustar 00root root 0000000 0000000 station: !random-simulator {}
output: !multi
children:
one: !service
name: out
instance: !stdio-out {}
two: !service
name: out
wfrog-0.8.2+svn953/wfdriver/wfdriver.py 0000775 0000000 0000000 00000007477 12134721171 0017763 0 ustar 00root root 0000000 0000000 #!/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/ 0000775 0000000 0000000 00000000000 12134721171 0015523 5 ustar 00root root 0000000 0000000 wfrog-0.8.2+svn953/wflogger/__init__.py 0000664 0000000 0000000 00000000000 12134721171 0017622 0 ustar 00root root 0000000 0000000 wfrog-0.8.2+svn953/wflogger/collector/ 0000775 0000000 0000000 00000000000 12134721171 0017511 5 ustar 00root root 0000000 0000000 wfrog-0.8.2+svn953/wflogger/collector/__init__.py 0000664 0000000 0000000 00000002421 12134721171 0021621 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000016020 12134721171 0022204 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000010441 12134721171 0020775 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000012262 12134721171 0021337 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000005320 12134721171 0021204 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000006513 12134721171 0021530 0 ustar 00root root 0000000 0000000 ## 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/ 0000775 0000000 0000000 00000000000 12134721171 0016770 5 ustar 00root root 0000000 0000000 wfrog-0.8.2+svn953/wflogger/config/wflogger.yaml 0000664 0000000 0000000 00000003207 12134721171 0021472 0 ustar 00root root 0000000 0000000 init:
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.yaml 0000664 0000000 0000000 00000006015 12134721171 0021002 0 ustar 00root root 0000000 0000000 init:
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/ 0000775 0000000 0000000 00000000000 12134721171 0016662 5 ustar 00root root 0000000 0000000 wfrog-0.8.2+svn953/wflogger/input/__init__.py 0000664 0000000 0000000 00000002326 12134721171 0020776 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000012443 12134721171 0020200 0 ustar 00root root 0000000 0000000 ## 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('%s>' % 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.py 0000664 0000000 0000000 00000006713 12134721171 0020155 0 ustar 00root root 0000000 0000000 ## 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):
pass wfrog-0.8.2+svn953/wflogger/input/function.py 0000664 0000000 0000000 00000002615 12134721171 0021065 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000004664 12134721171 0020225 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000003234 12134721171 0020360 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000013141 12134721171 0017235 0 ustar 00root root 0000000 0000000 #!/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/ 0000775 0000000 0000000 00000000000 12134721171 0016502 5 ustar 00root root 0000000 0000000 wfrog-0.8.2+svn953/wflogger/test/atom.yaml 0000664 0000000 0000000 00000000255 12134721171 0020330 0 ustar 00root root 0000000 0000000
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.yaml 0000664 0000000 0000000 00000000762 12134721171 0020166 0 ustar 00root root 0000000 0000000 context:
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.xmls 0000664 0000000 0000000 00000000605 12134721171 0020714 0 ustar 00root root 0000000 0000000
3
2
23.0
10503232
113
160
wfrog-0.8.2+svn953/wflogger/test/feed.xml 0000664 0000000 0000000 00000005341 12134721171 0020132 0 ustar 00root root 0000000 0000000
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.csv 0000664 0000000 0000000 00000010052 12134721171 0020341 0 ustar 00root root 0000000 0000000 timestamp,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.py 0000775 0000000 0000000 00000013737 12134721171 0017727 0 ustar 00root root 0000000 0000000 #!/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/ 0000775 0000000 0000000 00000000000 12134721171 0015523 5 ustar 00root root 0000000 0000000 wfrog-0.8.2+svn953/wfrender/__init__.py 0000664 0000000 0000000 00000001452 12134721171 0017636 0 ustar 00root root 0000000 0000000 ## 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.py 0000664 0000000 0000000 00000014444 12134721171 0017351 0 ustar 00root root 0000000 0000000 ## 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/ 0000775 0000000 0000000 00000000000 12134721171 0016770 5 ustar 00root root 0000000 0000000 wfrog-0.8.2+svn953/wfrender/config/default/ 0000775 0000000 0000000 00000000000 12134721171 0020414 5 ustar 00root root 0000000 0000000 wfrog-0.8.2+svn953/wfrender/config/default/24hours.yaml 0000664 0000000 0000000 00000000767 12134721171 0022620 0 ustar 00root root 0000000 0000000 renderer: !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.yaml 0000664 0000000 0000000 00000000746 12134721171 0022412 0 ustar 00root root 0000000 0000000 renderer: !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.yaml 0000664 0000000 0000000 00000001067 12134721171 0022502 0 ustar 00root root 0000000 0000000 renderer: !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.yaml 0000664 0000000 0000000 00000000745 12134721171 0022531 0 ustar 00root root 0000000 0000000 renderer: !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.yaml 0000664 0000000 0000000 00000000766 12134721171 0022340 0 ustar 00root root 0000000 0000000 renderer: !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.yaml 0000664 0000000 0000000 00000011740 12134721171 0025003 0 ustar 00root root 0000000 0000000 source: !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.yaml 0000664 0000000 0000000 00000025731 12134721171 0022574 0 ustar 00root root 0000000 0000000 renderer: !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.yaml 0000664 0000000 0000000 00000003106 12134721171 0022355 0 ustar 00root root 0000000 0000000 renderer: !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.yaml 0000664 0000000 0000000 00000002036 12134721171 0022763 0 ustar 00root root 0000000 0000000 renderer: !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.yaml 0000664 0000000 0000000 00000010046 12134721171 0024767 0 ustar 00root root 0000000 0000000 source: !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.yaml 0000664 0000000 0000000 00000006737 12134721171 0021422 0 ustar 00root root 0000000 0000000 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 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.png 0000664 0000000 0000000 00000007424 12134721171 0020445 0 ustar 00root root 0000000 0000000 PNG
IHDR 3 7 0r<