davisAPI full
This commit is contained in:
parent
7bb734a745
commit
396a248e10
5
davisAPI/.vscode/settings.json
vendored
Normal file
5
davisAPI/.vscode/settings.json
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"python.analysis.extraPaths": [
|
||||
"./PyWeather-master"
|
||||
]
|
||||
}
|
5
davisAPI/PyWeather-master/.gitignore
vendored
Normal file
5
davisAPI/PyWeather-master/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
*.pyc
|
||||
/build
|
||||
.idea/
|
||||
scripts/weatherpub.conf
|
||||
weather.egg-info/
|
67
davisAPI/PyWeather-master/CHANGELOG.txt
Normal file
67
davisAPI/PyWeather-master/CHANGELOG.txt
Normal file
@ -0,0 +1,67 @@
|
||||
.. PyWeather // (c) 2010, Patrick C. McGinty
|
||||
pyweather[@]tuxcoder[dot]com
|
||||
|
||||
v0.11.0
|
||||
======
|
||||
:Release Date: 2021-04-03
|
||||
* Added support of the Netatmo Weather Station.
|
||||
* Major update of weatherpub script:
|
||||
* Supports Netatmo weather station in addition to Vantage Pro.
|
||||
* Supports reading settings from the configuration file.
|
||||
* The major update attempts to be backwards-compatible, although it is very
|
||||
likely something is still broken.
|
||||
|
||||
v0.10.0
|
||||
======
|
||||
:Release Date: 2020-11-09
|
||||
* Python 3 support
|
||||
|
||||
v0.9.1
|
||||
======
|
||||
:Release Date: 8/9/10
|
||||
* Enhance debug output for publication failures
|
||||
|
||||
v0.9
|
||||
======
|
||||
:Release Date: 7/16/10
|
||||
|
||||
* Renamed vpro-to-wu.py script to weatherpub.py
|
||||
* Add support for pwsweather.com publication
|
||||
* Add support for local file publication
|
||||
|
||||
v0.8.2
|
||||
======
|
||||
:Release Date: 7/1/10
|
||||
|
||||
* Fixed run-time errors in vpro-to-wu.py script
|
||||
|
||||
v0.8.1
|
||||
======
|
||||
:Release Date: 7/1/10
|
||||
|
||||
* Add id/password params to vpro-to-wu.py script
|
||||
* Add update delay param to vpro-to-wu.py script
|
||||
* Add TTY device selector to vpro-to-wu.py script
|
||||
|
||||
v0.8
|
||||
========
|
||||
:Release Date: 6/25/10
|
||||
|
||||
* Add support for VantagePro CRC
|
||||
* Add support for VantagePro DMPAFT command
|
||||
* Misc fixes for VantagePro class
|
||||
* Misc fixes for Wunderground Publisher class
|
||||
* Add vpro-to-wu.py script for uploading Davis Vantage Pro data to
|
||||
wunderground.com
|
||||
|
||||
v0.7.1
|
||||
========
|
||||
:Release Date: 6/24/10
|
||||
|
||||
* Repackaged and updated build scripts.
|
||||
|
||||
v0.7.0
|
||||
========
|
||||
:Release Date: 6/20/10
|
||||
|
||||
* Official release from Christopher Blunk.
|
674
davisAPI/PyWeather-master/COPYING.txt
Normal file
674
davisAPI/PyWeather-master/COPYING.txt
Normal file
@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
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.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
15
davisAPI/PyWeather-master/INSTALL.txt
Normal file
15
davisAPI/PyWeather-master/INSTALL.txt
Normal file
@ -0,0 +1,15 @@
|
||||
Any one of the following command will install PyWeather. Make sure to run as
|
||||
``root`` user:
|
||||
|
||||
a. Use :command:`easy_install` from the `setuptools package
|
||||
<http://peak.telecommunity.com/DevCenter/EasyInstall]>`_::
|
||||
|
||||
sudo easy_install weather
|
||||
|
||||
b. Download the source distribution file and install from the
|
||||
command line::
|
||||
|
||||
tar xzf weather-*.tar.gz
|
||||
cd weather-*
|
||||
sudo make install
|
||||
|
3
davisAPI/PyWeather-master/MANIFEST.in
Normal file
3
davisAPI/PyWeather-master/MANIFEST.in
Normal file
@ -0,0 +1,3 @@
|
||||
include *.txt
|
||||
include Makefile
|
||||
include *.md
|
55
davisAPI/PyWeather-master/Makefile
Normal file
55
davisAPI/PyWeather-master/Makefile
Normal file
@ -0,0 +1,55 @@
|
||||
# Makefile for installing and testing PyWeather
|
||||
#
|
||||
# Author: Patrick C. McGinty
|
||||
# Date: Thursday, June 10 2010
|
||||
|
||||
|
||||
NAME=weather
|
||||
VER=$(shell python -c 'import weather;print weather.__version__')
|
||||
DIST_DIR=dist
|
||||
TAR=${DIST_DIR}/${NAME}-${VER}.tar.gz
|
||||
SRC_DIR=${DIST_DIR}/${NAME}-${VER}
|
||||
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo "The following targets are defined:"
|
||||
@echo ""
|
||||
@echo " clean remove tmp files"
|
||||
@echo " dist create distribution archive file"
|
||||
@echo " help print usage instructions for the Makefile"
|
||||
@echo " install install program into system dirs"
|
||||
@echo " test execute all unit tests"
|
||||
@echo " release perform a full test/dist/install"
|
||||
@echo " register update the PyPI registration"
|
||||
@echo ""
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
nosetests
|
||||
|
||||
.PHONY: install
|
||||
install:
|
||||
python setup.py install
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
python setup.py clean
|
||||
|
||||
.PHONY: dist
|
||||
dist:
|
||||
python setup.py sdist --force-manifest
|
||||
make clean
|
||||
|
||||
.PHONY: dist-test
|
||||
dist-test:
|
||||
tar xzf ${TAR} -C ${DIST_DIR}
|
||||
sudo make -C ${SRC_DIR} install
|
||||
sudo rm -rf ${SRC_DIR}
|
||||
|
||||
.PHONY: release
|
||||
release: test dist dist-test
|
||||
|
||||
.PHONY: register
|
||||
register:
|
||||
python setup.py register
|
||||
|
85
davisAPI/PyWeather-master/README.md
Normal file
85
davisAPI/PyWeather-master/README.md
Normal file
@ -0,0 +1,85 @@
|
||||
# PyWeather
|
||||
|
||||
## Abstract
|
||||
|
||||
PyWeather contains weather related modules implemented in Python.
|
||||
Anything weather related is fair game for PyWeather. Currently
|
||||
PyWeather is limited to unit conversion, console reading, and data
|
||||
publication. But, future work can be added to PyWeather in any area.
|
||||
|
||||
|
||||
## Unit Conversion
|
||||
|
||||
PyWeather has a lot of support for common unit conversions in
|
||||
distance, temperature, pressure, and volume. Conversion from
|
||||
Fahrenheit to Celsius, and kelvin is supported, as well as conversions
|
||||
between inches of mercury and millibars.
|
||||
|
||||
|
||||
## Station Observations
|
||||
|
||||
PyWeather also contains modules that are capable of downloading
|
||||
observations from weather consoles. The current list of supported
|
||||
weather consoles includes:
|
||||
|
||||
- Davis Vantage Pro
|
||||
- Davis Vantage Pro2
|
||||
- Netatmo weather station.
|
||||
|
||||
|
||||
## Data Publication
|
||||
|
||||
PyWeather contains a module that allows developers to post conditions
|
||||
to weather aggregation sites. The current list of support services includes:
|
||||
|
||||
- WeatherUnderground (wundgerground.com)
|
||||
- PWS Weather (pwsweather.com)
|
||||
- WeatherForYou (weatherforyou.com)
|
||||
|
||||
|
||||
For additional information, please email the maintainer:
|
||||
pyweather@tuxcoder.com
|
||||
|
||||
## Data Publication Script
|
||||
|
||||
`scripts/weatherpub.py` supports publication of the weather data. It can also serve as a good usage example.
|
||||
|
||||
### General usage
|
||||
|
||||
1. Copy `weatherpub.conf.example` as `weatehrpub.conf`.
|
||||
2. Modify `weatehrpub.conf`:
|
||||
3. In `[general]` section set `station` to the name of the station you have.
|
||||
4. Set `publication` to a comma-separated list of weather services you'd like
|
||||
to push data to.
|
||||
5. Configure weather station and publication service in corresponding sections
|
||||
of the configuration file (see below for more details).
|
||||
3. Run it: `./scripts/weatherpub.py -c scripts/weatherpub.conf`
|
||||
|
||||
### Weather stations
|
||||
|
||||
The script supports Vantage Pro and Netatmo. Vantage Pro support was not recently tested and may be broken by the latest update. Please report bugs and/or send pull requests.
|
||||
|
||||
#### Vantage Pro
|
||||
|
||||
By default script excepts Vantage Pro weather stations to be connected to /`/dev/ttyS0`. Use `--tty` command-line flag to override it (this cannot be currently set via command-line), e.g. `./scripts/weatherpub.py -c scripts/weatherpub.conf --tty /dev/ttyS1`
|
||||
|
||||
#### Netatmo
|
||||
|
||||
Since netatmo works via public API, some setup required first:
|
||||
|
||||
1. [Create a Netatmo app](https://dev.netatmo.com/apps/createanapp#form).
|
||||
2. Use generated app id and secret as `client_id` and `client_secret`.
|
||||
3. Use your own Netatmo username (e-mail) as `username` and `password`.
|
||||
4. Set `module_name` to your outdoor module name, e.g. 'Outdoor'.
|
||||
|
||||
Note: Rain Gauge and Anemometer are not supported yet.
|
||||
|
||||
### Publication services
|
||||
|
||||
Only 3 publication service are currently supported. Out of them only PWS Weather was properly tested.
|
||||
|
||||
#### PWS Weather
|
||||
|
||||
1. [Create PWS Weather profile](https://www.pwsweather.com/register).
|
||||
2. Go to your [dashboard](https://dashboard.pwsweather.com/) and [create a station](https://dashboard.pwsweather.com/stations/add).
|
||||
3. In `[pwsweather]` section of the configuration file use your station id as `site_id`, and password from account you created at step 1 as `password`.
|
23
davisAPI/PyWeather-master/scripts/weatherpub.conf.example
Normal file
23
davisAPI/PyWeather-master/scripts/weatherpub.conf.example
Normal file
@ -0,0 +1,23 @@
|
||||
[general]
|
||||
# Weather station name. Currently supported: vantage_pro, netatmo.
|
||||
station=netatmo
|
||||
# Publication websites, comma-separated.
|
||||
# Currently supported: 'wug', 'pwsweather', 'file'
|
||||
publication=pwsweather
|
||||
|
||||
##
|
||||
# Netatmo weather station settings
|
||||
[netatmo]
|
||||
|
||||
client_id="<your client_id>"
|
||||
client_secret="<your client_secret>"
|
||||
username="<netatmo username>"
|
||||
password="<netatmo user password>"
|
||||
module_name=Outdoor
|
||||
|
||||
##
|
||||
# pwsweather.com settings
|
||||
[pwsweather]
|
||||
|
||||
site_id=MySiteID
|
||||
password=MyPassowrd
|
240
davisAPI/PyWeather-master/scripts/weatherpub.py
Normal file
240
davisAPI/PyWeather-master/scripts/weatherpub.py
Normal file
@ -0,0 +1,240 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# PyWeather example script for reading PyWeather stations uploading to one or
|
||||
# more PyWeather publication sites
|
||||
#
|
||||
# Author: Patrick C. McGinty
|
||||
# Email: pyweather@tuxcoder.com
|
||||
# Date: Sunday, May 02 2010
|
||||
'''
|
||||
Periodically read data from a local weather station and upload to the PyWeather
|
||||
publication site.
|
||||
'''
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
import optparse
|
||||
import configparser
|
||||
|
||||
import weather.stations
|
||||
import weather.stations.netatmo
|
||||
import weather.services
|
||||
|
||||
log = logging.getLogger('')
|
||||
|
||||
# Intervals (in minutes) between each archive record generated by the weather
|
||||
# station:
|
||||
ARCHIVE_INTERVAL = 10
|
||||
# Gust 'time to live'; define many minutes should gust be reported:
|
||||
GUST_TTL = 10
|
||||
GUST_MPH_MIN = 7 # minimum mph of gust above avg wind speed to report
|
||||
|
||||
# Publication Services Lookup Table
|
||||
# key expected to match optparse destination parameter
|
||||
# value defines class object of publication service
|
||||
PUB_SERVICES = {
|
||||
'wug': weather.services.Wunderground,
|
||||
'pws': weather.services.PwsWeather,
|
||||
'pwsweather': weather.services.PwsWeather,
|
||||
'file': weather.services.TextFile,
|
||||
}
|
||||
|
||||
STATIONS = {
|
||||
'netatmo': weather.stations.netatmo.NetatmoStation,
|
||||
'vantage_pro': weather.stations.VantagePro,
|
||||
}
|
||||
|
||||
|
||||
class NoSensorException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class WindGust(object):
|
||||
NO_VALUE = ('NA', 'NA')
|
||||
|
||||
def __init__(self):
|
||||
self.value = self.NO_VALUE
|
||||
self.count = 0
|
||||
|
||||
def get(self, station, interval):
|
||||
'''
|
||||
return gust data, if above threshold value and current time is inside
|
||||
reporting window period
|
||||
'''
|
||||
rec = station.fields['Archive']
|
||||
# process new data
|
||||
if rec:
|
||||
threshold = station.fields['WindSpeed10Min'] + GUST_MPH_MIN
|
||||
if rec['WindHi'] >= threshold:
|
||||
self.value = (rec['WindHi'], rec['WindHiDir'])
|
||||
self.count = GUST_TTL * 60 / interval
|
||||
else:
|
||||
self.value = self.NO_VALUE
|
||||
|
||||
# return gust value, if remaining time is left, and valid
|
||||
if self.count:
|
||||
self.count -= 1
|
||||
else:
|
||||
self.value = self.NO_VALUE
|
||||
|
||||
log.debug('wind gust of {0} mph from {1}'.format(*self.value))
|
||||
return self.value
|
||||
|
||||
|
||||
WindGust = WindGust()
|
||||
|
||||
|
||||
def weather_update(station, pub_sites, interval):
|
||||
'''
|
||||
main execution loop. query weather data and post to online service.
|
||||
'''
|
||||
point = station.get_reading()
|
||||
|
||||
# santity check weather data
|
||||
if point.temperature_f > 200:
|
||||
raise NoSensorException(
|
||||
'Out of range temperature value: %.1f, check sensors' %
|
||||
(point.temperature_f,))
|
||||
|
||||
gust = None
|
||||
gust_dir = None
|
||||
if isinstance(station, weather.stations.VantagePro):
|
||||
# Wind is only supported in VantagePro.
|
||||
gust, gust_dir = WindGust.get(station, interval)
|
||||
|
||||
# upload data in the following order:
|
||||
for ps in pub_sites:
|
||||
try: # try block necessary to attempt every publisher
|
||||
ps.set(
|
||||
pressure=point.pressure,
|
||||
dewpoint=point.dew_point_f,
|
||||
humidity=point.humidity,
|
||||
tempf=point.temperature_f,
|
||||
rainin=point.rain_rate_in,
|
||||
rainday=point.rain_day_in,
|
||||
windspeed=point.wind_speed_mph,
|
||||
winddir=point.wind_direction,
|
||||
windgust=gust,
|
||||
windgustdir=gust_dir,
|
||||
dateutc=point.time.strftime("%Y-%m-%d %H:%M:%S"))
|
||||
ps.publish()
|
||||
# TODO: add user-friendly name
|
||||
log.info("Published to %s", ps.__class__)
|
||||
except (Exception) as e:
|
||||
log.exception('publisher %s: %s' % (ps.__class__.__name__, e))
|
||||
|
||||
|
||||
def init_log(quiet, debug):
|
||||
'''
|
||||
setup system logging to desired verbosity.
|
||||
'''
|
||||
from logging.handlers import SysLogHandler
|
||||
fmt = logging.Formatter(
|
||||
os.path.basename(sys.argv[0]) +
|
||||
".%(name)s %(levelname)s - %(message)s")
|
||||
facility = SysLogHandler.LOG_DAEMON
|
||||
syslog = SysLogHandler(address='/dev/log', facility=facility)
|
||||
syslog.setFormatter(fmt)
|
||||
log.addHandler(syslog)
|
||||
if not quiet:
|
||||
console = logging.StreamHandler()
|
||||
console.setFormatter(fmt)
|
||||
log.addHandler(console)
|
||||
log.setLevel(logging.INFO)
|
||||
if debug:
|
||||
log.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
def get_pub_services(opts, config):
|
||||
'''
|
||||
use values in opts data to generate instances of publication services.
|
||||
'''
|
||||
sites = []
|
||||
for p_key in list(vars(opts).keys()):
|
||||
args = getattr(opts, p_key)
|
||||
if p_key in PUB_SERVICES and args:
|
||||
if isinstance(args, tuple):
|
||||
ps = PUB_SERVICES[p_key](*args)
|
||||
else:
|
||||
ps = PUB_SERVICES[p_key](args)
|
||||
sites.append(ps)
|
||||
if config:
|
||||
for p_key in config['general']['publication'].split(','):
|
||||
ps = PUB_SERVICES[p_key](**config[p_key])
|
||||
sites.append(ps)
|
||||
return sites
|
||||
|
||||
|
||||
def get_options(parser):
|
||||
'''
|
||||
read command line options to configure program behavior.
|
||||
'''
|
||||
# station services
|
||||
# publication services
|
||||
pub_g = optparse.OptionGroup(
|
||||
parser, "Publication Services",
|
||||
'One or more publication service must be specified to enable upload '
|
||||
'of weather data.',)
|
||||
pub_g.add_option(
|
||||
'-w', '--wundergound', nargs=2, type='string', dest='wug',
|
||||
help='Weather Underground service; WUG=[SID(station ID), PASSWORD]')
|
||||
pub_g.add_option(
|
||||
'-p', '--pws', nargs=2, type='string', dest='pws',
|
||||
help='PWS service; PWS=[SID(station ID), PASSWORD]')
|
||||
pub_g.add_option(
|
||||
'-f', '--file', nargs=1, type='string', dest='file',
|
||||
help='Local file; FILE=[FILE_NAME]')
|
||||
parser.add_option_group(pub_g)
|
||||
|
||||
parser.add_option(
|
||||
'-d', '--debug', dest='debug', action="store_true",
|
||||
default=False, help='enable verbose debug logging')
|
||||
parser.add_option(
|
||||
'-q', '--quiet', dest='quiet', action="store_true",
|
||||
default=False, help='disable all console logging')
|
||||
parser.add_option(
|
||||
'-t', '--tty', dest='tty', default='/dev/ttyS0',
|
||||
help='set serial port device [/dev/ttyS0]')
|
||||
parser.add_option(
|
||||
'-n', '--interval', dest='interval', default=60,
|
||||
type='int', help='polling/update interval in seconds [60]')
|
||||
parser.add_option('-c', '--config', dest='config_path', default=None,
|
||||
type='str', help='path to the configuration file')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = optparse.OptionParser()
|
||||
opts, args = get_options(parser)
|
||||
init_log(opts.quiet, opts.debug)
|
||||
config = None
|
||||
|
||||
if opts.config_path:
|
||||
config = configparser.ConfigParser()
|
||||
config.read_file(open(opts.config_path))
|
||||
|
||||
# configure publication service defined in command-line args
|
||||
pub_sites = get_pub_services(opts, config)
|
||||
|
||||
if not pub_sites:
|
||||
log.error('no publication service defined')
|
||||
sys.exit(-1)
|
||||
|
||||
if config:
|
||||
station_name = config['general']['station']
|
||||
station = STATIONS[station_name](**config[station_name])
|
||||
else:
|
||||
# Only VantagePro is supported without config.
|
||||
station = weather.stations.VantagePro(opts.tty, ARCHIVE_INTERVAL)
|
||||
|
||||
while True:
|
||||
try:
|
||||
weather_update(station, pub_sites, opts.interval)
|
||||
except (Exception) as e:
|
||||
log.exception(e)
|
||||
# pause until next update time
|
||||
next_update = opts.interval - (time.time() % opts.interval)
|
||||
log.info('sleep')
|
||||
time.sleep(next_update)
|
47
davisAPI/PyWeather-master/setup.py
Normal file
47
davisAPI/PyWeather-master/setup.py
Normal file
@ -0,0 +1,47 @@
|
||||
#! /usr/bin/env python
|
||||
#
|
||||
# PyWeather
|
||||
# (c) 2010 Patrick C. McGinty <pyweather@tuxcoder.com>
|
||||
# (c) 2005 Christopher Blunck <chris@wxnet.org>
|
||||
#
|
||||
# You're welcome to redistribute this software under the
|
||||
# terms of the GNU General Public Licence version 2.0
|
||||
# or, at your option, any higher version.
|
||||
#
|
||||
# You can read the complete GNU GPL in the file COPYING
|
||||
# which should come along with this software, or visit
|
||||
# the Free Software Foundation's WEB site http://www.fsf.org
|
||||
#
|
||||
|
||||
import os
|
||||
from distutils.core import setup
|
||||
from pathlib import Path
|
||||
|
||||
import weather as pkg
|
||||
name = pkg.__name__
|
||||
|
||||
this_directory = Path(__file__).parent
|
||||
long_description = (this_directory / "README.md").read_text()
|
||||
|
||||
setup(name=name,
|
||||
version=pkg.__version__,
|
||||
license="GNU GPL",
|
||||
description=pkg.__doc__,
|
||||
long_description = long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
author="Patrick C. McGinty, Christopher Blunck",
|
||||
author_email="pyweather@tuxcoder.com, chris@wxnet.org",
|
||||
url="http://github.com/cmcginty/PyWeather",
|
||||
download_url="https://github.com/cmcginty/PyWeather/archive/%s.zip" %
|
||||
pkg.__version__,
|
||||
packages=[
|
||||
name,
|
||||
name + '.services',
|
||||
name + '.stations',
|
||||
name + '.units',
|
||||
],
|
||||
install_requires=[
|
||||
'pyserial==3.5'
|
||||
],
|
||||
scripts=['scripts/weatherpub.py'],
|
||||
)
|
18
davisAPI/PyWeather-master/weather/__init__.py
Normal file
18
davisAPI/PyWeather-master/weather/__init__.py
Normal file
@ -0,0 +1,18 @@
|
||||
'''
|
||||
PyWeather branch; bindings for Davis Vantage Pro and Pro2 weather stations,
|
||||
upload of weather data (e.g. wunderground.com), and meteorological
|
||||
calculation/conversion functions.
|
||||
'''
|
||||
|
||||
__version__ = '0.11.0'
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
class NullHandler(logging.Handler):
|
||||
def emit(self, record):
|
||||
pass
|
||||
|
||||
|
||||
# init a null handler, to prevent warnings
|
||||
logging.getLogger(__name__).addHandler(NullHandler())
|
3
davisAPI/PyWeather-master/weather/services/__init__.py
Normal file
3
davisAPI/PyWeather-master/weather/services/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
from .wunderground import *
|
||||
from .pws import *
|
||||
from .file import *
|
64
davisAPI/PyWeather-master/weather/services/_base.py
Normal file
64
davisAPI/PyWeather-master/weather/services/_base.py
Normal file
@ -0,0 +1,64 @@
|
||||
'''
|
||||
Common base classes for PyWeather pulication services.
|
||||
'''
|
||||
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PublishException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class HttpPublisher(object):
|
||||
'''
|
||||
Abstract base class for creation generic HTTP publication services
|
||||
'''
|
||||
SOFTWARE = 'PyWeather'
|
||||
STD_SERVER = None
|
||||
REALTIME_SERVER = None
|
||||
URI = None
|
||||
|
||||
def __init__(self, sid, password, rtfreq=None):
|
||||
self.sid = sid
|
||||
self.password = password
|
||||
self.rtfreq = rtfreq
|
||||
|
||||
def set(self, *args, **kw):
|
||||
'''
|
||||
Useful for defining weather data published to the server. Each
|
||||
publication service implements their own supported keyword args, but
|
||||
should support any number of arguments.
|
||||
'''
|
||||
raise NotImplementedError("abstract method")
|
||||
|
||||
@staticmethod
|
||||
def _publish(args, server, uri):
|
||||
from http.client import HTTPConnection, HTTPSConnection
|
||||
from urllib.parse import urlencode
|
||||
|
||||
args = {k: v for k, v in args.items() if v is not None and v != 'NA'}
|
||||
uri = uri + "?" + urlencode(args)
|
||||
|
||||
log.debug('Connect to: https://%s' % server)
|
||||
log.debug('GET %s' % uri)
|
||||
|
||||
conn = HTTPSConnection(server, timeout=5)
|
||||
if not conn:
|
||||
raise PublishException('Remote server connection timeout')
|
||||
conn.request("GET", uri)
|
||||
|
||||
http = conn.getresponse()
|
||||
data = (http.status, http.reason, http.read())
|
||||
conn.close()
|
||||
if not (data[0] == 200 and data[1] == 'OK'):
|
||||
raise PublishException(
|
||||
'Server returned invalid status: %d %s %s' % data)
|
||||
return data
|
||||
|
||||
def publish(self):
|
||||
'''
|
||||
Perform HTTP session to transmit defined weather values.
|
||||
'''
|
||||
return self._publish(self.args, self.server, self.URI)
|
86
davisAPI/PyWeather-master/weather/services/file.py
Normal file
86
davisAPI/PyWeather-master/weather/services/file.py
Normal file
@ -0,0 +1,86 @@
|
||||
'''
|
||||
Local File Publisher
|
||||
|
||||
Abstract:
|
||||
The class contained within this module allows python programs to
|
||||
publish weather conditions to a local text file. The format of the file is:
|
||||
|
||||
field value [value ...]
|
||||
field value [value ...]
|
||||
field value [value ...]
|
||||
...
|
||||
...
|
||||
|
||||
Each 'field' will begin on a separate line. The 'field' parameter is always a
|
||||
single word. Depending on the field, there maybe be multiple 'value'
|
||||
parameters. All fields and values are separated by a single space. String
|
||||
values will be surrounded by quotes.
|
||||
|
||||
This class does not define field names. The implementation assigns field names
|
||||
from the keyword parameters passed to it through the set() method. Therefore it
|
||||
is up to the user to define all field names using named parameters with the
|
||||
'set()' method. If you desire to keep the TextFile.set() command compatible
|
||||
with other set() publisher methods, please reference the other classes for
|
||||
expected field names.
|
||||
|
||||
Usage:
|
||||
>>> publisher = TextFile( 'file_name' )
|
||||
>>> publisher.set( ... )
|
||||
>>> publisher.publish()
|
||||
|
||||
Author: Patrick C. McGinty (pyweather@tuxcoder.com)
|
||||
Date: Thursday, July 15 2010
|
||||
'''
|
||||
|
||||
|
||||
|
||||
import io
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
from . _base import *
|
||||
|
||||
|
||||
class TextFile(object):
|
||||
'''
|
||||
Publishes weather data to a local file. See module
|
||||
documentation for additional information and usage idioms.
|
||||
'''
|
||||
def __init__(self, file_name):
|
||||
self.file_name = file_name
|
||||
self.args = {}
|
||||
|
||||
def set( self, **kw):
|
||||
'''
|
||||
Store keyword args to be written to output file.
|
||||
'''
|
||||
self.args = kw
|
||||
log.debug( self.args )
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _append_vals( buf, val):
|
||||
if isinstance(val,dict):
|
||||
msg = 'unsupported %s type: %s' % (type(val),repr(val),)
|
||||
log.error(msg)
|
||||
if isinstance(val,(list,tuple)):
|
||||
for i in val:
|
||||
TextFile._append_vals(buf, i)
|
||||
else:
|
||||
buf.write(' ' + repr(val))
|
||||
|
||||
|
||||
def publish(self):
|
||||
'''
|
||||
Write output file.
|
||||
'''
|
||||
with open( self.file_name, 'w') as fh:
|
||||
for k,v in self.args.items():
|
||||
buf = io.StringIO()
|
||||
buf.write(k)
|
||||
self._append_vals(buf,v)
|
||||
fh.write(buf.getvalue() + '\n')
|
||||
buf.close() # free string buffer
|
||||
|
||||
|
||||
|
91
davisAPI/PyWeather-master/weather/services/pws.py
Normal file
91
davisAPI/PyWeather-master/weather/services/pws.py
Normal file
@ -0,0 +1,91 @@
|
||||
'''
|
||||
PWSweather.com Publisher
|
||||
WeatherForYou.com Publisher
|
||||
|
||||
Abstract:
|
||||
The class contained within this module allows python programs to
|
||||
publish weather conditions to the pwsweather.com servers.
|
||||
|
||||
Usage:
|
||||
>>> publisher = PwsWeather( 'MySiteID', 'MyPassowrd')
|
||||
>>> publisher.set( ... )
|
||||
>>> response = publisher.publish()
|
||||
>>> print '%s: %s' % (response.status, response.reason)
|
||||
|
||||
Notes on arguments to Publisher.set():
|
||||
<float> pressure: in inches of Hg
|
||||
<float> dewpoint: in Fahrenheit
|
||||
<float> humidity: between 0.0 and 100.0 inclusive
|
||||
<float> tempf: in Fahrenheit
|
||||
<float> rainin: inches/hour of rain
|
||||
<float> rainday: total rainfall for day (localtime)
|
||||
<float> rainmonth: total rainfall for month (localtime)
|
||||
<float> rainyear: total rainfall for year (localtime)
|
||||
<tuple> dateutc: date string, "YYYY-MM-DD HH:MM:SS"
|
||||
<float> windgust: in mph
|
||||
<float> windspeed: in mph
|
||||
<float> winddir: in degrees, between 0.0 and 360.0
|
||||
<string> weather: unknown at this time (email me if you know!)
|
||||
|
||||
Author: Patrick C. McGinty (pyweather@tuxcoder.com)
|
||||
Date: Tuesday, July 13 2010
|
||||
'''
|
||||
|
||||
import logging
|
||||
from . _base import *
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PwsWeather(HttpPublisher):
|
||||
'''
|
||||
Publishes weather data to the pwsweather.com servers. See module
|
||||
documentation for additional information and usage idioms.
|
||||
'''
|
||||
STD_SERVER = "www.pwsweather.com"
|
||||
URI = "/pwsupdate/pwsupdate.php"
|
||||
|
||||
def __init__(self, sid: str = None, password: str = None,
|
||||
site_id: str = None):
|
||||
super(PwsWeather, self).__init__(sid, password)
|
||||
self.args = {'ID': sid or site_id,
|
||||
'PASSWORD': password,
|
||||
'action': 'updateraw',
|
||||
'softwaretype': self.SOFTWARE}
|
||||
self.server = self.STD_SERVER
|
||||
|
||||
def set(self, pressure='NA', dewpoint='NA', humidity='NA', tempf='NA',
|
||||
rainin='NA', rainday='NA', rainmonth='NA', rainyear='NA',
|
||||
dateutc='NA', windgust='NA', windspeed='NA', winddir='NA',
|
||||
weather='NA', *args, **kw):
|
||||
'''
|
||||
Define weatehr data published to the server.
|
||||
|
||||
Parameters not sent will be cleared and not set to server. Unknown
|
||||
keyword args will be silently ignored, so be careful. This is necessary
|
||||
for publishers that support more fields than others.
|
||||
'''
|
||||
# unused, but valid, parameters are:
|
||||
# solarradiation, UV
|
||||
self.args.update({
|
||||
'baromin': pressure,
|
||||
'dailyrainin': rainday,
|
||||
'dateutc': dateutc,
|
||||
'dewptf': dewpoint,
|
||||
'humidity': humidity,
|
||||
'monthrainin': rainmonth,
|
||||
'rainin': rainin,
|
||||
'tempf': tempf,
|
||||
'weather': weather,
|
||||
'winddir': winddir,
|
||||
'windgustmph': windgust,
|
||||
'windspeedmph': windspeed,
|
||||
'yearrainin': rainyear})
|
||||
log.debug(self.args)
|
||||
|
||||
def publish(self):
|
||||
http = super(PwsWeather, self).publish()
|
||||
if not http[2].find(b'Logged and posted') >= 0:
|
||||
raise PublishException(
|
||||
'Server returned invalid status: %d %s %s' % http)
|
||||
return http
|
@ -0,0 +1,60 @@
|
||||
|
||||
import builtins
|
||||
import unittest
|
||||
from mock import Mock, patch, mock_open
|
||||
|
||||
from ..file import TextFile
|
||||
|
||||
|
||||
class TestPublish(unittest.TestCase):
|
||||
|
||||
F = TextFile('output.txt')
|
||||
|
||||
def test_set(self):
|
||||
self.F.set(a=1, b=2, c=3)
|
||||
self.assertEqual(self.F.args, {'a': 1, 'b': 2, 'c': 3})
|
||||
self.F.set(c=1, d=2, e=3)
|
||||
self.assertEqual(self.F.args, {'c': 1, 'd': 2, 'e': 3})
|
||||
|
||||
def test_positional_set(self):
|
||||
self.assertRaises(TypeError, self.F.set, 1, 2, 3)
|
||||
|
||||
def test_single_value(self):
|
||||
m = mock_open()
|
||||
with patch.object(builtins, 'open', m):
|
||||
self.F.set(a=1)
|
||||
self.F.publish()
|
||||
|
||||
m.assert_called()
|
||||
handle = m()
|
||||
handle.write.assert_called_with('a 1\n')
|
||||
|
||||
def test_string_value(self):
|
||||
m = mock_open()
|
||||
with patch.object(builtins, 'open', m):
|
||||
self.F.set(a='string arg')
|
||||
self.F.publish()
|
||||
|
||||
m.assert_called()
|
||||
handle = m()
|
||||
handle.write.assert_called_with("a 'string arg'\n")
|
||||
|
||||
def test_list_value(self):
|
||||
m = mock_open()
|
||||
with patch.object(builtins, 'open', m):
|
||||
self.F.set(a=[1, 2, 3])
|
||||
self.F.publish()
|
||||
|
||||
m.assert_called()
|
||||
handle = m()
|
||||
handle.write.assert_called_with("a 1 2 3\n")
|
||||
|
||||
def test_double_list_value(self):
|
||||
m = mock_open()
|
||||
with patch.object(builtins, 'open', m):
|
||||
self.F.set(a=[['a', 'b', 'c'], 2, 3])
|
||||
self.F.publish()
|
||||
|
||||
m.assert_called()
|
||||
handle = m()
|
||||
handle.write.assert_called_with("a 'a' 'b' 'c' 2 3\n")
|
121
davisAPI/PyWeather-master/weather/services/wunderground.py
Normal file
121
davisAPI/PyWeather-master/weather/services/wunderground.py
Normal file
@ -0,0 +1,121 @@
|
||||
'''
|
||||
WUnderground.com Publisher
|
||||
|
||||
Abstract:
|
||||
The class contained within this module allows python programs to
|
||||
publish weather conditions to the wunderground.com servers. That is,
|
||||
this class encapsulates the wire protocol wunderground.com supports
|
||||
and allows application developers to insulate themselves against
|
||||
changes in the wunderground.com wire protocol.
|
||||
|
||||
If the rtfreq parameter is passed to the Publisher constructor,
|
||||
posting of the current conditions will go to the "real time updater"
|
||||
service provided by wunderground.com. The rtfreq optional parameter
|
||||
passed to the constructor is a float that represents the number of
|
||||
seconds between observations.
|
||||
|
||||
Usage:
|
||||
>>> publisher = Wunderground( 'MySiteID', 'MyPassowrd')
|
||||
>>> publisher.set( ... )
|
||||
>>> response = publisher.publish()
|
||||
>>> print '%s: %s' % (response.status, response.reason)
|
||||
|
||||
Notes on arguments to Publisher.set():
|
||||
<float> pressure: in inches of Hg
|
||||
<float> dewpoint: in Fahrenheit
|
||||
<float> humidity: between 0.0 and 100.0 inclusive
|
||||
<float> tempf: in Fahrenheit
|
||||
<float> rainin: inches/hour of rain
|
||||
<float> rainday: total rainfall in day (localtime)
|
||||
<string> dateutc: date "YYYY-MM-DD HH:MM:SS" in GMT timezone
|
||||
<float> windgust: in mph
|
||||
<float> windgustdir:in degrees, between 0.0 and 360.0
|
||||
<float> windspeed: in mph
|
||||
<float> winddir: in degrees, between 0.0 and 360.0
|
||||
<string> clouds: unknown at this time (email me if you know!)
|
||||
<string> weather: unknown at this time (email me if you know!)
|
||||
|
||||
Developers Notes:
|
||||
It appears that even if you provide an invalid username and password,
|
||||
a status of 200, and a reason of "OK" is returned.
|
||||
|
||||
Author: Patrick C. McGinty (pyweather@tuxcoder.com)
|
||||
Date: Tuesday, July 13 2010
|
||||
Author: Christopher Blunck (chris@wxnet.org)
|
||||
Date: 2006-03-27
|
||||
'''
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
from . _base import *
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Wunderground(HttpPublisher):
|
||||
'''
|
||||
Publishes weather data to the wunderground.com servers. See
|
||||
module documentation for additional information and usage idioms.
|
||||
'''
|
||||
STD_SERVER = "weatherstation.wunderground.com"
|
||||
REALTIME_SERVER = "rtupdate.wunderground.com"
|
||||
URI = "/weatherstation/updateweatherstation.php"
|
||||
|
||||
def __init__(self, sid, password, rtfreq=None):
|
||||
super(Wunderground, self).__init__(sid, password)
|
||||
self.args = {'ID': sid,
|
||||
'PASSWORD': password,
|
||||
'action': 'updateraw',
|
||||
'softwaretype': self.SOFTWARE, }
|
||||
if rtfreq:
|
||||
self.args['realtime'] = 1
|
||||
self.args['rtfreq'] = self.rtfreq
|
||||
self.server = self.REALTIME_SERVER
|
||||
else:
|
||||
self.server = self.STD_SERVER
|
||||
|
||||
def set(self, pressure='NA', dewpoint='NA', humidity='NA', tempf='NA',
|
||||
rainin='NA', rainday='NA', dateutc='NA', windgust='NA',
|
||||
windgustdir='NA', windspeed='NA', winddir='NA',
|
||||
clouds='NA', weather='NA', *args, **kw):
|
||||
'''
|
||||
Useful for defining weather data published to the server. Parameters
|
||||
not set will be reset and not sent to server. Unknown keyword args will
|
||||
be silently ignored, so be careful. This is necessary for publishers
|
||||
that support more fields than others.
|
||||
'''
|
||||
# see: http://wiki.wunderground.com/index.php/PWS_-_Upload_Protocol
|
||||
# unused, but valid, parameters are:
|
||||
# windspdmph_avg2m, winddir_avg2m, windgustmph_10m, windgusdir_10m
|
||||
# soiltempf, soilmoisture, leafwetness, solarradiation, UV
|
||||
# indoortempf, indoorhumidity
|
||||
self.args.update({
|
||||
'baromin': pressure,
|
||||
'clouds': clouds,
|
||||
'dailyrainin': rainday,
|
||||
'dateutc': dateutc,
|
||||
'dewptf': dewpoint,
|
||||
'humidity': humidity,
|
||||
'rainin': rainin,
|
||||
'tempf': tempf,
|
||||
'weather': weather,
|
||||
'winddir': winddir,
|
||||
'windgustdir': windgustdir,
|
||||
'windgustmph': windgust,
|
||||
'windspeedmph': windspeed,
|
||||
})
|
||||
log.debug(self.args)
|
||||
|
||||
def publish(self):
|
||||
http = super(Wunderground, self).publish()
|
||||
if not http[2].find(b'success') >= 0:
|
||||
raise PublishException(
|
||||
'Server returned invalid status: %d %s %s' % http)
|
||||
return http
|
||||
|
||||
|
||||
# for legacy support <= v0.8.2, depreciated, do not use
|
||||
Publisher = Wunderground
|
||||
|
||||
# vim: sts=4:ts=4:sw=4
|
1
davisAPI/PyWeather-master/weather/stations/__init__.py
Normal file
1
davisAPI/PyWeather-master/weather/stations/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .davis import *
|
50
davisAPI/PyWeather-master/weather/stations/_struct.py
Normal file
50
davisAPI/PyWeather-master/weather/stations/_struct.py
Normal file
@ -0,0 +1,50 @@
|
||||
"""
|
||||
Binary Data Interfaces
|
||||
|
||||
Abstract:
|
||||
Helper classes for working with binary data structures. This simplifies data
|
||||
field extraction from a binary buffer.
|
||||
|
||||
Author: Patrick C. McGinty (pyweather@tuxcoder.com)
|
||||
Date: 2010-06-025
|
||||
"""
|
||||
|
||||
|
||||
import struct
|
||||
|
||||
|
||||
class Struct(struct.Struct):
|
||||
"""
|
||||
Implements a reusable class for working with a binary data structure. It
|
||||
provides a named fields interface, similar 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 definition.
|
||||
"""
|
||||
def __init__(self, fmt, order='@'):
|
||||
self.fields, fmt_t = list(zip(*fmt))
|
||||
super(Struct, self).__init__(order + ''.join(fmt_t))
|
||||
|
||||
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 dictation of named fields. the
|
||||
fields can be post-processed by extending the _post_unpack() method.
|
||||
"""
|
||||
data = super(Struct, self).unpack_from(buf, offset)
|
||||
items = dict(list(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
|
702
davisAPI/PyWeather-master/weather/stations/davis.py
Normal file
702
davisAPI/PyWeather-master/weather/stations/davis.py
Normal file
@ -0,0 +1,702 @@
|
||||
"""
|
||||
Davis Vantage Pro and Pro2 Service
|
||||
|
||||
Abstract:
|
||||
Allows data query of Davis Vantage Pro and Pro2 devices via serial port
|
||||
interface. The primary implemented serial commands supported are LOOP and
|
||||
DMPAFT.
|
||||
|
||||
The LOOP command can acquire all real-time data points. The DMPAFT command is
|
||||
used to acquire periodic high/low data.
|
||||
|
||||
All data is returned in a dict structure with value/key pairs. Periodic data is
|
||||
only captured once per period. When not active, the keys for periodic data are
|
||||
not present in the results.
|
||||
|
||||
Author: Patrick C. McGinty (pyweather@tuxcoder.com)
|
||||
Date: 2010-06-025
|
||||
|
||||
Original Author: Christopher Blunck (chris@wxnet.org)
|
||||
Date: 2006-03-27
|
||||
"""
|
||||
|
||||
from ._struct import Struct
|
||||
from ..units import *
|
||||
from .station import *
|
||||
|
||||
import logging
|
||||
import serial
|
||||
import struct
|
||||
import time
|
||||
from array import array
|
||||
import datetime as dt
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# public interfaces for module
|
||||
__all__ = ['VantagePro', 'NoDeviceException']
|
||||
|
||||
READ_DELAY = 5
|
||||
BAUD = 19200
|
||||
|
||||
|
||||
def log_raw(msg, raw):
|
||||
log.debug(msg + ': ' + raw.decode())
|
||||
|
||||
|
||||
class NoDeviceException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class NoNewRecordsException(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('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)
|
||||
if crc:
|
||||
log.info("CRC Bad")
|
||||
else:
|
||||
log.debug("CRC OK")
|
||||
return not crc
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
class LoopStruct(Struct):
|
||||
"""
|
||||
For unpacking data structure returned by the 'LOOP' command. this structure
|
||||
contains all 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'), ('ExtraTemps', '7s'),
|
||||
('SoilTemps', '4s'), ('LeafTemps', '4s'), ('HumOut', 'B'),
|
||||
('HumExtra', '7s'), ('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):
|
||||
super(LoopStruct, self).__init__(self.FMT, '=')
|
||||
|
||||
def _post_unpack(self, items):
|
||||
items['Pressure'] = items['Pressure'] / 1000.0
|
||||
items['TempIn'] = items['TempIn'] / 10.0
|
||||
items['TempOut'] = items['TempOut'] / 10.0
|
||||
items['RainRate'] = items['RainRate'] / 100.0
|
||||
items['RainStorm'] = items['RainStorm'] / 100.0
|
||||
items['StormStartDate'] = self._unpack_storm_date(items['StormStartDate'])
|
||||
# rain totals
|
||||
items['RainDay'] = items['RainDay'] / 100.0
|
||||
items['RainMonth'] = items['RainMonth'] / 100.0
|
||||
items['RainYear'] = items['RainYear'] / 100.0
|
||||
# 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)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
class _ArchiveStruct(object):
|
||||
"""
|
||||
common features for both Rev.A and Rev.B structures.
|
||||
"""
|
||||
FMT = None
|
||||
|
||||
def __init__(self):
|
||||
super(_ArchiveStruct, self).__init__(self.FMT, '=')
|
||||
|
||||
def _post_unpack(self, items):
|
||||
vals = self._unpack_date_time(items['DateStamp'], items['TimeStamp'])
|
||||
items.update(zip(('Year', 'Month', 'Day', 'Hour', 'Min'), vals))
|
||||
items['TempOut'] = items['TempOut'] / 10.0
|
||||
items['TempOutHi'] = items['TempOutHi'] / 10.0
|
||||
items['TempOutLow'] = items['TempOutLow'] / 10.0
|
||||
items['Barometer'] = items['Barometer'] / 1000.0
|
||||
items['TempIn'] = items['TempIn'] / 10.0
|
||||
items['UV'] = items['UV'] / 10.0
|
||||
items['UVHi'] = items['UVHi'] / 10.0
|
||||
items['ETHour'] = items['ETHour'] / 1000.0
|
||||
items['SoilTemps'] = tuple(
|
||||
t - 90 for t in struct.unpack('4B', items['SoilTemps']))
|
||||
items['ExtraHum'] = struct.unpack('2B', items['ExtraHum'])
|
||||
items['SoilMoist'] = struct.unpack('4B', items['SoilMoist'])
|
||||
return items
|
||||
|
||||
@staticmethod
|
||||
def _unpack_date_time(date, time_):
|
||||
day = date & 0x1f # 5 bits
|
||||
month = (date >> 5) & 0x0f # 4 bits
|
||||
year = ((date >> 9) & 0x7f) + 2000 # 7 bits
|
||||
hour, min_ = divmod(time_, 100)
|
||||
return year, month, day, hour, min_
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
class _ArchiveAStruct(_ArchiveStruct, Struct):
|
||||
FMT = (
|
||||
('DateStamp', 'H'), ('TimeStamp', 'H'), ('TempOut', 'H'),
|
||||
('TempOutHi', 'H'), ('TempOutLow', 'H'), ('RainRate', 'H'),
|
||||
('RainRateHi', 'H'), ('Pressure', 'H'), ('SolarRad', 'H'),
|
||||
('WindSamps', 'H'), ('TempIn', 'H'), ('HumIn', 'B'),
|
||||
('HumOut', 'B'), ('WindAvg', 'B'), ('WindHi', 'B'),
|
||||
('WindHiDir', 'B'), ('WindAvgDir', 'B'), ('UV', 'B'),
|
||||
('ETHour', 'B'), ('unused', 'B'), ('SoilMoist', '4s'),
|
||||
('SoilTemps', '4s'), ('LeafWetness', '4s'), ('ExtraTemps', '2s'),
|
||||
('ExtraHum', '2s'), ('ReedClosed', 'H'), ('ReedOpened', 'H'),
|
||||
('unused', 'B'),
|
||||
)
|
||||
|
||||
def _post_unpack(self, items):
|
||||
items = super(_ArchiveAStruct, self)._post_unpack(items)
|
||||
items['LeafWetness'] = struct.unpack('4B', items['LeafWetness'])
|
||||
items['ExtraTemps'] = tuple(
|
||||
t - 90 for t in struct.unpack('2B', items['ExtraTemps']))
|
||||
return items
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
class _ArchiveBStruct(_ArchiveStruct, Struct):
|
||||
"""
|
||||
This represents the structure of the Archive Packet (RevB) returned by the station with the DMPAFT command
|
||||
"""
|
||||
FMT = (
|
||||
# These 16 bits hold the date that the archive was written in the following format:
|
||||
# Year (7 bits) | Month (4 bits) | Day (5 bits) or: day + month*32 + (year-2000)*512)
|
||||
('DateStamp', 'H'),
|
||||
# Time on the Vantage that the archive record was
|
||||
# written:
|
||||
# (Hour * 100) + minute.
|
||||
('TimeStamp', 'H'),
|
||||
# Either the Average Outside Temperature, or the
|
||||
# Final Outside Temperature over the archive period.
|
||||
# Units are (F / 10)
|
||||
('TempOut', 'H'),
|
||||
# Highest Outside Temp over the archive period.
|
||||
('TempOutHi', 'H'),
|
||||
# Lowest Outside Temp over the archive period.
|
||||
('TempOutLow', 'H'),
|
||||
# Number of rain clicks over the archive period
|
||||
('RainRate', 'H'),
|
||||
# Highest rain rate over the archive period, or the rate
|
||||
# shown on the console at the end of the period if there
|
||||
# was no rain. Units are (rain clicks / hour)
|
||||
('RainRateHi', 'H'),
|
||||
# Barometer reading at the end of the archive period.
|
||||
# Units are (in Hg / 1000).
|
||||
('Barometer', 'H'),
|
||||
# Average Solar Rad over the archive period.
|
||||
# Units are (Watts / m 2 )
|
||||
('SolarRad', 'H'),
|
||||
# Number of packets containing wind speed data
|
||||
# received from the ISS or wireless anemometer.
|
||||
('WindSamps', 'H'),
|
||||
# Either the Average Inside Temperature, or the Final
|
||||
# Inside Temperature over the archive period. Units
|
||||
# are (F / 10)
|
||||
('TempIn', 'H'),
|
||||
# Inside Humidity at the end of the archive period
|
||||
('HumIn', 'B'),
|
||||
# Outside Humidity at the end of the archive period
|
||||
('HumOut', 'B'),
|
||||
# Average Wind Speed over the archive interval. Units are (MPH)
|
||||
('WindAvg', 'B'),
|
||||
# Highest Wind Speed over the archive interval. Units are (MPH)
|
||||
('WindHi', 'B'),
|
||||
# Direction code of the High Wind speed. 0 = N, 1 = NNE, 2 = NE, ... 14 = NW, 15 = NNW, 255 = Dashed
|
||||
('WindHiDir', 'B'),
|
||||
# Prevailing or Dominant Wind Direction code.
|
||||
# 0 = N, 1 = NNE, 2 = NE, ... 14 = NW, 15 = NNW, 255 = Dashed
|
||||
# Firmware before July 8th 2001 does not report direction code 255
|
||||
('WindAvgDir', 'B'),
|
||||
# Average UV Index. Units are (UV Index / 10)
|
||||
('UV', 'B'),
|
||||
# ET accumulated over the last hour. Only records "on the hour" will have a non-zero value. Units are (in /1000)
|
||||
('ETHour', 'B'),
|
||||
# Highest Solar Rad's value over the archive period. Units are (Watts / m 2)
|
||||
('SolarRadHi', 'H'),
|
||||
# Highest UV Index value over the archive period.
|
||||
('UVHi', 'B'),
|
||||
# Weather forecast rule at the end of the archive period.
|
||||
('ForecastRuleNo', 'B'),
|
||||
# 2 Leaf Temperature values. Units are (F + 90)
|
||||
('LeafTemps', '2s'),
|
||||
# 2 Leaf Wetness values. Range is 0-15
|
||||
('LeafWetness', '2s'),
|
||||
# 4 Soil Temperatures. Units are (F + 90)
|
||||
('SoilTemps', '4s'),
|
||||
# 0xFF = Rev A, 0x00 = Rev B archive record
|
||||
('RecType', 'B'),
|
||||
# 2 Extra Humidity values
|
||||
('ExtraHum', '2s'),
|
||||
# 3 Extra Temperature values. Units are (F + 90)
|
||||
('ExtraTemps', '3s'),
|
||||
# 4 Soil Moisture values. Units are (cb)
|
||||
('SoilMoist', '4s'),
|
||||
)
|
||||
|
||||
def _post_unpack(self, items):
|
||||
items = super(_ArchiveBStruct, self)._post_unpack(items)
|
||||
items['LeafTemps'] = tuple(
|
||||
t - 90 for t in struct.unpack('2B', items['LeafTemps']))
|
||||
items['LeafWetness'] = struct.unpack('2B', items['LeafWetness'])
|
||||
items['ExtraTemps'] = tuple(
|
||||
t - 90 for t in struct.unpack('3B', items['ExtraTemps']))
|
||||
return items
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
# simple data structures
|
||||
DmpStruct = Struct(
|
||||
(('Pages', 'H'), ('Offset', 'H'), ('CRC', 'H')),
|
||||
order='=')
|
||||
|
||||
DmpPageStruct = Struct(
|
||||
(('Index', 'B'), ('Records', '260s'), ('unused', '4B'), ('CRC', 'H')),
|
||||
order='=')
|
||||
|
||||
|
||||
class _TimeStruct(Struct):
|
||||
FMT = (
|
||||
('Sec', 'B'),
|
||||
('Min', 'B'),
|
||||
('Hour', 'B'),
|
||||
('Day', 'B'),
|
||||
('Month', 'B'),
|
||||
('Year', 'B'),
|
||||
('CRC', 'H'),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
super(_TimeStruct, self).__init__(self.FMT, '=')
|
||||
|
||||
def _post_unpack(self, items):
|
||||
items['Year'] = items['Year'] + 1900
|
||||
return items
|
||||
|
||||
|
||||
# init structure classes
|
||||
LoopStruct = LoopStruct()
|
||||
ArchiveAStruct = _ArchiveAStruct()
|
||||
ArchiveBStruct = _ArchiveBStruct()
|
||||
timeStruct = _TimeStruct()
|
||||
|
||||
|
||||
##############################################################################
|
||||
# |--------------------------------------------------------------------------|#
|
||||
# |--------------------------------------------------------------------------|#
|
||||
# | API for the Davis Vantage Pro |#
|
||||
# |--------------------------------------------------------------------------|#
|
||||
# |--------------------------------------------------------------------------|#
|
||||
##############################################################################
|
||||
|
||||
class VantagePro(Station):
|
||||
"""
|
||||
A class capable of reading raw (binary) weather data from a
|
||||
vantage pro console and parsing it into usable scalar
|
||||
(integer/long/real) values.
|
||||
|
||||
The data read from the console is in binary format. The data is in
|
||||
least-ordered nybble strategy, and must be read with correct sizes and
|
||||
offsets for proper byte ordering.
|
||||
"""
|
||||
|
||||
# device reply commands
|
||||
WAKE_ACK = '\n\r'
|
||||
ACK = '\x06'
|
||||
ESC = '\x1b'
|
||||
OK = '\n\rOK\n\r'
|
||||
|
||||
# archive format type, unknown
|
||||
_ARCHIVE_REV_B = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
device,
|
||||
log_interval=5,
|
||||
log_start_date=None,
|
||||
clear=False
|
||||
):
|
||||
"""
|
||||
Initialize the serial connection with the console.
|
||||
:param device: /dev/yourConsoleDevice
|
||||
:param log_interval: default 5
|
||||
:param log_start_date: the datetime.datetime object representing the
|
||||
starting log date. Default None aka "all"
|
||||
:param clear: boolean, if true clean all the log in the console.
|
||||
Default False.
|
||||
"""
|
||||
self.port = serial.Serial(device, BAUD, timeout=READ_DELAY)
|
||||
# set the logging interval to be downloaded. Default all
|
||||
if log_start_date is None:
|
||||
self._archive_time = (0, 0)
|
||||
else:
|
||||
self._archive_time = (self.calcDateStamp(log_start_date),
|
||||
self.calcTimeStamp(log_start_date))
|
||||
|
||||
if clear:
|
||||
self._cmd('CLRLOG') # prevent getting a full log dump at startup
|
||||
self._cmd('SETPER', log_interval, ok=True)
|
||||
|
||||
self.fields = {}
|
||||
|
||||
@staticmethod
|
||||
def calcDateStamp(date):
|
||||
"""
|
||||
As stated into the Vantage Serial Protocol manual, this method converts
|
||||
a datetime object into the right DateStamp
|
||||
|
||||
:param date: the datetime object to convert
|
||||
:return: the dateStamp integer
|
||||
"""
|
||||
return date.day + date.month * 32 + (date.year - 2000) * 512
|
||||
|
||||
@staticmethod
|
||||
def calcTimeStamp(date):
|
||||
"""
|
||||
As stated into the Vantage Serial Protocol manual, this method converts
|
||||
a datetime object into the right TimeStamp.
|
||||
|
||||
:param date: the datetime object to convert
|
||||
:return: the timeStamp integer
|
||||
"""
|
||||
return 100 * date.hour + date.minute
|
||||
|
||||
def __del__(self):
|
||||
"""
|
||||
close serial port when object is deleted.
|
||||
"""
|
||||
self.port.close()
|
||||
|
||||
def _use_rev_b_archive(self, records, offset):
|
||||
"""
|
||||
return True if weather station returns Rev.B archives
|
||||
"""
|
||||
# if pre-determined, return result
|
||||
if type(self._ARCHIVE_REV_B) is bool:
|
||||
return self._ARCHIVE_REV_B
|
||||
# assume, B and check 'RecType' field
|
||||
data = ArchiveBStruct.unpack_from(records, offset)
|
||||
if data['RecType'] == 0:
|
||||
log.info('detected archive rev. B')
|
||||
self._ARCHIVE_REV_B = True
|
||||
else:
|
||||
log.info('detected archive rev. A')
|
||||
self._ARCHIVE_REV_B = False
|
||||
|
||||
return self._ARCHIVE_REV_B
|
||||
|
||||
def _wakeup(self) -> None:
|
||||
"""
|
||||
issue wakeup command to device to take out of standby mode.
|
||||
"""
|
||||
log.info("send: WAKEUP")
|
||||
|
||||
awake, i = False, 0
|
||||
while not awake and i < 3:
|
||||
self.port.write("\n".encode())
|
||||
ack = self.port.read(len(self.WAKE_ACK))
|
||||
if ack.decode() == self.WAKE_ACK:
|
||||
awake = True
|
||||
else:
|
||||
time.sleep(1.2)
|
||||
i += 1
|
||||
|
||||
try:
|
||||
assert awake is True
|
||||
except AssertionError:
|
||||
raise NoDeviceException('Can not access weather station')
|
||||
|
||||
return None
|
||||
|
||||
def _cmd(self, cmd, *args, **kw) -> None:
|
||||
"""
|
||||
write a single command, with variable number of arguments. after the
|
||||
command, the device must return ACK
|
||||
"""
|
||||
ok = kw.setdefault('ok', False)
|
||||
|
||||
self._wakeup()
|
||||
if args:
|
||||
cmd = "%s %s" % (cmd, ' '.join(str(a) for a in args))
|
||||
for i in range(3):
|
||||
log.info("send: " + cmd)
|
||||
self.port.write(f"{cmd} \n".encode())
|
||||
if ok:
|
||||
ack = self.port.read(len(self.OK)) # read OK
|
||||
# log_raw('read', ack)
|
||||
if ack == self.OK:
|
||||
return
|
||||
else:
|
||||
ack = self.port.read(len(self.ACK)) # read ACK
|
||||
# log_raw('read', ack)
|
||||
if ack.decode() == self.ACK:
|
||||
return
|
||||
# raise NoDeviceException('Can not access weather station')
|
||||
|
||||
def _loop_cmd(self):
|
||||
"""
|
||||
Reads a raw string containing data read from the device
|
||||
provided (in /dev/XXX) format. All reads are non-blocking.
|
||||
"""
|
||||
self._cmd('LOOP', 1)
|
||||
raw = self.port.read(LoopStruct.size) # read data
|
||||
return raw
|
||||
|
||||
def _dmpaft_cmd(self, time_fields):
|
||||
"""
|
||||
issue a command to read the archive records after a known time stamp.
|
||||
"""
|
||||
records = []
|
||||
# convert time stamp fields to buffer
|
||||
tbuf = struct.pack('2H', *time_fields)
|
||||
|
||||
# 1. send 'DMPAFT' cmd
|
||||
self._cmd('DMPAFT')
|
||||
|
||||
# 2. send time stamp + crc
|
||||
crc = VProCRC.get(tbuf)
|
||||
crc = struct.pack('>H', crc) # crc in big-endian format
|
||||
self.port.write(tbuf + crc) # send time stamp + crc
|
||||
ack = self.port.read(len(self.ACK)) # read ACK
|
||||
if ack.decode() != self.ACK:
|
||||
return None # if bad ack, return None
|
||||
|
||||
# 3. read pre-amble data
|
||||
raw = self.port.read(DmpStruct.size)
|
||||
if not VProCRC.verify(raw): # check CRC value
|
||||
self.port.write(self.ESC) # if bad, escape and abort
|
||||
return
|
||||
self.port.write(self.ACK.encode()) # send ACK
|
||||
|
||||
# 4. loop through all page records
|
||||
dmp = DmpStruct.unpack(raw)
|
||||
log.info('reading %d pages, start offset %d' %
|
||||
(dmp['Pages'], dmp['Offset']))
|
||||
for i in range(dmp['Pages']):
|
||||
# 5. read page data
|
||||
raw = self.port.read(DmpPageStruct.size)
|
||||
if not VProCRC.verify(raw): # check CRC value
|
||||
self.port.write(self.ESC) # if bad, escape and abort
|
||||
return
|
||||
self.port.write(self.ACK.encode()) # send ACK
|
||||
|
||||
# 6. loop through archive records
|
||||
page = DmpPageStruct.unpack(raw)
|
||||
offset = 0 # assume offset at 0
|
||||
if i == 0:
|
||||
offset = dmp['Offset'] * ArchiveAStruct.size
|
||||
while offset < ArchiveAStruct.size * 5:
|
||||
log.info('page %d, reading record at offset %d' %
|
||||
(page['Index'], offset))
|
||||
if self._use_rev_b_archive(page['Records'], offset):
|
||||
a = ArchiveBStruct.unpack_from(page['Records'], offset)
|
||||
else:
|
||||
a = ArchiveAStruct.unpack_from(page['Records'], offset)
|
||||
# 7. verify that record has valid data, and store
|
||||
if a['DateStamp'] != 0xffff and a['TimeStamp'] != 0xffff:
|
||||
records.append(a)
|
||||
offset += ArchiveAStruct.size
|
||||
log.info('read all pages')
|
||||
return records
|
||||
|
||||
def _get_loop_fields(self):
|
||||
crc_ok = None
|
||||
for i in range(3):
|
||||
raw = self._loop_cmd() # read raw data
|
||||
crc_ok = VProCRC.verify(raw)
|
||||
if crc_ok:
|
||||
break # exit loop if valid
|
||||
time.sleep(1)
|
||||
|
||||
if not crc_ok:
|
||||
raise NoDeviceException('Can not access weather station')
|
||||
|
||||
return LoopStruct.unpack(raw)
|
||||
|
||||
def _get_new_archive_fields(self):
|
||||
"""
|
||||
returns a dictionary of fields from the newest archive record in the
|
||||
device. return None when no records are new.
|
||||
"""
|
||||
records = []
|
||||
for i in range(3):
|
||||
records = self._dmpaft_cmd(self._archive_time)
|
||||
if records is not None:
|
||||
break
|
||||
time.sleep(1)
|
||||
|
||||
if records is None:
|
||||
raise NoNewRecordsException('Can not download any new record.')
|
||||
|
||||
# find the newest record
|
||||
new_rec = None
|
||||
for r in records:
|
||||
new_time = (r['DateStamp'], r['TimeStamp'])
|
||||
if self._archive_time < new_time:
|
||||
self._archive_time = new_time
|
||||
new_rec = r
|
||||
|
||||
return new_rec
|
||||
|
||||
@staticmethod
|
||||
def _calc_derived_fields(fields):
|
||||
"""
|
||||
calculates the derived fields (those fields that are calculated)
|
||||
"""
|
||||
# convenience variables for the calculations below
|
||||
temp_ = fields['TempOut']
|
||||
hum = fields['HumOut']
|
||||
wind_ = fields['WindSpeed']
|
||||
wind10min = fields['WindSpeed10Min']
|
||||
fields['HeatIndex'] = calc_heat_index(temp_, hum)
|
||||
fields['WindChill'] = calc_wind_chill(temp_, wind_, wind10min)
|
||||
fields['DewPoint'] = calc_dewpoint(temp_, hum)
|
||||
# store current data string
|
||||
now = time.localtime()
|
||||
fields['DateStamp'] = time.strftime("%Y-%m-%d %H:%M:%S", now)
|
||||
fields['Year'] = now[0]
|
||||
fields['Month'] = str(now[1]).zfill(2)
|
||||
now = time.gmtime()
|
||||
fields['DateStampUtc'] = time.strftime("%Y-%m-%d %H:%M:%S", now)
|
||||
fields['YearUtc'] = now[0]
|
||||
fields['MonthUtc'] = str(now[1]).zfill(2)
|
||||
|
||||
def parse(self):
|
||||
"""
|
||||
read and parse a set of data read from the console. after the
|
||||
data is parsed it is available in the fields variable.
|
||||
"""
|
||||
fields = self._get_loop_fields()
|
||||
# TODO: this will overwrite the last archived record with the newest record.
|
||||
# Is this the expected behavior?
|
||||
fields['Archive'] = self._get_new_archive_fields()
|
||||
|
||||
self._calc_derived_fields(fields)
|
||||
|
||||
# set the fields variable the values in the dict
|
||||
self.fields = fields
|
||||
|
||||
def get_reading(self) -> WeatherPoint:
|
||||
"""Return a single weather reading."""
|
||||
self.parse()
|
||||
|
||||
return self._fields_to_weather_point(self.fields)
|
||||
|
||||
@staticmethod
|
||||
def _fields_to_weather_point(fields: dict) -> WeatherPoint:
|
||||
"""Convert VantagePro fields dictionary to WeatherPoint.
|
||||
|
||||
Only supports limited subset of data available in self.fields -
|
||||
generally only those useful for posting to weather services.
|
||||
"""
|
||||
return WeatherPoint(
|
||||
temperature_f=fields['TempOut'],
|
||||
pressure=fields['Pressure'],
|
||||
dew_point_f=fields['DewPoint'],
|
||||
humidity=fields['HumOut'],
|
||||
|
||||
rain_rate_in=fields['RainRate'],
|
||||
rain_day_in=fields['RainDay'],
|
||||
time=dt.datetime.strptime(fields['DateStampUtc'], "%Y-%m-%d %H:%M:%S"),
|
||||
wind_speed_mph=fields['WindSpeed10Min'],
|
||||
wind_direction=fields['WindDir'],
|
||||
)
|
74
davisAPI/PyWeather-master/weather/stations/netatmo.py
Normal file
74
davisAPI/PyWeather-master/weather/stations/netatmo.py
Normal file
@ -0,0 +1,74 @@
|
||||
'''Netatmo Weather Station support.
|
||||
|
||||
https://www.netatmo.com/en-eu/weather/weatherstation
|
||||
|
||||
Prep:
|
||||
|
||||
1. [Create a Netatmo app](https://dev.netatmo.com/apps/createanapp#form).
|
||||
2. Use generated app id and secret as `client_id` and `client_secret`.
|
||||
3. Use your own Netatmo username (e-mail) as `username` and `password`.
|
||||
|
||||
Example usage:
|
||||
|
||||
station = NetatmoStation(cliend_id='deadbeef1234567890abcdef',
|
||||
client_secret='123',
|
||||
username='me@example.com',
|
||||
password='correcthorse')
|
||||
|
||||
pprint.pprint(station.get_reading())
|
||||
'''
|
||||
|
||||
import datetime
|
||||
|
||||
from weather.stations.station import *
|
||||
|
||||
import pyatmo
|
||||
|
||||
|
||||
class NetatmoStation(Station):
|
||||
'''Netatmo Weather Station support.
|
||||
|
||||
Implementes as a relatively thin wrapper on top of pyatmo.
|
||||
'''
|
||||
|
||||
def __init__(self, client_id: str, client_secret: str, username: str,
|
||||
password: str, module_name: str = 'Outdoor', **kwargs):
|
||||
'''Initialize and auth Netatmo Weatehr station reader.
|
||||
|
||||
See module docstring for agrs explanation.
|
||||
'''
|
||||
if kwargs:
|
||||
raise ValueError("Unknown params: %s" % ",".join(kwargs.keys()))
|
||||
self.module_name = module_name
|
||||
self._auth = pyatmo.ClientAuth(
|
||||
client_id=client_id,
|
||||
client_secret=client_secret,
|
||||
username=username,
|
||||
password=password)
|
||||
|
||||
def get_reading(self) -> WeatherPoint:
|
||||
'''Return single weather reading.
|
||||
|
||||
Currently only assumes account only has one Netatmo station with
|
||||
one module. Does not support anemometer or rain gauge.
|
||||
'''
|
||||
weatherData = pyatmo.WeatherStationData(self._auth)
|
||||
# We assume there is only one station in the account.
|
||||
station_id = next(iter(weatherData.stations.values()))['_id']
|
||||
module_id = None
|
||||
for module in weatherData.get_modules(station_id).values():
|
||||
if module['module_name'] == self.module_name:
|
||||
module_id = module['id']
|
||||
break
|
||||
if module_id is None:
|
||||
raise ValueError(
|
||||
'Module %s not found, found %s' % (
|
||||
self.module_name,
|
||||
weatherData.get_module_names(station_id)))
|
||||
|
||||
module_data = weatherData.get_module(module_id)
|
||||
|
||||
return WeatherPoint(
|
||||
temperature_c=module_data['dashboard_data']['Temperature'],
|
||||
humidity=module_data['dashboard_data']['Humidity'],
|
||||
time=datetime.datetime.fromtimestamp(module_data['last_message']))
|
104
davisAPI/PyWeather-master/weather/stations/station.py
Normal file
104
davisAPI/PyWeather-master/weather/stations/station.py
Normal file
@ -0,0 +1,104 @@
|
||||
"""Base class for all Weather Station implementations."""
|
||||
|
||||
import datetime
|
||||
import time as time_module
|
||||
|
||||
from ..units.temp import fahrenheit_to_celsius, celsius_to_fahrenheit
|
||||
|
||||
__all__ = ['WeatherPoint', 'Station']
|
||||
|
||||
|
||||
class WeatherPoint:
|
||||
"""
|
||||
Represents a single weather measurement.
|
||||
"""
|
||||
time: datetime.datetime = None
|
||||
|
||||
_temperature_c: float = None # Temperature in Celsius
|
||||
_temperature_f: float = None # Temperature in Fahrenheit
|
||||
humidity: int = None # Relative humidity in percent
|
||||
dew_point_f: float = None # Dew point in Fahrenheit
|
||||
pressure: float = None # Atmospheric pressure
|
||||
|
||||
rain_rate_in: float = None # Rain rate in inches
|
||||
rain_day_in: float = None # Rain inches so far today
|
||||
|
||||
wind_speed_mph: float = None # Wind's speed in miles per hour
|
||||
wind_direction: int = None # Wind's direction, in degrees
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
time=None,
|
||||
temperature_c: float = None,
|
||||
temperature_f: float = None,
|
||||
humidity: int = None,
|
||||
dew_point_f: float = None,
|
||||
pressure: float = None,
|
||||
rain_rate_in: float = None,
|
||||
rain_day_in: float = None,
|
||||
wind_speed_mph: float = None,
|
||||
wind_direction: int = None,
|
||||
):
|
||||
self.time = time or time_module.gmtime()
|
||||
if temperature_c is not None and temperature_f is not None:
|
||||
raise ValueError('Only one of temperature_c and temperature_f can be passed.')
|
||||
if temperature_c is not None:
|
||||
self.temperature_c = temperature_c
|
||||
else:
|
||||
self.temperature_f = temperature_f
|
||||
self.humidity = humidity
|
||||
self.dew_point_f = dew_point_f
|
||||
self.pressure = pressure
|
||||
self.rain_rate_in = rain_rate_in
|
||||
self.rain_day_in = rain_day_in
|
||||
self.wind_speed_mph = wind_speed_mph
|
||||
self.wind_direction = wind_direction
|
||||
|
||||
@property
|
||||
def temperature_f(self) -> float:
|
||||
if self._temperature_f is not None:
|
||||
return self._temperature_f
|
||||
elif self._temperature_c is not None:
|
||||
return celsius_to_fahrenheit(self._temperature_c)
|
||||
|
||||
@temperature_f.setter
|
||||
def temperature_f(self, value: float):
|
||||
self._temperature_f = value
|
||||
self._temperature_c = None
|
||||
|
||||
@property
|
||||
def temperature_c(self) -> float:
|
||||
if self._temperature_c is not None:
|
||||
return self._temperature_c
|
||||
elif self._temperature_f is not None:
|
||||
return fahrenheit_to_celsius(self._temperature_f)
|
||||
|
||||
@temperature_c.setter
|
||||
def temperature_c(self, value: float):
|
||||
self._temperature_f = None
|
||||
self._temperature_c = value
|
||||
|
||||
def __eq__(self, other):
|
||||
return (
|
||||
self.time == other.time and
|
||||
self._temperature_c == other._temperature_c and
|
||||
self._temperature_f == other._temperature_f and
|
||||
self.humidity == other.humidity and
|
||||
self.dew_point_f == other.dew_point_f and
|
||||
self.pressure == other.pressure and
|
||||
self.rain_rate_in == other.rain_rate_in and
|
||||
self.rain_day_in == other.rain_day_in and
|
||||
self.wind_speed_mph == other.wind_speed_mph and
|
||||
self.wind_direction == other.wind_direction
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.__dict__)
|
||||
|
||||
|
||||
class Station:
|
||||
"""Base class for all Weather Station implementations."""
|
||||
|
||||
def get_reading(self) -> WeatherPoint:
|
||||
"""Returns a single weather point."""
|
||||
raise NotImplementedError('Not implemented')
|
116
davisAPI/PyWeather-master/weather/stations/tests/test_davis.py
Normal file
116
davisAPI/PyWeather-master/weather/stations/tests/test_davis.py
Normal file
@ -0,0 +1,116 @@
|
||||
|
||||
|
||||
import codecs
|
||||
import datetime
|
||||
import mock
|
||||
import unittest
|
||||
|
||||
from ..davis import VProCRC, VantagePro, LoopStruct, _fields_to_weather_point
|
||||
from ..station import WeatherPoint
|
||||
|
||||
loop_data = (
|
||||
b"4c4f4f14003e032175da0239d10204056301ffffffffffffffffffff"
|
||||
b"ffffffffff4effffffffffffff0000ffff7f0000ffff000000000000000000000000ffff"
|
||||
b"ffffffffff0000000000000000000000000000000000002703064b26023e070a0d1163")
|
||||
|
||||
|
||||
class TestCRC(unittest.TestCase):
|
||||
|
||||
def test_crc(self):
|
||||
raw = codecs.decode(loop_data, 'hex')
|
||||
result = VProCRC.verify(raw)
|
||||
self.assertTrue(result)
|
||||
|
||||
|
||||
class TestParse(unittest.TestCase):
|
||||
cmd_mock = mock.Mock() # for mocking '_cmd' method in 'vp'
|
||||
loop_mock = mock.Mock() # for mocking '_loop_cmd' method in 'vp'
|
||||
|
||||
def test_unpack_loop_data(self):
|
||||
LoopStruct.unpack(codecs.decode(loop_data, 'hex'))
|
||||
|
||||
@mock.patch.object(VantagePro, '_cmd', cmd_mock)
|
||||
@mock.patch.object(VantagePro, '_loop_cmd', loop_mock)
|
||||
def test_fields(self):
|
||||
self.loop_mock.return_value = codecs.decode(loop_data, 'hex')
|
||||
# TODO: this is working just if there is an actual weather station attached.
|
||||
vp = VantagePro('/dev/ttyUSB0')
|
||||
fields = vp._get_loop_fields()
|
||||
|
||||
self.assertAlmostEqual(fields['Pressure'], 29.98499999)
|
||||
self.assertAlmostEqual(fields['TempIn'], 73.0)
|
||||
self.assertEqual(fields['HumIn'], 57)
|
||||
self.assertAlmostEqual(fields['TempOut'], 72.09999999999)
|
||||
self.assertEqual(fields['WindSpeed'], 4)
|
||||
self.assertEqual(fields['WindSpeed10Min'], 5)
|
||||
self.assertEqual(fields['WindDir'], 355)
|
||||
self.assertEqual(fields['HumOut'], 78)
|
||||
self.assertEqual(fields['RainRate'], 0.0)
|
||||
self.assertEqual(fields['UV'], 0xFF)
|
||||
self.assertEqual(fields['SolarRad'], 0x7FFF)
|
||||
self.assertEqual(fields['RainStorm'], 0)
|
||||
self.assertEqual(fields['StormStartDate'], '2127-15-31')
|
||||
self.assertEqual(fields['RainDay'], 0)
|
||||
self.assertEqual(fields['RainMonth'], 0)
|
||||
self.assertEqual(fields['RainYear'], 0)
|
||||
self.assertEqual(fields['ETDay'], 0)
|
||||
self.assertEqual(fields['ETMonth'], 0)
|
||||
self.assertEqual(fields['ETYear'], 0)
|
||||
self.assertEqual(fields['SoilMoist'], (0xFF, 0xFF, 0xFF, 0xFF))
|
||||
self.assertEqual(fields['LeafWetness'], (0xFF, 0xFF, 0xFF, 0))
|
||||
self.assertEqual(fields['BatteryStatus'], 0)
|
||||
self.assertAlmostEqual(fields['BatteryVolts'], 4.728515625)
|
||||
self.assertEqual(fields['ForecastIcon'], 6)
|
||||
self.assertEqual(fields['ForecastRuleNo'], 75)
|
||||
self.assertEqual(fields['SunRise'], '05:50')
|
||||
self.assertEqual(fields['SunSet'], '18:54')
|
||||
|
||||
@mock.patch.object(VantagePro, '_cmd', cmd_mock)
|
||||
@mock.patch.object(VantagePro, '_loop_cmd', loop_mock)
|
||||
def test_derived_fields(self):
|
||||
self.loop_mock.return_value = codecs.decode(loop_data, 'hex')
|
||||
# TODO same as todo above!
|
||||
vp = VantagePro('/dev/ttyUSB0')
|
||||
fields = vp._get_loop_fields()
|
||||
vp._calc_derived_fields(fields)
|
||||
|
||||
self.assertAlmostEqual(fields['HeatIndex'], 72.09999999)
|
||||
self.assertAlmostEqual(fields['WindChill'], 74.17574285)
|
||||
self.assertAlmostEqual(fields['DewPoint'], 64.97343800)
|
||||
self.assertNotEqual(fields['DateStamp'], '')
|
||||
self.assertTrue(fields['Year'] > 2000)
|
||||
self.assertTrue(1 <= int(fields['Month']) <= 12)
|
||||
self.assertNotEqual(fields['DateStampUtc'], '')
|
||||
self.assertTrue(fields['YearUtc'] > 2000)
|
||||
self.assertTrue(1 <= int(fields['MonthUtc']) <= 12)
|
||||
|
||||
|
||||
class TestFieldsToWeatherPoint(unittest.TestCase):
|
||||
|
||||
def test_fields_to_weather_point(self):
|
||||
fields = {
|
||||
'TempOut': 87,
|
||||
'Pressure': 29.9,
|
||||
'DewPoint': 80,
|
||||
'HumOut': 56,
|
||||
'RainRate': 0.1,
|
||||
'RainDay': 0.2,
|
||||
'DateStampUtc': '2020-01-02 03:04:05',
|
||||
'WindSpeed10Min': 5,
|
||||
'WindDir': 15
|
||||
}
|
||||
expected = WeatherPoint(
|
||||
time=datetime.datetime(2020, 1, 2, 3, 4, 5),
|
||||
temperature_f=87,
|
||||
humidity=56,
|
||||
dew_point_f=80,
|
||||
pressure=29.9,
|
||||
rain_rate_in=0.1,
|
||||
rain_day_in=0.2,
|
||||
wind_speed_mph=5,
|
||||
wind_direction=15
|
||||
)
|
||||
self.assertEqual(_fields_to_weather_point(fields), expected)
|
||||
|
||||
|
||||
# vim: sts=4:ts=4:sw=4
|
@ -0,0 +1,17 @@
|
||||
'''Tests for the station module.'''
|
||||
|
||||
import unittest
|
||||
|
||||
from ..station import WeatherPoint
|
||||
|
||||
|
||||
class WeatherPointTest(unittest.TestCase):
|
||||
|
||||
def test_temperature_conversion(self):
|
||||
w_f = WeatherPoint(temperature_f=80)
|
||||
self.assertEqual(w_f.temperature_f, 80)
|
||||
self.assertAlmostEqual(w_f.temperature_c, 26.666688)
|
||||
|
||||
w_c = WeatherPoint(temperature_c=21)
|
||||
self.assertEqual(w_c.temperature_c, 21)
|
||||
self.assertAlmostEqual(w_c.temperature_f, 69.8)
|
28
davisAPI/PyWeather-master/weather/stations/validate.py
Normal file
28
davisAPI/PyWeather-master/weather/stations/validate.py
Normal file
@ -0,0 +1,28 @@
|
||||
|
||||
class Validator:
|
||||
def __init__(self, fields):
|
||||
self.fields = fields
|
||||
|
||||
def get_value(self, field, default):
|
||||
return self.fields.get(field, default)
|
||||
|
||||
def validate(self):
|
||||
assert 0 <= self.get_value('HumOut', -1) <= 100
|
||||
assert 0 <= self.get_value('HumIn', -1) <= 100
|
||||
assert -120 <= self.get_value('DewPoint', -1) <= 254
|
||||
|
||||
assert -20 <= self.get_value('TempIn', -255) <= 254
|
||||
assert -120 <= self.get_value('TempOut', -255) <= 254
|
||||
assert -254 <= self.get_value('WindChill', -255) <= 254
|
||||
assert -120 <= self.get_value('HeatIndex', -255) <= 254
|
||||
|
||||
assert 0 <= self.get_value('RainYear', -1) <= 254
|
||||
assert 0 <= self.get_value('RainMonth', -1) <= 254
|
||||
assert 0 <= self.get_value('RainDay', -1) <= 254
|
||||
assert 0 <= self.get_value('RainStorm', -1) <= 254
|
||||
|
||||
assert 0 <= self.get_value('WindSpeed', -1) <= 200
|
||||
assert 0 <= self.get_value('WindSpeed10Min', -1) <= 200
|
||||
assert 0 <= self.get_value('WindDir', -1) <= 359
|
||||
|
||||
assert 26.00 <= self.get_value('Pressure', -1) <= 34.00
|
4
davisAPI/PyWeather-master/weather/units/__init__.py
Normal file
4
davisAPI/PyWeather-master/weather/units/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
from .temp import *
|
||||
from .precip import *
|
||||
from .wind import *
|
||||
from .pressure import *
|
103
davisAPI/PyWeather-master/weather/units/astro.py
Normal file
103
davisAPI/PyWeather-master/weather/units/astro.py
Normal file
@ -0,0 +1,103 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
from math import *
|
||||
|
||||
# 90 degrees 50 minutes
|
||||
zenith = -0.01454
|
||||
|
||||
|
||||
def radians_to_degrees(radians):
|
||||
return radians * (180 / pi)
|
||||
|
||||
def degrees_to_radians(degrees):
|
||||
return (degrees * pi) / 180
|
||||
|
||||
|
||||
def _daylight(n, t, lat, long, tz, day, month, year, rise=1):
|
||||
lngHour = int / 15.0
|
||||
|
||||
# calculate the Sun's mean anomaly
|
||||
m = (0.9856 * t) - 3.289
|
||||
|
||||
# calculate the Sun's true longitude
|
||||
opA = 1.916 * sin(radians(m))
|
||||
opB = 0.020 * sin(2 * radians(m))
|
||||
l = m + opA + opB + 282.634
|
||||
if l > 360:
|
||||
l = l - 360
|
||||
if l < 0:
|
||||
l = l + 360
|
||||
|
||||
# calculate the Sun's right ascension
|
||||
foo = 0.91764 * tan(degrees_to_radians(l))
|
||||
ra = radians_to_degrees(atan(foo))
|
||||
|
||||
# right ascension value needs to be in the same quadrant as l
|
||||
lQuandrant = (floor(l / 90)) * 90
|
||||
raQuandrant = (floor(ra / 90)) * 90
|
||||
ra = ra + (lQuandrant - raQuandrant)
|
||||
|
||||
# right ascension value needs to converted to hours
|
||||
ra = ra / 15
|
||||
|
||||
# calculate the Sun's declination
|
||||
sinDec = 0.39782 * sin(radians(l))
|
||||
cosDec = cos(asin(sinDec))
|
||||
|
||||
# calculate the Sun's local hour angle
|
||||
numerator = sinDec * sin(radians(lat))
|
||||
denomenator = cosDec * cos(radians(lat))
|
||||
cosH = (zenith - numerator) / denomenator
|
||||
|
||||
if (cosH > 1) or (cosH < -1):
|
||||
# the sun don't shine here son (on the specified date)
|
||||
return None
|
||||
|
||||
# finish calulating h and convert into hours
|
||||
if rise:
|
||||
h = 360 - radians_to_degrees(acos(cosH))
|
||||
else:
|
||||
h = radians_to_degrees(acos(cosH))
|
||||
h = h / 15
|
||||
|
||||
# calculate local mean time of rising / setting
|
||||
T = h + ra - (0.06571 * t) - 6.622
|
||||
|
||||
# adjust back to UTC
|
||||
UT = T - lngHour
|
||||
|
||||
# convert UT value to local time zone of lat/long
|
||||
localT = UT + tz
|
||||
|
||||
hour = int(localT)
|
||||
min = 60 * (localT % hour)
|
||||
if hour < 0:
|
||||
hour = 24 + hour - 1
|
||||
if min < 0:
|
||||
min = 60 + min
|
||||
return (year, month, day, hour, min, 0, 0, 0, 0)
|
||||
|
||||
|
||||
def daylight(lat, long, tz, day, month, year):
|
||||
# calc the day of the year
|
||||
n1 = floor(275 * month / 9)
|
||||
n2 = floor((month + 9) / 12)
|
||||
n3 = (1 + floor((year - 4 * floor(year / 4) + 2) / 3))
|
||||
n = n1 - (n2 * n3) + day - 30
|
||||
|
||||
# convert the long to hour value and calc an approximate time
|
||||
lngHour = int / 15.0
|
||||
|
||||
t = n + ((6 - lngHour) / 24.0)
|
||||
sunrise = _daylight(n, t, lat, int, tz, day, month, year, 1)
|
||||
|
||||
t = n + ((18 - lngHour) / 24.0)
|
||||
sunset = _daylight(n, t, lat, int, tz, day, month, year, 0)
|
||||
|
||||
return (sunrise, sunset)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sunrise, sunset = daylight(40.9, -74.3, -4, 14, 4, 2003)
|
||||
print('sunrise is ' + str(sunrise))
|
||||
print('sunset is ' + str(sunset))
|
21
davisAPI/PyWeather-master/weather/units/precip.py
Normal file
21
davisAPI/PyWeather-master/weather/units/precip.py
Normal file
@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
#
|
||||
# Precipitation conversions
|
||||
#
|
||||
# -Christopher Blunck
|
||||
#
|
||||
|
||||
import sys
|
||||
|
||||
__author__ = 'Christopher Blunck'
|
||||
__email__ = 'chris@wxnet.org'
|
||||
__revision__ = '$Revision: 1.6 $'
|
||||
|
||||
__doc__ = '''
|
||||
precipitation related conversions
|
||||
'''
|
||||
|
||||
__usage__ = ''
|
||||
|
||||
|
209
davisAPI/PyWeather-master/weather/units/pressure.py
Normal file
209
davisAPI/PyWeather-master/weather/units/pressure.py
Normal file
@ -0,0 +1,209 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
#
|
||||
# See __doc__ for an explanation of what this module does
|
||||
#
|
||||
# See __usage__ for an explanation of runtime arguments.
|
||||
#
|
||||
# -Christopher Blunck
|
||||
#
|
||||
|
||||
|
||||
__author__ = 'Christopher Blunck'
|
||||
__email__ = 'chris@wxnet.org'
|
||||
__revision__ = '$Revision: 1.6 $'
|
||||
|
||||
__doc__ = 'pressure related conversion functions'
|
||||
__usage__ = 'this module should not be run via the command line'
|
||||
|
||||
|
||||
def atm_to_in32(atm):
|
||||
"""Atmospheres (atm) to inches of mercury @32F (inHg32)"""
|
||||
return atm * 29.9213
|
||||
|
||||
|
||||
def atm_to_in60(atm):
|
||||
"""Atmospheres (atm) to inches of mercury @60F (inHg60)"""
|
||||
return atm * 30.0058
|
||||
|
||||
|
||||
def atm_to_mb(atm):
|
||||
"""Atmospheres (atm) to millibars (mb)"""
|
||||
return atm * 1013.25
|
||||
|
||||
|
||||
def atm_to_pa(atm):
|
||||
"""Atmospheres (atm) to pascals (Pa)"""
|
||||
return atm * 101325
|
||||
|
||||
|
||||
def atm_to_lb_sqin(atm):
|
||||
"""Atmospheres (atm) to pounds/square inch (lb/in**2)"""
|
||||
return atm * 14.696
|
||||
|
||||
|
||||
def in32_to_mb(inches):
|
||||
"""Inches of mercury @32F (inHg32) to millibars (mb)"""
|
||||
return inches * 33.8639
|
||||
|
||||
|
||||
def in32_to_atm(inches):
|
||||
"""Inches of mercury @32F (inHg32) to millibars (mb)"""
|
||||
return inches * 0.0334211
|
||||
|
||||
|
||||
def in32_to_lbs(inches):
|
||||
"""Inches of mercury @32F (inHg32) to pounds/square inch (lb/in**2)"""
|
||||
return inches * 0.49115
|
||||
|
||||
|
||||
def in60_to_mb(inches):
|
||||
"""Inches of mercury @60F (inHg60) to atmospheres (atm)"""
|
||||
return inches * 33.7685
|
||||
|
||||
|
||||
def in60_to_atm(inches):
|
||||
"""Inches of mercury @60F (inHg60) to millibars (mb)"""
|
||||
return inches * 0.0333269
|
||||
|
||||
|
||||
def in60_to_lbs(inches):
|
||||
"""Inches of mercury @60F (inHg60) to pounds/square inch (lb/in**2)"""
|
||||
return inches * 0.48977
|
||||
|
||||
|
||||
def incConv_to_Pa(inches):
|
||||
"""
|
||||
Inches of mercury to Pascals using the NIST conventional coefficient
|
||||
:param inches: inches of mg
|
||||
:return: pascals
|
||||
"""
|
||||
return inches * 3.386389
|
||||
|
||||
|
||||
def incConv_to_kPa(inches):
|
||||
"""
|
||||
Inches of mercury to kilo Pascals using the NIST conventional coefficient
|
||||
:param inches: inches of mg
|
||||
:return: pascals
|
||||
"""
|
||||
return incConv_to_Pa(inches) * 1000
|
||||
|
||||
|
||||
def mb_to_atm(mb):
|
||||
"""Millibars (mb) to atmospheres (atm)"""
|
||||
return mb * 0.000986923
|
||||
|
||||
|
||||
def mb_to_hpa(mb):
|
||||
"""Millibars (mb) to hectopascals (hPa)"""
|
||||
return mb
|
||||
|
||||
|
||||
def mb_to_in32(mb):
|
||||
"""Millibars (mb) to inches of mercury @32F (inHg60)"""
|
||||
return mb * 0.02953
|
||||
|
||||
|
||||
def mb_to_in60(mb):
|
||||
"""Millibars (mb) to inches of mercury @60F (inHg60)"""
|
||||
return mb * 0.02961
|
||||
|
||||
|
||||
def mb_to_kpa(mb):
|
||||
"""Millibars (mb) to kilopascals (kPa)"""
|
||||
return mb * 0.1
|
||||
|
||||
|
||||
def mb_to_mm32(mb):
|
||||
"""Millibars (mb) to millimeters of mercury @32F (mmHg)"""
|
||||
return mb * 0.75006
|
||||
|
||||
|
||||
def mb_to_mm60(mb):
|
||||
"""Millibars (mb) to millimeters of mercury @60F (mmHg)"""
|
||||
return mb * 0.75218
|
||||
|
||||
|
||||
def mb_to_n_sqm(mb):
|
||||
"""Millibars (mb) to newtons/square meter (N/m**2)"""
|
||||
return mb * 100
|
||||
|
||||
|
||||
def mb_to_pa(mb):
|
||||
"""Millibars (mb) to pascals (Pa)"""
|
||||
return mb * 100
|
||||
|
||||
|
||||
def mb_to_lb_sqft(mb):
|
||||
"""Millibars (mb) to pounds/square foot (lb/ft**2)"""
|
||||
return mb * 2.088543
|
||||
|
||||
|
||||
def mb_to_lb_sqin(mb):
|
||||
"""Millibars (mb) to pounds/square inch (lb/in**2)"""
|
||||
return mb * 0.0145038
|
||||
|
||||
|
||||
def mm32_to_mb(mm32):
|
||||
"""Millimeters of mercury @32F (mmHg) to millibars (mb)"""
|
||||
return mm32 * 1.33322
|
||||
|
||||
|
||||
def mm60_to_mb(mm60):
|
||||
"""Millimeters of mercury @60F (mmHg) to millibars (mb)"""
|
||||
return mm60 * 1.32947
|
||||
|
||||
|
||||
def n_sqm_to_mb(nsqm):
|
||||
"""Newtons/square meter (N/m**2) to millibars (mb)"""
|
||||
return nsqm * 0.01
|
||||
|
||||
|
||||
def pa_to_atm(pa):
|
||||
"""Pascals (Pa) to atmospheres (atm)"""
|
||||
return pa * 0.000009869
|
||||
|
||||
|
||||
def pa_to_mb(pa):
|
||||
"""Pascals (Pa) to millibars (mb)"""
|
||||
return pa * 0.01
|
||||
|
||||
|
||||
def hpa_to_mb(hpa):
|
||||
"""Hectopascals (hPa) to millibars (mb)"""
|
||||
return hpa
|
||||
|
||||
|
||||
def kpa_to_mb(hpa):
|
||||
"""Kilopascals (kPa) to millibars (mb)"""
|
||||
return hpa * 10
|
||||
|
||||
|
||||
def lb_sqft_to_mb(lbs):
|
||||
"""Pounds/square foot (lb/ft**2) to millibars (mb)"""
|
||||
return lbs * 0.478803
|
||||
|
||||
|
||||
def lb_sqin_to_atm(lbs):
|
||||
"""Pounds/square inch (lb/in**2) to atmospheres (atm)"""
|
||||
return lbs * 0.068046
|
||||
|
||||
|
||||
def lb_sqin_to_mm32(lbs):
|
||||
"""Pounds/square inch (lb/in**2) to inches of mercury @32F (inHg32)"""
|
||||
return lbs * 2.03602
|
||||
|
||||
|
||||
def lb_sqin_to_mm60(lbs):
|
||||
"""Pounds/square inch (lb/in**2) to inches of mercury @60F (inHg60)"""
|
||||
return lbs * 2.04177
|
||||
|
||||
|
||||
def lb_sqin_to_mb(lbs):
|
||||
"""Pounds/square inch (lb/in**2) to millibars (mb)"""
|
||||
return lbs * 68.9474483
|
||||
|
||||
|
||||
def hpa_to_inches(hpa):
|
||||
return hpa / 33.87
|
158
davisAPI/PyWeather-master/weather/units/temp.py
Normal file
158
davisAPI/PyWeather-master/weather/units/temp.py
Normal file
@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
#
|
||||
# See __doc__ for an explanation of what this module does
|
||||
#
|
||||
# See __usage__ for an explanation of runtime arguments.
|
||||
#
|
||||
# -Christopher Blunck
|
||||
#
|
||||
|
||||
import math
|
||||
import sys
|
||||
|
||||
__author__ = 'Christopher Blunck'
|
||||
__email__ = 'chris@wxnet.org'
|
||||
__revision__ = '$Revision: 1.6 $'
|
||||
|
||||
__doc__ = 'temperature related conversion functions'
|
||||
__usage__ = 'this module should not be run via the command line'
|
||||
|
||||
|
||||
def celsius_to_fahrenheit(c):
|
||||
"""Degrees Celsius (C) to degrees Fahrenheit (F)"""
|
||||
return (c * 1.8) + 32.0
|
||||
|
||||
|
||||
def celsius_to_kelvin(c):
|
||||
"""Degrees Celsius (C) to degrees Kelvin (K)"""
|
||||
return c + 273.15
|
||||
|
||||
|
||||
def celsius_to_rankine(c):
|
||||
"""Degrees Celsius (C) to degrees Rankine (R)"""
|
||||
return (c * 1.8) + 491.67
|
||||
|
||||
|
||||
def fahrenheit_to_celsius(f):
|
||||
"""Degrees Fahrenheit (F) to degrees Celsius (C)"""
|
||||
return (f - 32.0) * 0.555556
|
||||
|
||||
|
||||
def fahrenheit_to_kelvin(f):
|
||||
"""Degrees Fahrenheit (F) to degrees Kelvin (K)"""
|
||||
return (f * 0.555556) + 255.37
|
||||
|
||||
|
||||
def fahrenheit_to_rankine(f):
|
||||
"""Degrees Fahrenheit (F) to degrees Rankine (R)"""
|
||||
return f + 459.67
|
||||
|
||||
|
||||
def kelvin_to_celsius(k):
|
||||
"""Degrees Kelvin (K) to degrees Celsius (C)"""
|
||||
return k - 273.15
|
||||
|
||||
|
||||
def kelvin_to_fahrenheit(k):
|
||||
"""Degrees Kelvin (K) to degrees Fahrenheit (F)"""
|
||||
return (k - 255.37) * 1.8
|
||||
|
||||
|
||||
def kelvin_to_rankine(k):
|
||||
"""Degrees Kelvin (K) to degrees Rankine (R)"""
|
||||
return k * 1.8
|
||||
|
||||
|
||||
def rankine_to_celsius(r):
|
||||
"""Degrees Rankine (R) to degrees Celsius (C)"""
|
||||
return (r - 491.67) * 0.555556
|
||||
|
||||
|
||||
def rankine_to_fahrenheit(r):
|
||||
"""Degrees Rankine (R) to degrees Fahrenheit (F)"""
|
||||
return r - 459.67
|
||||
|
||||
|
||||
def rankine_to_kelvin(r):
|
||||
"""Degrees Rankine (R) to degrees Kelvin (K)"""
|
||||
return r * 0.555556
|
||||
|
||||
|
||||
def calc_heat_index(temp, hum):
|
||||
"""
|
||||
calculates the heat index based upon temperature (in F) and humidity.
|
||||
http://www.srh.noaa.gov/bmx/tables/heat_index.html
|
||||
|
||||
returns the heat index in degrees F.
|
||||
"""
|
||||
|
||||
if temp < 80:
|
||||
return temp
|
||||
else:
|
||||
return -42.379 + 2.04901523 * temp + 10.14333127 * hum - 0.22475541 * \
|
||||
temp * hum - 6.83783 * (10 ** -3) * (temp ** 2) - 5.481717 * \
|
||||
(10 ** -2) * (hum ** 2) + 1.22874 * (10 ** -3) * (temp ** 2) * \
|
||||
hum + 8.5282 * (10 ** -4) * temp * (hum ** 2) - 1.99 * \
|
||||
(10 ** -6) * (temp ** 2) * (hum ** 2)
|
||||
|
||||
|
||||
def calc_wind_chill(t, windspeed, windspeed10min=None):
|
||||
"""
|
||||
calculates the wind chill value based upon the temperature (F) and
|
||||
wind.
|
||||
|
||||
returns the wind chill in degrees F.
|
||||
"""
|
||||
|
||||
w = max(windspeed10min or 0, windspeed)
|
||||
return 35.74 + 0.6215 * t - 35.75 * (w ** 0.16) + 0.4275 * t * (w ** 0.16)
|
||||
|
||||
|
||||
def calc_humidity(temp, dewpoint):
|
||||
"""
|
||||
calculates the humidity via the formula from weatherwise.org
|
||||
return the relative humidity
|
||||
"""
|
||||
|
||||
t = fahrenheit_to_celsius(temp)
|
||||
td = fahrenheit_to_celsius(dewpoint)
|
||||
|
||||
num = 112 - (0.1 * t) + td
|
||||
denom = 112 + (0.9 * t)
|
||||
|
||||
rh = math.pow((num / denom), 8)
|
||||
|
||||
return rh
|
||||
|
||||
|
||||
def calc_dewpoint(temp, hum):
|
||||
"""
|
||||
calculates the dewpoint via the formula from weatherwise.org
|
||||
return the dewpoint in degrees F.
|
||||
"""
|
||||
|
||||
c = fahrenheit_to_celsius(temp)
|
||||
x = 1 - 0.01 * hum
|
||||
|
||||
dewpoint = (14.55 + 0.114 * c) * x
|
||||
dewpoint = dewpoint + ((2.5 + 0.007 * c) * x) ** 3
|
||||
dewpoint = dewpoint + (15.9 + 0.117 * c) * x ** 14
|
||||
dewpoint = c - dewpoint
|
||||
|
||||
return celsius_to_fahrenheit(dewpoint)
|
||||
|
||||
|
||||
def calc_dewpoint_davis(temp, hum):
|
||||
'''
|
||||
calculate the dewpoint via the formula used by Davis. See Davis Application Note 28 - Derived Variables in Davis
|
||||
Weather Products.
|
||||
:author: Paolo Bellagente (p.bellagente@unibs.it - 23/03/2017
|
||||
:param temp: Outside temperature in F
|
||||
:param hum: Relative outside humidity
|
||||
:return: the dewpoint in F
|
||||
'''
|
||||
v = hum * 0.01 * 6.112 * math.exp((17.62 * temp) / (temp + 243.12))
|
||||
n = 243.12 * (math.log(v)) - 440.1
|
||||
d = 19.43 - math.log(v)
|
||||
return n / d
|
302
davisAPI/PyWeather-master/weather/units/tests/test_pressure.py
Normal file
302
davisAPI/PyWeather-master/weather/units/tests/test_pressure.py
Normal file
@ -0,0 +1,302 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
#
|
||||
# Unit Tests for pressure module
|
||||
#
|
||||
# See __usage__ for an explanation of runtime arguments.
|
||||
#
|
||||
# -Christopher Blunck
|
||||
#
|
||||
|
||||
|
||||
import unittest
|
||||
|
||||
from ..pressure import *
|
||||
|
||||
__author__ = 'Christopher Blunck'
|
||||
__email__ = 'chris@wxnet.org'
|
||||
__revision__ = '$Revision: 1.6 $'
|
||||
|
||||
__doc__ = '''
|
||||
Unit tests the pressure module.
|
||||
'''
|
||||
|
||||
__usage__ = '''
|
||||
python $0
|
||||
'''
|
||||
|
||||
|
||||
def usage():
|
||||
print(__usage__)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class TestCase(unittest.TestCase):
|
||||
def setUp(self): pass
|
||||
|
||||
def tearDown(self): pass
|
||||
|
||||
def test__atm_to_in32(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(atm_to_in32(1.0364), 4) == 31.0104, "value not correct"
|
||||
assert round(atm_to_in32(1.0000), 4) == 29.9213, "value not correct"
|
||||
assert round(atm_to_in32(0.9268), 4) == 27.7311, "value not correct"
|
||||
assert round(atm_to_in32(0.8883), 4) == 26.5791, "value not correct"
|
||||
|
||||
def test__atm_to_in60(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(atm_to_in60(1.0364), 4) == 31.0980, "value not correct"
|
||||
assert round(atm_to_in60(1.0000), 4) == 30.0058, "value not correct"
|
||||
assert round(atm_to_in60(0.9268), 4) == 27.8094, "value not correct"
|
||||
assert round(atm_to_in60(0.8883), 4) == 26.6542, "value not correct"
|
||||
|
||||
def test__atm_to_mb(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(atm_to_mb(1.0364), 4) == 1050.1323, "value not correct"
|
||||
assert round(atm_to_mb(1.0000), 4) == 1013.2500, "value not correct"
|
||||
assert round(atm_to_mb(0.9268), 4) == 939.0801, "value not correct"
|
||||
assert round(atm_to_mb(0.8883), 4) == 900.0700, "value not correct"
|
||||
|
||||
def test__atm_to_pa(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(atm_to_pa(1.0364), 4) == 105013.2300, "value not correct"
|
||||
assert round(atm_to_pa(1.0000), 4) == 101325.0000, "value not correct"
|
||||
assert round(atm_to_pa(0.9268), 4) == 93908.0100, "value not correct"
|
||||
assert round(atm_to_pa(0.8883), 4) == 90006.9975, "value not correct"
|
||||
|
||||
def test__atm_to_lb_sqin(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(atm_to_lb_sqin(1.0364), 4) == 15.2309, "value not correct"
|
||||
assert round(atm_to_lb_sqin(1.0000), 4) == 14.6960, "value not correct"
|
||||
assert round(atm_to_lb_sqin(0.9268), 4) == 13.6203, "value not correct"
|
||||
assert round(atm_to_lb_sqin(0.8883), 4) == 13.0545, "value not correct"
|
||||
|
||||
def test__in32_to_mb(self):
|
||||
# make sure some hard-coded values work
|
||||
assert int(in32_to_mb(31.01)) == 1050, "value not correct"
|
||||
assert int(in32_to_mb(29.92)) == 1013, "value not correct"
|
||||
assert int(in32_to_mb(27.73)) == 939, "value not correct"
|
||||
assert int(in32_to_mb(26.58)) == 900, "value not correct"
|
||||
|
||||
def test__in32_to_atm(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(in32_to_atm(31.01), 4) == 1.0364, "value not correct"
|
||||
assert round(in32_to_atm(29.92), 4) == 1.0000, "value not correct"
|
||||
assert round(in32_to_atm(27.73), 4) == 0.9268, "value not correct"
|
||||
assert round(in32_to_atm(26.58), 4) == 0.8883, "value not correct"
|
||||
|
||||
def test__in32_to_lbs(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(in32_to_lbs(31.01), 4) == 15.2306, "value not correct"
|
||||
assert round(in32_to_lbs(29.92), 4) == 14.6952, "value not correct"
|
||||
assert round(in32_to_lbs(27.73), 4) == 13.6196, "value not correct"
|
||||
assert round(in32_to_lbs(26.58), 4) == 13.0548, "value not correct"
|
||||
|
||||
def test__in60_to_mb(self):
|
||||
# make sure some hard-coded values work
|
||||
assert int(in60_to_mb(31.01)) == 1047, "value not correct"
|
||||
assert int(in60_to_mb(29.92)) == 1010, "value not correct"
|
||||
assert int(in60_to_mb(27.73)) == 936, "value not correct"
|
||||
assert int(in60_to_mb(26.58)) == 897, "value not correct"
|
||||
|
||||
def test__in60_to_atm(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(in60_to_atm(31.01), 4) == 1.0335, "value not correct"
|
||||
assert round(in60_to_atm(29.92), 4) == 0.9971, "value not correct"
|
||||
assert round(in60_to_atm(27.73), 4) == 0.9242, "value not correct"
|
||||
assert round(in60_to_atm(26.58), 4) == 0.8858, "value not correct"
|
||||
|
||||
def test__in60_to_lbs(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(in60_to_lbs(31.01), 4) == 15.1878, "value not correct"
|
||||
assert round(in60_to_lbs(29.92), 4) == 14.6539, "value not correct"
|
||||
assert round(in60_to_lbs(27.73), 4) == 13.5813, "value not correct"
|
||||
assert round(in60_to_lbs(26.58), 4) == 13.0181, "value not correct"
|
||||
|
||||
def test__mb_to_atm(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(mb_to_atm(1050), 4) == 1.0363, "value not correct"
|
||||
assert round(mb_to_atm(1013), 4) == 0.9998, "value not correct"
|
||||
assert round(mb_to_atm(939), 4) == 0.9267, "value not correct"
|
||||
assert round(mb_to_atm(900), 4) == 0.8882, "value not correct"
|
||||
|
||||
def test__mb_to_hpa(self):
|
||||
# make sure some hard-coded values work
|
||||
assert mb_to_hpa(1050) == 1050, "value not correct"
|
||||
assert mb_to_hpa(1013) == 1013, "value not correct"
|
||||
assert mb_to_hpa(939) == 939, "value not correct"
|
||||
assert mb_to_hpa(900) == 900, "value not correct"
|
||||
|
||||
def test__mb_to_in32(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(mb_to_in32(1050), 4) == 31.0065, "value not correct"
|
||||
assert round(mb_to_in32(1013), 4) == 29.9139, "value not correct"
|
||||
assert round(mb_to_in32(939), 4) == 27.7287, "value not correct"
|
||||
assert round(mb_to_in32(900), 4) == 26.5770, "value not correct"
|
||||
|
||||
def test__mb_to_in60(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(mb_to_in60(1050), 4) == 31.0905, "value not correct"
|
||||
assert round(mb_to_in60(1013), 4) == 29.9949, "value not correct"
|
||||
assert round(mb_to_in60(939), 4) == 27.8038, "value not correct"
|
||||
assert round(mb_to_in60(900), 4) == 26.6490, "value not correct"
|
||||
|
||||
def test__mb_to_kpa(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(mb_to_kpa(1050), 4) == 105.0000, "value not correct"
|
||||
assert round(mb_to_kpa(1013), 4) == 101.3000, "value not correct"
|
||||
assert round(mb_to_kpa(939), 4) == 93.9000, "value not correct"
|
||||
assert round(mb_to_kpa(900), 4) == 90.0000, "value not correct"
|
||||
|
||||
def test__mb_to_mm32(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(mb_to_mm32(1050), 4) == 787.5630, "value not correct"
|
||||
assert round(mb_to_mm32(1013), 4) == 759.8108, "value not correct"
|
||||
assert round(mb_to_mm32(939), 4) == 704.3063, "value not correct"
|
||||
assert round(mb_to_mm32(900), 4) == 675.0540, "value not correct"
|
||||
|
||||
def test__mb_to_mm60(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(mb_to_mm60(1050), 4) == 789.7890, "value not correct"
|
||||
assert round(mb_to_mm60(1013), 4) == 761.9583, "value not correct"
|
||||
assert round(mb_to_mm60(939), 4) == 706.2970, "value not correct"
|
||||
assert round(mb_to_mm60(900), 4) == 676.9620, "value not correct"
|
||||
|
||||
def test__mb_to_n_sqm(self):
|
||||
# make sure some hard-coded values work
|
||||
assert mb_to_n_sqm(1050) == 105000, "value not correct"
|
||||
assert mb_to_n_sqm(1013) == 101300, "value not correct"
|
||||
assert mb_to_n_sqm(939) == 93900, "value not correct"
|
||||
assert mb_to_n_sqm(900) == 90000, "value not correct"
|
||||
|
||||
def test__mb_to_pa(self):
|
||||
# make sure some hard-coded values work
|
||||
assert mb_to_pa(1050) == 105000, "value not correct"
|
||||
assert mb_to_pa(1013) == 101300, "value not correct"
|
||||
assert mb_to_pa(939) == 93900, "value not correct"
|
||||
assert mb_to_pa(900) == 90000, "value not correct"
|
||||
|
||||
def test__mb_to_lb_sqft(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(mb_to_lb_sqft(1050), 4) == 2192.9702, "value not correct"
|
||||
assert round(mb_to_lb_sqft(1013), 4) == 2115.6941, "value not correct"
|
||||
assert round(mb_to_lb_sqft(939), 4) == 1961.1419, "value not correct"
|
||||
assert round(mb_to_lb_sqft(900), 4) == 1879.6887, "value not correct"
|
||||
|
||||
def test__mb_to_lb_sqin(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(mb_to_lb_sqin(1050), 4) == 15.2290, "value not correct"
|
||||
assert round(mb_to_lb_sqin(1013), 4) == 14.6923, "value not correct"
|
||||
assert round(mb_to_lb_sqin(939), 4) == 13.6191, "value not correct"
|
||||
assert round(mb_to_lb_sqin(900), 4) == 13.0534, "value not correct"
|
||||
|
||||
def test__mm32_to_mb(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(mm32_to_mb(787), 4) == 1049.2441, "value not correct"
|
||||
assert round(mm32_to_mb(759), 4) == 1011.9140, "value not correct"
|
||||
assert round(mm32_to_mb(704), 4) == 938.5869, "value not correct"
|
||||
assert round(mm32_to_mb(675), 4) == 899.9235, "value not correct"
|
||||
|
||||
def test__mm60_to_mb(self):
|
||||
# make sure some hard-coded values work
|
||||
for p, expected in [(787, 1046.2929),
|
||||
(759, 1009.0677),
|
||||
(704, 935.9469),
|
||||
(675, 897.3923)]:
|
||||
self.assertAlmostEqual(
|
||||
mm60_to_mb(p),
|
||||
expected,
|
||||
places=4,
|
||||
msg=f"Incorrect conversion for {p}")
|
||||
|
||||
def test__n_sqm_to_mb(self):
|
||||
# make sure some hard-coded values work
|
||||
assert n_sqm_to_mb(105000) == 1050, "value not correct"
|
||||
assert n_sqm_to_mb(101300) == 1013, "value not correct"
|
||||
assert n_sqm_to_mb(93900) == 939, "value not correct"
|
||||
assert n_sqm_to_mb(90000) == 900, "value not correct"
|
||||
|
||||
def test__pa_to_atm(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(pa_to_atm(105000), 4) == 1.0362, "value not correct"
|
||||
assert round(pa_to_atm(101300), 4) == 0.9997, "value not correct"
|
||||
assert round(pa_to_atm(93900), 4) == 0.9267, "value not correct"
|
||||
assert round(pa_to_atm(90000), 4) == 0.8882, "value not correct"
|
||||
|
||||
def test__pa_to_mb(self):
|
||||
# make sure some hard-coded values work
|
||||
assert pa_to_mb(105000) == 1050, "value not correct"
|
||||
assert pa_to_mb(101300) == 1013, "value not correct"
|
||||
assert pa_to_mb(93900) == 939, "value not correct"
|
||||
assert pa_to_mb(90000) == 900, "value not correct"
|
||||
|
||||
def test__hpa_to_mb(self):
|
||||
# make sure some hard-coded values work
|
||||
assert hpa_to_mb(1050) == 1050, "value not correct"
|
||||
assert hpa_to_mb(1013) == 1013, "value not correct"
|
||||
assert hpa_to_mb(939) == 939, "value not correct"
|
||||
assert hpa_to_mb(900) == 900, "value not correct"
|
||||
|
||||
def test__kpa_to_mb(self):
|
||||
# make sure some hard-coded values work
|
||||
assert kpa_to_mb(1050) == 10500, "value not correct"
|
||||
assert kpa_to_mb(1013) == 10130, "value not correct"
|
||||
assert kpa_to_mb(939) == 9390, "value not correct"
|
||||
assert kpa_to_mb(900) == 9000, "value not correct"
|
||||
|
||||
def test__lb_sqft_to_mb(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(lb_sqft_to_mb(2192), 4) == 1049.5362, "value not correct"
|
||||
assert round(lb_sqft_to_mb(2115), 4) == 1012.6683, "value not correct"
|
||||
assert round(lb_sqft_to_mb(1961), 4) == 938.9327, "value not correct"
|
||||
assert round(lb_sqft_to_mb(1879), 4) == 899.6708, "value not correct"
|
||||
|
||||
def test__lb_sqin_to_atm(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(lb_sqin_to_atm(15.2310), 4) == 1.0364, "value not correct"
|
||||
assert round(lb_sqin_to_atm(14.6960), 4) == 1.0000, "value not correct"
|
||||
assert round(lb_sqin_to_atm(13.6203), 4) == 0.9268, "value not correct"
|
||||
assert round(lb_sqin_to_atm(13.0545), 4) == 0.8883, "value not correct"
|
||||
|
||||
def test__lb_sqin_to_mm32(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(lb_sqin_to_mm32(15.2310), 4) == 31.0106, \
|
||||
"value not correct"
|
||||
assert round(lb_sqin_to_mm32(14.6960), 4) == 29.9213, \
|
||||
"value not correct"
|
||||
assert round(lb_sqin_to_mm32(13.6203), 4) == 27.7312, \
|
||||
"value not correct"
|
||||
assert round(lb_sqin_to_mm32(13.0545), 4) == 26.5792, \
|
||||
"value not correct"
|
||||
|
||||
def test__lb_sqin_to_mm60(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(lb_sqin_to_mm60(15.2310), 4) == 31.0982, \
|
||||
"value not correct"
|
||||
assert round(lb_sqin_to_mm60(14.6960), 4) == 30.0059, \
|
||||
"value not correct"
|
||||
assert round(lb_sqin_to_mm60(13.6203), 4) == 27.8095, \
|
||||
"value not correct"
|
||||
assert round(lb_sqin_to_mm60(13.0545), 4) == 26.6543, \
|
||||
"value not correct"
|
||||
|
||||
def test__lb_sqin_to_mb(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(lb_sqin_to_mb(15.2310), 4) == 1050.1386, \
|
||||
"value not correct"
|
||||
assert round(lb_sqin_to_mb(14.6960), 4) == 1013.2517, \
|
||||
"value not correct"
|
||||
assert round(lb_sqin_to_mb(13.6203), 4) == 939.0849, \
|
||||
"value not correct"
|
||||
assert round(lb_sqin_to_mb(13.0545), 4) == 900.0745, \
|
||||
"value not correct"
|
||||
|
||||
|
||||
def main():
|
||||
suite = unittest.makeSuite(TestCase, 'test')
|
||||
runner = unittest.TextTestRunner()
|
||||
runner.run(suite)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
212
davisAPI/PyWeather-master/weather/units/tests/test_temp.py
Normal file
212
davisAPI/PyWeather-master/weather/units/tests/test_temp.py
Normal file
@ -0,0 +1,212 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
#
|
||||
# Unit Tests for temp module
|
||||
#
|
||||
# See __usage__ for an explanation of runtime arguments.
|
||||
#
|
||||
# -Christopher Blunck
|
||||
#
|
||||
|
||||
|
||||
|
||||
import unittest
|
||||
|
||||
from ..temp import *
|
||||
|
||||
__author__ = 'Christopher Blunck'
|
||||
__email__ = 'chris@wxnet.org'
|
||||
__revision__ = '$Revision: 1.6 $'
|
||||
|
||||
__doc__ = '''
|
||||
Unit tests the temp module.
|
||||
'''
|
||||
|
||||
__usage__ = '''
|
||||
python $0
|
||||
'''
|
||||
|
||||
|
||||
def usage():
|
||||
print(__usage__)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class TestCase(unittest.TestCase):
|
||||
def setUp(self): pass
|
||||
|
||||
def tearDown(self): pass
|
||||
|
||||
def test__calc_heat_index(self):
|
||||
# if the temperature is < 80, heat index == temperature
|
||||
assert calc_heat_index(70, 100) == 70 , "value not correct"
|
||||
assert calc_heat_index(79.9, 100) == 79.9 , "value not correct"
|
||||
assert calc_heat_index(80, 100) != 80 , "value not correct"
|
||||
|
||||
# make sure some hard-coded values work
|
||||
assert int(calc_heat_index(80, 100)) == 87, "value not correct"
|
||||
assert int(calc_heat_index(80, 10)) == 78, "value not correct"
|
||||
assert int(calc_heat_index(90, 50)) == 94, "value not correct"
|
||||
assert int(calc_heat_index(120, 100)) == 380, "value not correct"
|
||||
|
||||
|
||||
def test__calc_wind_chill(self):
|
||||
# make sure some hard-coded values work
|
||||
assert int(calc_wind_chill(80, 10)) == 83, "value not correct"
|
||||
assert int(calc_wind_chill(32, 10)) == 23, "value not correct"
|
||||
assert int(calc_wind_chill(-20, 5)) == -34, "value not correct"
|
||||
|
||||
|
||||
def test__fahrenheit_to_celsius(self):
|
||||
# make sure some special values work
|
||||
assert int(fahrenheit_to_celsius(32)) == 0, "value not correct"
|
||||
assert int(fahrenheit_to_celsius(212)) == 100, "value not correct"
|
||||
|
||||
# make sure some hard coded values work
|
||||
assert int(fahrenheit_to_celsius(60)) == 15, "value not correct"
|
||||
assert int(fahrenheit_to_celsius(-60)) == -51, "value not correct"
|
||||
assert int(fahrenheit_to_celsius(90)) == 32, "value not correct"
|
||||
|
||||
|
||||
def test__celsius_to_fahrenheit(self):
|
||||
# make sure some special values work
|
||||
assert int(celsius_to_fahrenheit(0)) == 32, "value not correct"
|
||||
assert int(celsius_to_fahrenheit(100)) == 212, "value not correct"
|
||||
|
||||
# make sure some hard coded values work
|
||||
assert int(celsius_to_fahrenheit(60)) == 140, "value not correct"
|
||||
assert int(celsius_to_fahrenheit(-60)) == -76, "value not correct"
|
||||
assert int(celsius_to_fahrenheit(30)) == 86, "value not correct"
|
||||
|
||||
|
||||
def test__celsius_to_kelvin(self):
|
||||
# make sure some special values work
|
||||
assert int(celsius_to_kelvin(-273.15)) == 0, "value not correct"
|
||||
assert int(celsius_to_kelvin(100)) == 373, "value not correct"
|
||||
|
||||
# make sure some hard coded values work
|
||||
assert int(celsius_to_kelvin(60)) == 333, "value not correct"
|
||||
assert int(celsius_to_kelvin(-60)) == 213, "value not correct"
|
||||
assert int(celsius_to_kelvin(30)) == 303, "value not correct"
|
||||
|
||||
|
||||
def test__celsius_to_rankine(self):
|
||||
# make sure some special values work
|
||||
assert int(celsius_to_rankine(0)) == 491, "value not correct"
|
||||
assert int(celsius_to_rankine(100)) == 671, "value not correct"
|
||||
|
||||
# make sure some hard coded values work
|
||||
assert int(celsius_to_rankine(60)) == 599, "value not correct"
|
||||
assert int(celsius_to_rankine(-60)) == 383, "value not correct"
|
||||
assert int(celsius_to_rankine(30)) == 545, "value not correct"
|
||||
|
||||
|
||||
def test__fahrenheit_to_kelvin(self):
|
||||
# make sure some special values work
|
||||
assert int(fahrenheit_to_kelvin(32)) == 273, "value not correct"
|
||||
assert int(fahrenheit_to_kelvin(212)) == 373, "value not correct"
|
||||
|
||||
# make sure some hard coded values work
|
||||
assert int(fahrenheit_to_kelvin(60)) == 288, "value not correct"
|
||||
assert int(fahrenheit_to_kelvin(-60)) == 222, "value not correct"
|
||||
assert int(fahrenheit_to_kelvin(90)) == 305, "value not correct"
|
||||
|
||||
|
||||
def test__fahrenheit_to_rankine(self):
|
||||
# make sure some special values work
|
||||
assert int(fahrenheit_to_rankine(32)) == 491, "value not correct"
|
||||
assert int(fahrenheit_to_rankine(212)) == 671, "value not correct"
|
||||
|
||||
# make sure some hard coded values work
|
||||
assert int(fahrenheit_to_rankine(60)) == 519, "value not correct"
|
||||
assert int(fahrenheit_to_rankine(-60)) == 399, "value not correct"
|
||||
assert int(fahrenheit_to_rankine(90)) == 549, "value not correct"
|
||||
|
||||
|
||||
def test__kelvin_to_celsius(self):
|
||||
# make sure some special values work
|
||||
assert int(kelvin_to_celsius(273.15)) == 0, "value not correct"
|
||||
assert int(kelvin_to_celsius(373.15)) == 100, "value not correct"
|
||||
|
||||
# make sure some hard coded values work
|
||||
assert int(kelvin_to_celsius(0)) == -273, "value not correct"
|
||||
assert int(kelvin_to_celsius(293.15)) == 20, "value not correct"
|
||||
assert int(kelvin_to_celsius(343.15)) == 70, "value not correct"
|
||||
|
||||
|
||||
def test__kelvin_to_fahrenheit(self):
|
||||
# make sure some special values work
|
||||
assert int(kelvin_to_fahrenheit(273.15)) == 32, "value not correct"
|
||||
assert int(kelvin_to_fahrenheit(373.15)) == 212, "value not correct"
|
||||
|
||||
# make sure some hard coded values work
|
||||
assert int(kelvin_to_fahrenheit(0)) == -459, "value not correct"
|
||||
assert int(kelvin_to_fahrenheit(293.15)) == 68, "value not correct"
|
||||
assert int(kelvin_to_fahrenheit(343.15)) == 158, "value not correct"
|
||||
|
||||
|
||||
def test__kelvin_to_rankine(self):
|
||||
# make sure some special values work
|
||||
assert int(kelvin_to_rankine(273.15)) == 491, "value not correct"
|
||||
assert int(kelvin_to_rankine(373.15)) == 671, "value not correct"
|
||||
|
||||
# make sure some hard coded values work
|
||||
assert int(kelvin_to_rankine(0)) == 0, "value not correct"
|
||||
assert int(kelvin_to_rankine(293.15)) == 527, "value not correct"
|
||||
assert int(kelvin_to_rankine(343.15)) == 617, "value not correct"
|
||||
|
||||
|
||||
def test__rankine_to_celsius(self):
|
||||
# make sure some special values work
|
||||
assert int(rankine_to_celsius(491)) == 0, "value not correct"
|
||||
assert int(rankine_to_celsius(671)) == 99, "value not correct"
|
||||
|
||||
# make sure some hard coded values work
|
||||
assert int(rankine_to_celsius(0)) == -273, "value not correct"
|
||||
assert int(rankine_to_celsius(527)) == 19, "value not correct"
|
||||
assert int(rankine_to_celsius(617)) == 69, "value not correct"
|
||||
|
||||
|
||||
def test__rankine_to_fahrenheit(self):
|
||||
# make sure some special values work
|
||||
assert int(rankine_to_fahrenheit(491)) == 31, "value not correct"
|
||||
assert int(rankine_to_fahrenheit(671)) == 211, "value not correct"
|
||||
|
||||
# make sure some hard coded values work
|
||||
assert int(rankine_to_fahrenheit(0)) == -459, "value not correct"
|
||||
assert int(rankine_to_fahrenheit(527)) == 67, "value not correct"
|
||||
assert int(rankine_to_fahrenheit(617)) == 157, "value not correct"
|
||||
|
||||
|
||||
def test__rankine_to_kelvin(self):
|
||||
# make sure some special values work
|
||||
assert int(rankine_to_kelvin(491)) == 272, "value not correct"
|
||||
assert int(rankine_to_kelvin(671)) == 372, "value not correct"
|
||||
|
||||
# make sure some hard coded values work
|
||||
assert int(rankine_to_kelvin(0)) == 0, "value not correct"
|
||||
assert int(rankine_to_kelvin(527)) == 292, "value not correct"
|
||||
assert int(rankine_to_kelvin(617)) == 342, "value not correct"
|
||||
|
||||
|
||||
def test__dewpoint(self):
|
||||
# make sure some hard coded values work
|
||||
assert int(calc_dewpoint(12, 72)) == 4, "value not correct"
|
||||
assert int(calc_dewpoint(75, 33)) == 43, "value not correct"
|
||||
assert int(calc_dewpoint(90, 85)) == 84, "value not correct"
|
||||
|
||||
def test__humidity(self):
|
||||
# make sure some hard coded values work
|
||||
assert int(calc_humidity(87, 76) * 100) == 69, "value not correct"
|
||||
assert int(calc_humidity(75, 45) * 100) == 34, "value not correct"
|
||||
assert int(calc_humidity(50, 10) * 100) == 19, "value not correct"
|
||||
assert int(calc_humidity(100, 88) * 100) == 68, "value not correct"
|
||||
|
||||
|
||||
def main():
|
||||
suite = unittest.makeSuite(TestCase, 'test')
|
||||
runner = unittest.TextTestRunner()
|
||||
runner.run(suite)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
215
davisAPI/PyWeather-master/weather/units/tests/test_wind.py
Normal file
215
davisAPI/PyWeather-master/weather/units/tests/test_wind.py
Normal file
@ -0,0 +1,215 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
#
|
||||
# Unit Tests for pressure module
|
||||
#
|
||||
# See __usage__ for an explanation of runtime arguments.
|
||||
#
|
||||
# -Christopher Blunck
|
||||
#
|
||||
|
||||
|
||||
|
||||
import unittest
|
||||
|
||||
from ..wind import *
|
||||
|
||||
__author__ = 'Christopher Blunck'
|
||||
__email__ = 'chris@wxnet.org'
|
||||
__revision__ = '$Revision: 1.6 $'
|
||||
|
||||
__doc__ = '''
|
||||
Unit tests the pressure module.
|
||||
'''
|
||||
|
||||
__usage__ = '''
|
||||
python $0
|
||||
'''
|
||||
|
||||
|
||||
def usage():
|
||||
print(__usage__)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class TestCase(unittest.TestCase):
|
||||
def setUp(self): pass
|
||||
|
||||
def tearDown(self): pass
|
||||
|
||||
|
||||
def test__knots_to_ft_sec(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(knots_to_ft_sec(5), 4) == 8.4390, "value not correct"
|
||||
assert round(knots_to_ft_sec(14), 4) == 23.6293, "value not correct"
|
||||
assert round(knots_to_ft_sec(35), 4) == 59.0733, "value not correct"
|
||||
assert round(knots_to_ft_sec(70), 4) == 118.1467, "value not correct"
|
||||
|
||||
|
||||
def test__knots_to_km_hr(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(knots_to_km_hr(5), 4) == 9.2600, "value not correct"
|
||||
assert round(knots_to_km_hr(14), 4) == 25.9280, "value not correct"
|
||||
assert round(knots_to_km_hr(35), 4) == 64.8200, "value not correct"
|
||||
assert round(knots_to_km_hr(70), 4) == 129.6400, "value not correct"
|
||||
|
||||
|
||||
def test__knots_to_m_sec(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(knots_to_m_sec(5), 4) == 2.5722, "value not correct"
|
||||
assert round(knots_to_m_sec(14), 4) == 7.2022, "value not correct"
|
||||
assert round(knots_to_m_sec(35), 4) == 18.0055, "value not correct"
|
||||
assert round(knots_to_m_sec(70), 4) == 36.0111, "value not correct"
|
||||
|
||||
|
||||
def test__knots_to_mph(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(knots_to_mph(5), 4) == 5.7539, "value not correct"
|
||||
assert round(knots_to_mph(14), 4) == 16.1109, "value not correct"
|
||||
assert round(knots_to_mph(35), 4) == 40.2773, "value not correct"
|
||||
assert round(knots_to_mph(70), 4) == 80.5546, "value not correct"
|
||||
|
||||
|
||||
def test__knots_to_nmph(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(knots_to_nmph(5), 4) == 5, "value not correct"
|
||||
assert round(knots_to_nmph(14), 4) == 14, "value not correct"
|
||||
assert round(knots_to_nmph(35), 4) == 35, "value not correct"
|
||||
assert round(knots_to_nmph(70), 4) == 70, "value not correct"
|
||||
|
||||
|
||||
def test__ft_sec_to_knots(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(ft_sec_to_knots(8.4390), 4) == 5, "value not correct"
|
||||
assert round(ft_sec_to_knots(23.6293), 4) == 14, "value not correct"
|
||||
assert round(ft_sec_to_knots(59.0733), 4) == 35, "value not correct"
|
||||
assert round(ft_sec_to_knots(118.1467), 4) == 70, "value not correct"
|
||||
|
||||
|
||||
def test__km_hr_to_knots(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(km_hr_to_knots(9.2600), 4) == 5, "value not correct"
|
||||
assert round(km_hr_to_knots(25.9280), 4) == 14, "value not correct"
|
||||
assert round(km_hr_to_knots(64.8200), 4) == 35, "value not correct"
|
||||
assert round(km_hr_to_knots(129.6400), 4) == 70, "value not correct"
|
||||
|
||||
|
||||
def test__m_sec_to_knots(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(m_sec_to_knots(2.5722), 4) == 5, "value not correct"
|
||||
assert round(m_sec_to_knots(7.2022), 4) == 14, "value not correct"
|
||||
assert round(m_sec_to_knots(18.0055), 4) == 34.9999, "value not correct"
|
||||
assert round(m_sec_to_knots(36.0111), 4) == 70, "value not correct"
|
||||
|
||||
|
||||
def test__mph_to_knots(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(mph_to_knots(5.7539), 4) == 5, "value not correct"
|
||||
assert round(mph_to_knots(16.1109), 4) == 14, "value not correct"
|
||||
assert round(mph_to_knots(40.2773), 4) == 35, "value not correct"
|
||||
assert round(mph_to_knots(80.5546), 4) == 70, "value not correct"
|
||||
|
||||
|
||||
def test__nmph_to_knots(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(nmph_to_knots(5), 4) == 5, "value not correct"
|
||||
assert round(nmph_to_knots(14), 4) == 14, "value not correct"
|
||||
assert round(nmph_to_knots(35), 4) == 35, "value not correct"
|
||||
assert round(nmph_to_knots(70), 4) == 70, "value not correct"
|
||||
|
||||
|
||||
def test__mph_to_ft_min(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(mph_to_ft_min(5), 4) == 440, "value not correct"
|
||||
assert round(mph_to_ft_min(16), 4) == 1408, "value not correct"
|
||||
assert round(mph_to_ft_min(40), 4) == 3520, "value not correct"
|
||||
assert round(mph_to_ft_min(80), 4) == 7040, "value not correct"
|
||||
|
||||
|
||||
def test__mph_to_ft_sec(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(mph_to_ft_sec(5), 4) == 7.3333, "value not correct"
|
||||
assert round(mph_to_ft_sec(16), 4) == 23.4667, "value not correct"
|
||||
assert round(mph_to_ft_sec(40), 4) == 58.6666, "value not correct"
|
||||
assert round(mph_to_ft_sec(80), 4) == 117.3333, "value not correct"
|
||||
|
||||
|
||||
def test__mph_to_km_hr(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(mph_to_km_hr(5), 4) == 8.0467, "value not correct"
|
||||
assert round(mph_to_km_hr(16), 4) == 25.7495, "value not correct"
|
||||
assert round(mph_to_km_hr(40), 4) == 64.3738, "value not correct"
|
||||
assert round(mph_to_km_hr(80), 4) == 128.7475, "value not correct"
|
||||
|
||||
|
||||
def test__mph_to_m_sec(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(mph_to_m_sec(5), 4) == 2.2352, "value not correct"
|
||||
assert round(mph_to_m_sec(16), 4) == 7.1526, "value not correct"
|
||||
assert round(mph_to_m_sec(40), 4) == 17.8816, "value not correct"
|
||||
assert round(mph_to_m_sec(80), 4) == 35.7632, "value not correct"
|
||||
|
||||
|
||||
def test__ft_min_to_mph(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(ft_min_to_mph(440), 4) == 5, "value not correct"
|
||||
assert round(ft_min_to_mph(1408), 4) == 16, "value not correct"
|
||||
assert round(ft_min_to_mph(3520), 4) == 40, "value not correct"
|
||||
assert round(ft_min_to_mph(7040), 4) == 80, "value not correct"
|
||||
|
||||
|
||||
def test__ft_sec_to_knots(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(ft_sec_to_knots(8.4390), 4) == 5, "value not correct"
|
||||
assert round(ft_sec_to_knots(23.6293), 4) == 14, "value not correct"
|
||||
assert round(ft_sec_to_knots(59.0733), 4) == 35, "value not correct"
|
||||
assert round(ft_sec_to_knots(118.1467), 4) == 70, "value not correct"
|
||||
|
||||
|
||||
def test__ft_sec_to_mph(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(ft_sec_to_mph(7.3333), 4) == 5, "value not correct"
|
||||
assert round(ft_sec_to_mph(23.4667), 4) == 16, "value not correct"
|
||||
assert round(ft_sec_to_mph(58.6666), 4) == 39.9999, "value not correct"
|
||||
assert round(ft_sec_to_mph(117.3333), 4) == 80, "value not correct"
|
||||
|
||||
|
||||
def test__km_hr_to_knots(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(km_hr_to_knots(9.2600), 4) == 5, "value not correct"
|
||||
assert round(km_hr_to_knots(25.9280), 4) == 14, "value not correct"
|
||||
assert round(km_hr_to_knots(64.8200), 4) == 35, "value not correct"
|
||||
assert round(km_hr_to_knots(129.6400), 4) == 70, "value not correct"
|
||||
|
||||
|
||||
def test__km_hr_to_mph(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(km_hr_to_mph(8.0467), 4) == 5, "value not correct"
|
||||
assert round(km_hr_to_mph(25.7495), 4) == 16, "value not correct"
|
||||
assert round(km_hr_to_mph(64.3738), 4) == 40, "value not correct"
|
||||
assert round(km_hr_to_mph(128.7475), 4) == 80, "value not correct"
|
||||
|
||||
|
||||
def test__m_sec_to_knots(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(m_sec_to_knots(2.5722), 4) == 5, "value not correct"
|
||||
assert round(m_sec_to_knots(7.2022), 4) == 14, "value not correct"
|
||||
assert round(m_sec_to_knots(18.0055), 4) == 34.9999, "value not correct"
|
||||
assert round(m_sec_to_knots(36.0111), 4) == 70, "value not correct"
|
||||
|
||||
|
||||
def test__m_sec_to_mph(self):
|
||||
# make sure some hard-coded values work
|
||||
assert round(m_sec_to_mph(2.2352), 4) == 5, "value not correct"
|
||||
assert round(m_sec_to_mph(7.1526), 4) == 15.9999, "value not correct"
|
||||
assert round(m_sec_to_mph(17.8816), 4) == 40, "value not correct"
|
||||
assert round(m_sec_to_mph(35.7632), 4) == 80, "value not correct"
|
||||
|
||||
|
||||
def main():
|
||||
suite = unittest.makeSuite(TestCase, 'test')
|
||||
runner = unittest.TextTestRunner()
|
||||
runner.run(suite)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
123
davisAPI/PyWeather-master/weather/units/wind.py
Normal file
123
davisAPI/PyWeather-master/weather/units/wind.py
Normal file
@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
#
|
||||
# See __doc__ for an explanation of what this module does
|
||||
#
|
||||
# See __usage__ for an explanation of runtime arguments.
|
||||
#
|
||||
# -Christopher Blunck
|
||||
#
|
||||
|
||||
import sys
|
||||
|
||||
__author__ = 'Christopher Blunck'
|
||||
__email__ = 'chris@wxnet.org'
|
||||
__revision__ = '$Revision: 1.6 $'
|
||||
|
||||
__doc__ = 'wind related conversion functions'
|
||||
__usage__ = 'this module should not be run via the command line'
|
||||
|
||||
|
||||
def knots_to_ft_sec(kts):
|
||||
"""Knots (kt) to feet/second (ft/s)"""
|
||||
return kts * 1.6878099
|
||||
|
||||
|
||||
def knots_to_km_hr(kts):
|
||||
"""Knots (kt) to kilometers/hour (kph)"""
|
||||
return kts * 1.852
|
||||
|
||||
|
||||
def knots_to_m_sec(kts):
|
||||
"""Knots (kt) to meters/second (m/s)"""
|
||||
return kts * 0.514444
|
||||
|
||||
|
||||
def knots_to_mph(kts):
|
||||
"""Knots (kt) to miles/hour (mph)"""
|
||||
return kts * 1.1507794
|
||||
|
||||
|
||||
def knots_to_nmph(kts):
|
||||
"""Knots (kt) to nautical miles/hour (nmph)"""
|
||||
return kts
|
||||
|
||||
|
||||
def ft_sec_to_knots(ft):
|
||||
"""Feet/second (ft/s) to knots (kt)"""
|
||||
return ft * 0.5924838
|
||||
|
||||
|
||||
def km_hr_to_knots(km):
|
||||
"""Kilometers/hour (kph) to knots (kt)"""
|
||||
return km * 0.5399568
|
||||
|
||||
|
||||
def m_sec_to_knots(m):
|
||||
"""Meters/second (m/s) to knots (kt)"""
|
||||
return m * 1.943846
|
||||
|
||||
|
||||
def mph_to_knots(mph):
|
||||
"""Miles/hour (mph) to knots (kt)"""
|
||||
return mph * 0.86897624
|
||||
|
||||
|
||||
def nmph_to_knots(mph):
|
||||
"""Nautical miles/hour (nmph) to knots (kt)"""
|
||||
return mph
|
||||
|
||||
|
||||
def mph_to_ft_min(mph):
|
||||
"""Miles/hour (mph) to feet/minute (ft/min)"""
|
||||
return mph * 88
|
||||
|
||||
|
||||
def mph_to_ft_sec(mph):
|
||||
"""Miles/hour (mph) to feet/second (ft/s)"""
|
||||
return mph * 1.466666
|
||||
|
||||
|
||||
def mph_to_km_hr(mph):
|
||||
"""Miles/hour (mph) to kilometers/hour (kph)"""
|
||||
return mph * 1.609344
|
||||
|
||||
|
||||
def mph_to_m_sec(mph):
|
||||
"""Miles/hour (mph) to meters/second (m/s)"""
|
||||
return mph * 0.44704
|
||||
|
||||
|
||||
def ft_min_to_mph(ft):
|
||||
"""Feet/minute (ft/min) to miles/hour (mph)"""
|
||||
return ft * 0.01136363
|
||||
|
||||
|
||||
def ft_sec_to_knots(ft):
|
||||
"""Feet/second (ft/s) to knots (kt)"""
|
||||
return ft * 0.5924838
|
||||
|
||||
|
||||
def ft_sec_to_mph(ft):
|
||||
"""Feet/second (ft/s) to miles/hour (mph)"""
|
||||
return ft * 0.681818
|
||||
|
||||
|
||||
def km_hr_to_knots(km):
|
||||
"""Kilometers/hour (kph) to knots (kt)"""
|
||||
return km * 0.5399568
|
||||
|
||||
|
||||
def km_hr_to_mph(km):
|
||||
"""Kilometers/hour (kph) to miles/hour (mph)"""
|
||||
return km * 0.62137119
|
||||
|
||||
|
||||
def m_sec_to_knots(m):
|
||||
"""Meters/second (m/s) to knots (kt)"""
|
||||
return m * 1.943846
|
||||
|
||||
|
||||
def m_sec_to_mph(m):
|
||||
"""Meters/second (m/s) to miles/hour (mph)"""
|
||||
return m * 2.2369363
|
77
davisAPI/davisAPI.py
Normal file
77
davisAPI/davisAPI.py
Normal file
@ -0,0 +1,77 @@
|
||||
from influxdb_client import InfluxDBClient, Point
|
||||
from influxdb_client.client.write_api import SYNCHRONOUS
|
||||
from weather.stations.davis import VantagePro
|
||||
import gc
|
||||
from pprint import pprint
|
||||
import time
|
||||
import logging
|
||||
import serial.tools.list_ports
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
logging.basicConfig(filename="Stations.log",
|
||||
format='%(asctime)s %(message)s',
|
||||
filemode='a')
|
||||
logger = logging.getLogger('davis_api')
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
|
||||
def write_data(device, station, send=True):
|
||||
try:
|
||||
device.parse()
|
||||
data = device.fields
|
||||
points = []
|
||||
fields = ['BarTrend', 'CRC', 'DateStamp', 'DewPoint', 'HeatIndex', 'ETDay', 'HeatIndex',
|
||||
'HumIn', 'HumOut', 'Pressure', 'RainDay', 'RainMonth', 'RainRate', 'RainStorm',
|
||||
'RainYear', 'SunRise', 'SunSet', 'TempIn', 'TempOut', 'WindDir', 'WindSpeed',
|
||||
'WindSpeed10Min']
|
||||
if send:
|
||||
for field in fields:
|
||||
points.append(Point(station).field(field, data[field]))
|
||||
write_api.write(bucket=bucket, record=points)
|
||||
else:
|
||||
pprint(data)
|
||||
# tables = query_api.query(f'from(bucket:"{bucket}") |> range(start: -1h)')
|
||||
del data
|
||||
del points
|
||||
del fields
|
||||
gc.collect()
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
raise e
|
||||
|
||||
# убрать
|
||||
try:
|
||||
token = "2CEePET3ss2khtjsdGrJap8mVzHhR2dRwuyK3NuFBvDgGtOMSi6Jstsrp2o-OANzD8fxB73PsTyIbqgbnokoXQ=="
|
||||
bucket = 'wind'
|
||||
org = "UlSTU"
|
||||
url = "http://influxdb.athene.tech/"
|
||||
client = InfluxDBClient(url=url, token=token, org=org)
|
||||
write_api = client.write_api(write_options=SYNCHRONOUS)
|
||||
query_api = client.query_api()
|
||||
except Exception as e:
|
||||
logger.error('DB_ERR:' + str(e))
|
||||
raise e
|
||||
|
||||
try:
|
||||
ports = serial.tools.list_ports.comports()
|
||||
available_ports = {}
|
||||
for port in ports:
|
||||
# print(port)
|
||||
if port.serial_number == '0001':
|
||||
available_ports['/dev/'+port.name] = port.vid
|
||||
# COM = 'COM8' # '/dev/ttyUSB0'
|
||||
# COM1 = '/dev/ttyUSB1'
|
||||
# available_ports = {'/dev/ttyUSB0':'st1'}
|
||||
devices = [VantagePro(port) for port in available_ports.keys()]
|
||||
# print(available_ports)
|
||||
while True:
|
||||
for i in range(len(devices)):
|
||||
write_data(devices[i], 'st' + str(available_ports[list(available_ports.keys())[i]]), True)
|
||||
time.sleep(15) # лучше 60
|
||||
except Exception as e:
|
||||
logger.error('Device_error' + str(e))
|
||||
raise e
|
||||
|
69
davisAPI/davisAPI.py.save
Normal file
69
davisAPI/davisAPI.py.save
Normal file
@ -0,0 +1,69 @@
|
||||
from influxdb_client import InfluxDBClient, Point
|
||||
from influxdb_client.client.write_api import SYNCHRONOUS
|
||||
from weather.stations.davis import VantagePro
|
||||
import gc
|
||||
from pprint import pprint
|
||||
import time
|
||||
import logging
|
||||
import serial.tools.list_ports
|
||||
|
||||
logging.basicConfig(filename="Stations.log",
|
||||
format='%(asctime)s %(message)s',
|
||||
filemode='a')
|
||||
logger = logging.getLogger('davis_api')
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
def write_data(device, station, send=True):
|
||||
try:
|
||||
device.parse()
|
||||
data = device.fields
|
||||
points = []
|
||||
fields = ['BarTrend', 'CRC', 'DateStamp', 'DateStampUTC', 'DewPoint', 'HeatIndex', 'ETDay', 'HeatIndex',
|
||||
'HumIn', 'HumOut', 'LeafWetness', 'Pressure', 'RainDay', 'RainMonth', 'RainRate', 'RainStorm',
|
||||
'RainYear', 'SoilMoist', 'SunRise', 'SunSet', 'TempIn', 'TempOut', 'WindDir', 'WindSpeed',
|
||||
'WindSpeed10Min']
|
||||
if send:
|
||||
for field in fields:
|
||||
points.append(Point(station).field(field, data[field]))
|
||||
write_api.write(bucket=bucket, record=points)
|
||||
else:
|
||||
pprint(data)
|
||||
# tables = query_api.query(f'from(bucket:"{bucket}") |> range(start: -1h)')
|
||||
del data
|
||||
del points
|
||||
del fields
|
||||
gc.collect()
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
|
||||
|
||||
try:
|
||||
token = "2CEePET3ss2khtjsdGrJap8mVzHhR2dRwuyK3NuFBvDgGtOMSi6Jstsrp2o-OANzD8fxB73PsTyIbqgbnokoXQ=="
|
||||
bucket = 'wind'
|
||||
org = "UlSTU"
|
||||
url = "http://influxdb.athene.tech/"
|
||||
client = InfluxDBClient(url=url, token=token, org=org)
|
||||
write_api = client.write_api(write_options=SYNCHRONOUS)
|
||||
query_api = client.query_api()
|
||||
except Exception as e:
|
||||
logger.error('DB_ERR:' + str(e))
|
||||
|
||||
try:
|
||||
ports = serial.tools.list_ports.comports()
|
||||
available_ports = {}
|
||||
for port in ports:
|
||||
print(port)
|
||||
if port.manufacturer == 'Silicon Labs':
|
||||
available_ports['/dev/'+port.name] = port.vid
|
||||
# COM = 'COM8' # '/dev/ttyUSB0'
|
||||
# COM1 = '/dev/ttyUSB1
|
||||
'
|
||||
devices = [VantagePro(port) for port in available_ports.keys()]
|
||||
print(available_ports)
|
||||
while True:
|
||||
for i in range(len(devices)):
|
||||
write_data(devices[i], list(available_ports.keys())[i], False)
|
||||
time.sleep(15)
|
||||
except Exception as e:
|
||||
logger.error('Device_error' + str(e))
|
69
davisAPI/davisAPI.py.save.1
Normal file
69
davisAPI/davisAPI.py.save.1
Normal file
@ -0,0 +1,69 @@
|
||||
from influxdb_client import InfluxDBClient, Point
|
||||
from influxdb_client.client.write_api import SYNCHRONOUS
|
||||
from weather.stations.davis import VantagePro
|
||||
import gc
|
||||
from pprint import pprint
|
||||
import time
|
||||
import logging
|
||||
import serial.tools.list_ports
|
||||
|
||||
logging.basicConfig(filename="Stations.log",
|
||||
format='%(asctime)s %(message)s',
|
||||
filemode='a')
|
||||
logger = logging.getLogger('davis_api')
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
def write_data(device, station, send=True):
|
||||
try:
|
||||
device.parse()
|
||||
data = device.fields
|
||||
points = []
|
||||
fields = ['BarTrend', 'CRC', 'DateStamp', 'DateStampUTC', 'DewPoint', 'HeatIndex', 'ETDay', 'HeatIndex',
|
||||
'HumIn', 'HumOut', 'LeafWetness', 'Pressure', 'RainDay', 'RainMonth', 'RainRate', 'RainStorm',
|
||||
'RainYear', 'SoilMoist', 'SunRise', 'SunSet', 'TempIn', 'TempOut', 'WindDir', 'WindSpeed',
|
||||
'WindSpeed10Min']
|
||||
if send:
|
||||
for field in fields:
|
||||
points.append(Point(station).field(field, data[field]))
|
||||
write_api.write(bucket=bucket, record=points)
|
||||
else:
|
||||
pprint(data)
|
||||
# tables = query_api.query(f'from(bucket:"{bucket}") |> range(start: -1h)')
|
||||
del data
|
||||
del points
|
||||
del fields
|
||||
gc.collect()
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
|
||||
|
||||
try:
|
||||
token = "2CEePET3ss2khtjsdGrJap8mVzHhR2dRwuyK3NuFBvDgGtOMSi6Jstsrp2o-OANzD8fxB73PsTyIbqgbnokoXQ=="
|
||||
bucket = 'wind'
|
||||
org = "UlSTU"
|
||||
url = "http://influxdb.athene.tech/"
|
||||
client = InfluxDBClient(url=url, token=token, org=org)
|
||||
write_api = client.write_api(write_options=SYNCHRONOUS)
|
||||
query_api = client.query_api()
|
||||
except Exception as e:
|
||||
logger.error('DB_ERR:' + str(e))
|
||||
|
||||
try:
|
||||
ports = serial.tools.list_ports.comports()
|
||||
available_ports = {}
|
||||
for port in ports:
|
||||
if port.manufacturer == 'Silicon Labs':
|
||||
available_ports['/dev/' + port.name] = port.vid
|
||||
# COM = 'COM8' # '/dev/ttyUSB0'
|
||||
# COM1 = '/dev/ttyUSB1'
|
||||
print(ports)
|
||||
print(available_ports)
|
||||
device = VantagePro(available_ports
|
||||
devices = [VantagePro(port) for port in available_ports.keys()]
|
||||
while True:
|
||||
for i in range(len(devices)):
|
||||
write_data(devices[i], list(available_ports.keys())[i], False)
|
||||
time.sleep(15)
|
||||
except Exception as e:
|
||||
logger.error('Device_error' + str(e))
|
82
davisAPI/davisAPI.py.save.2
Normal file
82
davisAPI/davisAPI.py.save.2
Normal file
@ -0,0 +1,82 @@
|
||||
from influxdb_client import InfluxDBClient, Point
|
||||
from influxdb_client.client.write_api import SYNCHRONOUS
|
||||
from weather.stations.davis import VantagePro
|
||||
import gc
|
||||
from pprint import pprint
|
||||
import time
|
||||
import logging
|
||||
import serial.tools.list_ports
|
||||
import os
|
||||
import sys
|
||||
import psutil
|
||||
|
||||
logging.basicConfig(filename="Stations.log",
|
||||
format='%(asctime)s %(message)s',
|
||||
filemode='a')
|
||||
logger = logging.getLogger('davis_api')
|
||||
logger.setLevel(logging.DEBUG)
|
||||
ef restart_program():
|
||||
try:
|
||||
p = psutil.Process(os.getpid())
|
||||
for handler in p.open_files() + p.connections():
|
||||
os.close(handler.fd)
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
python = sys.executable
|
||||
os.execl(python,python,*sys.argv)
|
||||
|
||||
|
||||
def write_data(device, station, send=True):
|
||||
try:
|
||||
device.parse()
|
||||
data = device.fields
|
||||
points = []
|
||||
fields = ['BarTrend', 'CRC', 'DateStamp', 'DewPoint', 'HeatIndex', 'ETDay', 'HeatIndex',
|
||||
'HumIn', 'HumOut', 'Pressure', 'RainDay', 'RainMonth', 'RainRate', 'RainStorm',
|
||||
'RainYear', 'SunRise', 'SunSet', 'TempIn', 'TempOut', 'WindDir', 'WindSpeed',
|
||||
'WindSpeed10Min']
|
||||
if send:
|
||||
for field in fields:
|
||||
points.append(Point(station).field(field, data[field]))
|
||||
write_api.write(bucket=bucket, record=points)
|
||||
else:
|
||||
pprint(data)
|
||||
# tables = query_api.query(f'from(bucket:"{bucket}") |> range(start: -1h)')
|
||||
del data
|
||||
del points
|
||||
del fields
|
||||
gc.collect()
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
os.execl('runme.sh','')
|
||||
|
||||
|
||||
try:
|
||||
token = "2CEePET3ss2khtjsdGrJap8mVzHhR2dRwuyK3NuFBvDgGtOMSi6Jstsrp2o-OANzD8fxB73PsTyIbqgbnokoXQ=="
|
||||
bucket = 'wind'
|
||||
org = "UlSTU"
|
||||
url = "http://influxdb.athene.tech/"
|
||||
client = InfluxDBClient(url=url, token=token, org=org)
|
||||
write_api = client.write_api(write_options=SYNCHRONOUS)
|
||||
query_api = client.query_api()
|
||||
except Exception as e:
|
||||
logger.error('DB_ERR:' + str(e))
|
||||
|
||||
try:
|
||||
ports = serial.tools.list_ports.comports()
|
||||
available_ports = {}
|
||||
for port in ports:
|
||||
# print(port)
|
||||
if port.serial_number == '0001':
|
||||
available_ports['/dev/'+port.name] = port.vid
|
||||
# COM = 'COM8' # '/dev/ttyUSB0'
|
||||
# COM1 = '/dev/ttyUSB1'
|
||||
# available_ports = {'/dev/ttyUSB0':'st1'}
|
||||
devices = [VantagePro(port) for port in available_ports.keys()]
|
||||
# print(available_ports)
|
||||
while True:
|
||||
for i in range(len(devices)):
|
||||
write_data(devices[i], 'st' + str(available_ports[list(available_ports.keys())[i]]), True)
|
||||
time.sleep(15)
|
||||
except Exception as e:
|
||||
logger.error('Device_error' + str(e))
|
15
davisAPI/get_ports.py
Normal file
15
davisAPI/get_ports.py
Normal file
@ -0,0 +1,15 @@
|
||||
import serial.tools.list_ports
|
||||
|
||||
ports = serial.tools.list_ports.comports()
|
||||
|
||||
for port, desc, hwid in sorted(ports):
|
||||
print("{}: {} [{}]".format(port, desc, hwid))
|
||||
|
||||
available_ports = {}
|
||||
for port in ports:
|
||||
print(port.serial_number)
|
||||
available_ports['/dev/' + port.name] = port.vid
|
||||
devices = [port for port in available_ports.keys()]
|
||||
print(devices)
|
||||
print(ports[1].manufacturer)
|
||||
#print(ports[0].name)
|
15
davisAPI/get_ports.py.save
Normal file
15
davisAPI/get_ports.py.save
Normal file
@ -0,0 +1,15 @@
|
||||
import serial.tools.list_ports
|
||||
|
||||
ports = serial.tools.list_ports.comports()
|
||||
|
||||
for port, desc, hwid in sorted(ports):
|
||||
print("{}: {} [{}]".format(port, desc, hwid))
|
||||
|
||||
available_ports = {}
|
||||
for port in ports:
|
||||
print(port.serial_number)
|
||||
available_ports['/dev/' + port.name] = port.vid
|
||||
devices = [port for port in available_ports.keys()]
|
||||
print(devices)
|
||||
print(ports[1].manufacturer)
|
||||
#print(ports[0].name)
|
3
davisAPI/runme.sh
Normal file
3
davisAPI/runme.sh
Normal file
@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
kill -9 davisAPI.py
|
||||
sudo python3 davisAPI.py
|
9
davisAPI/runner.py
Normal file
9
davisAPI/runner.py
Normal file
@ -0,0 +1,9 @@
|
||||
from subprocess import Popen
|
||||
import sys
|
||||
|
||||
#filename = sys.argv[1]
|
||||
filename = "davisAPI.py"
|
||||
while True:
|
||||
print('Start'+filename)
|
||||
p = Popen("python " + filename, shell=True)
|
||||
p.wait()
|
27
davisAPI/ulstu_influx.py
Normal file
27
davisAPI/ulstu_influx.py
Normal file
@ -0,0 +1,27 @@
|
||||
from influxdb_client import InfluxDBClient, Point
|
||||
from influxdb_client.client.write_api import SYNCHRONOUS
|
||||
|
||||
bucket = "wind"
|
||||
|
||||
client = InfluxDBClient(url="http://influxdb.athene.tech/",
|
||||
token="2CEePET3ss2khtjsdGrJap8mVzHhR2dRwuyK3NuFBvDgGtOMSi6Jstsrp2o-OANzD8fxB73PsTyIbqgbnokoXQ==",
|
||||
org="UlSTU")
|
||||
|
||||
write_api = client.write_api(write_options=SYNCHRONOUS)
|
||||
query_api = client.query_api()
|
||||
|
||||
# p = Point("my_measurement").tag("location", "Prague").field("val", 200.0)
|
||||
#
|
||||
# write_api.write(bucket=bucket, record=p)
|
||||
|
||||
tables = query_api.query('from(bucket:"wind") |> range(start: -7d)')
|
||||
|
||||
for table in tables:
|
||||
print(table)
|
||||
for row in table.records:
|
||||
print (row.values)
|
||||
print(row.values['_field'])
|
||||
print(row.values['_value'])
|
||||
|
||||
# manger = InfluxManager("wind","http://localhost:8086","IkRV5NlnRp_fTHR5x4mgMzi_coQ31ILQBaQUf5wZfXIJ9iwCZBH9qiHnSREgdu_bdsmAjUisUTmqoMvpXDXUOA==","nikitaOrganization")
|
||||
# print(manger.getDataByTime())
|
Loading…
x
Reference in New Issue
Block a user