-
106
Kodi/Lenovo/addons/weather.openmeteo/CHANGELOG.txt
Normal file
@@ -0,0 +1,106 @@
|
||||
v1.0.14 (22/07/2025)
|
||||
--------------------
|
||||
* Added pressure units: hPa, kPa, mmHg, inHg, psi
|
||||
* Adjust map download timers
|
||||
* Fix geolocation loop on broken KODI devices
|
||||
* Added translation: Chinese (thanks @wabisabi926)
|
||||
|
||||
v1.0.10 (22/05/2025)
|
||||
--------------------
|
||||
* Custom location names
|
||||
* Use highest weather code for condition alert
|
||||
* Re-download maps & layers when changing mapzoom level
|
||||
* Removing locations should now work properly
|
||||
* Fix 12h/24h time for weather alerts
|
||||
* Added translation: Slovak (thanks @frederik877)
|
||||
* Added translation: Tamil (thanks @தமிழ்நேரம்)
|
||||
* Added translation: Czech (thanks @Tomas)
|
||||
* Added translation: Indonesian (thanks @Nao3Line)
|
||||
* Added translation: Hungarian (thanks @frodo19)
|
||||
|
||||
v1.0.0 (15/12/2024)
|
||||
-------------------
|
||||
* Official KODI repository
|
||||
* Translations (Weblate)
|
||||
|
||||
v0.9.9 (24/11/2024)
|
||||
-------------------
|
||||
* Switching locations should be much faster now:
|
||||
Optimized index matching and dynamic settings to improve startup time & performance
|
||||
Added settings caching to further improve startup time & performance
|
||||
* Mergemap() now processes all data in memory to improve performance and reduce IO
|
||||
* Added weather data refresh on SettingsChanged()
|
||||
* Fixed graphs with both negative & positive temperature values
|
||||
* Added filesystem check: Send notification if data directory is read-only, full or corrupt
|
||||
* Added translation: Dutch (thanks @kuilbop)
|
||||
|
||||
v0.9.8 (11/11/2024)
|
||||
-------------------
|
||||
* Added experimental setting: 5 locations support (use with caution)
|
||||
* Adjusted INFO and ERROR logging to prepare for the v1.0 "stable" release
|
||||
* Adjusted weather data / current time matching:
|
||||
This fixes an android sleep/wakeup problem if device is set to "disable wifi/network on sleep"
|
||||
* Refactored HTTP error handling to fix an issue if there is no network connection available for 24+ hours
|
||||
* Fixed "current.[LOCID]" window properties when using local location timezone
|
||||
* Minor code cleanup
|
||||
|
||||
v0.9.7 (29/10/2024)
|
||||
-------------------
|
||||
* Changed strftime() parameter to fix LOCALE (LC_TIME) difference on macOS
|
||||
* Changed localization mapping to match new strftime() value
|
||||
* Revert some addon localization strings to be global again
|
||||
|
||||
v0.9.6 (27/10/2024)
|
||||
-------------------
|
||||
* Moved global localization strings (e.g. Monday, Mon, NNE) from KODI directly to the addon:
|
||||
Workaround for broken labels when changing languages on macOS
|
||||
* Fixed "current.[LOCID]" in advanced mode
|
||||
* Fixed a rare case of Addon & KODI service running simultaneously on startup
|
||||
|
||||
v0.9.5 (26/10/2024)
|
||||
-------------------
|
||||
* Fixed exception when disabling, uninstalling or upgrading the addon
|
||||
|
||||
v0.9.4 (25/10/2024)
|
||||
-------------------
|
||||
* Added day & night weather icon support for KODIs built-in weather codes
|
||||
* Added addon operation modes: Compatibility (default) or Advanced <onload>SetProperty(openmeteo,skin.xyz,weather)</onload>
|
||||
* Added "current.location" to fix KODIs built-in "weather.location" infolabel
|
||||
* Localization mapping is now dynamic to fix broken labels when changing languages
|
||||
* Enforce utf8 with open() to fix exception when changing languages
|
||||
* Changed "Decimal places" setting to sliders
|
||||
* ImageMagick: Use PNG32 to fix broken graph if value is zero or below threshold
|
||||
|
||||
v0.9.3 (22/10/2024)
|
||||
-------------------
|
||||
* KODI only:
|
||||
* Changed daily & hourly index to start at 1
|
||||
* Changed daily LowTemperature and HighTemperature
|
||||
* Changed the content of precipitation to precipitationprobability
|
||||
* Added new "precip" window property for the actual precipitation
|
||||
* Adapted some window properties and values+units from Multi Weather
|
||||
|
||||
* Fixed some graph transparency glitches
|
||||
* Workaround for KODI's DayX.OutlookIcon: Add static path "resource://resource.images.weathericons.default"
|
||||
* Fixed a rare case of endless geoip lookups if KODI's userdata filesystem is full, read-only or broken
|
||||
* Disabled "hour.X" window properties by default to improve performance (hardly used by skins)
|
||||
* Added assets: screenshots & banner
|
||||
|
||||
v0.9.2 (30/09/2024)
|
||||
-------------------
|
||||
* Added translation: French (thanks @nic020)
|
||||
* Added check if addon is enabled
|
||||
* Fixed DayX.LowTemp, DayX.HighTemp
|
||||
* Fixed some graphs/autoscaling
|
||||
* Fixed exception when changing languages (possibly a KODI bug)
|
||||
* Added moonphase image
|
||||
* Added map history setting
|
||||
|
||||
v0.9.1 (15/09/2024)
|
||||
-------------------
|
||||
* Public release
|
||||
* Some code cleanup
|
||||
|
||||
v0.9.0 (06/09/2024)
|
||||
-------------------
|
||||
* Initial release
|
||||
282
Kodi/Lenovo/addons/weather.openmeteo/LICENSE.txt
Normal file
@@ -0,0 +1,282 @@
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) 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
|
||||
this service 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 make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. 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.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
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
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the 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 a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE 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.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
-------------------------------------------------------------------------
|
||||
19
Kodi/Lenovo/addons/weather.openmeteo/README.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Open-Meteo Weather
|
||||
(weather.openmeteo)
|
||||
|
||||
### Features
|
||||
|
||||
- No account or API key required
|
||||
- Geolocation (only on first run)
|
||||
- Current, hourly and daily weather
|
||||
- Maps: Radar, Infrared, Temperature, Wind Maps, ...
|
||||
12h history for map animations (Requires skin support)
|
||||
- Airquality: AQI, PM25, PM10, ... (Requires skin support)
|
||||
- Pollen: Alder, Birch, Grass, ... (Requires skin support, Europe only)
|
||||
- Sun and moon data (Requires skin support)
|
||||
- Weather alert notifications
|
||||
- Weather alert properties for skins (Requires skin support)
|
||||
- Graphs (Requires skin support)
|
||||
Special window properties that can be used for graphs (e.g. Example)
|
||||
- WMO weather codes support (Requires skin support)
|
||||
WMO icons are more precise than KODIs builtin codes
|
||||
48
Kodi/Lenovo/addons/weather.openmeteo/addon.xml
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<addon id="weather.openmeteo" name="Open-Meteo" version="1.0.14" provider-name="OpenHT">
|
||||
<requires>
|
||||
<import addon="xbmc.python" version="3.0.0"/>
|
||||
<import addon="script.module.requests" version="2.27.1+matrix.1"/>
|
||||
<import addon="script.module.dateutil" version="2.8.1+matrix.1"/>
|
||||
<import addon="script.module.pytz" version="2023.3.0+matrix.1"/>
|
||||
<import addon="script.module.pil" version="1.1.7"/>
|
||||
</requires>
|
||||
<extension point="xbmc.python.weather" library="default.py"/>
|
||||
<extension point="xbmc.service" library="service.py" />
|
||||
<extension point="xbmc.addon.metadata">
|
||||
<summary lang="cs_CZ">Predpoveď počasia z Open-Meteo</summary>
|
||||
<description lang="cs_CZ">Počasie, mapy, kvalita vzduchu a pyľové údaje z open-meteo.com, rainviewer.com, weather.gc.ca a met.no</description>
|
||||
<summary lang="de_DE">Wettervorhersage von Open-Meteo</summary>
|
||||
<description lang="de_DE">Wetter, Karten, Luftqualität und Pollen Vorhersage von open-meteo.com, rainviewer.com, weather.gc.ca und met.no</description>
|
||||
<summary lang="en_GB">Weather forecast from Open-Meteo</summary>
|
||||
<description lang="en_GB">Weather, maps, airquality and pollen forecast from open-meteo.com, rainviewer.com, weather.gc.ca and met.no</description>
|
||||
<summary lang="hu_HU">Időjárás-előrejelzés az Open-Meteo-ból</summary>
|
||||
<description lang="hu_HU">Időjárás, térképek, levegőminőség és pollenelőrejelzés az open-meteo.com, a rainviewer.com, a weather.gc.ca és a met.no oldalakról</description>
|
||||
<summary lang="id_ID">Prakiraan cuaca oleh Open-Meteo</summary>
|
||||
<description lang="id_ID">Ramalan cuaca, peta, kualitas udara, dan serbuk sari dari open-meteo.com, rainviewer.com, weather.gc.ca dan met.no</description>
|
||||
<summary lang="sk_SK">Predpoveď počasia z Open-Meteo</summary>
|
||||
<description lang="sk_SK">Počasie, mapy, kvalita vzduchu a peľové údaje z open-meteo.com, rainviewer.com, weather.gc.ca a met.no</description>
|
||||
<summary lang="ta_IN">திறந்த-மெட்டியோவிலிருந்து வானிலை முன்னறிவிப்பு</summary>
|
||||
<description lang="ta_IN">திறந்த-மெட்டியோ.காம், ரெயின்வியூவர்.காம், வானிலை. GC.CA மற்றும் MET.NO இலிருந்து வானிலை, வரைபடங்கள், காற்றோட்டம் மற்றும் மகரந்த முன்னறிவிப்பு</description>
|
||||
<summary lang="uk_UA">Прогноз погоди від Open-Meteo</summary>
|
||||
<description lang="uk_UA">Прогноз погоди, мап , якість повітря та пилку від open-meteo.com, rainviewer.com, weather.gc.ca та met.no</description>
|
||||
<summary lang="zh_CN">Open-Meteo 提供的天气预报</summary>
|
||||
<description lang="zh_CN">天气、地图、空气质量和花粉预报来自 open-meteo.com、rainviewer.com、weather.gc.ca 和 met.no</description>
|
||||
<platform>all</platform>
|
||||
<license>GPL-2.0-or-later</license>
|
||||
<forum>https://forum.kodi.tv/forumdisplay.php?fid=155</forum>
|
||||
<website>https://openht.org</website>
|
||||
<email>support@openht.org</email>
|
||||
<source>https://github.com/bkury/weather.openmeteo</source>
|
||||
<assets>
|
||||
<icon>resources/icon.png</icon>
|
||||
<banner>resources/banner.png</banner>
|
||||
<fanart>resources/fanart.jpg</fanart>
|
||||
<screenshot>resources/screenshot1.jpg</screenshot>
|
||||
<screenshot>resources/screenshot2.jpg</screenshot>
|
||||
<screenshot>resources/screenshot3.jpg</screenshot>
|
||||
<screenshot>resources/screenshot4.jpg</screenshot>
|
||||
<screenshot>resources/screenshot5.jpg</screenshot>
|
||||
</assets>
|
||||
</extension>
|
||||
</addon>
|
||||
9
Kodi/Lenovo/addons/weather.openmeteo/default.py
Normal file
@@ -0,0 +1,9 @@
|
||||
import sys
|
||||
|
||||
from lib import weather
|
||||
from lib import config
|
||||
|
||||
if (__name__ == '__main__'):
|
||||
config.init(cache=True)
|
||||
weather.Main(sys.argv[1])
|
||||
|
||||
286
Kodi/Lenovo/addons/weather.openmeteo/lib/api.py
Normal file
@@ -0,0 +1,286 @@
|
||||
import os
|
||||
import io
|
||||
import socket
|
||||
import json
|
||||
import requests
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
|
||||
from PIL import Image
|
||||
from pathlib import Path
|
||||
from requests.adapters import HTTPAdapter, Retry
|
||||
|
||||
from . import config
|
||||
from . import utils
|
||||
from . import weather
|
||||
|
||||
# DNS cache
|
||||
old_getaddrinfo = socket.getaddrinfo
|
||||
|
||||
def new_getaddrinfo(*args):
|
||||
try:
|
||||
return config.dnscache[args]
|
||||
except KeyError:
|
||||
r = old_getaddrinfo(*args)
|
||||
config.dnscache[args] = r
|
||||
return r
|
||||
|
||||
socket.getaddrinfo = new_getaddrinfo
|
||||
|
||||
# Requests
|
||||
r = Retry(total=2, backoff_factor=1, status_forcelist=[500, 502, 503, 504])
|
||||
s = requests.Session()
|
||||
s.headers.update(config.addon_ua)
|
||||
s.mount('https://', HTTPAdapter(max_retries=r))
|
||||
|
||||
# Network
|
||||
def network():
|
||||
if config.neterr > 10:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
# Get url
|
||||
def geturl(url, head=False):
|
||||
|
||||
# Network timeout
|
||||
if network():
|
||||
timeout = 6
|
||||
else:
|
||||
timeout = 1
|
||||
|
||||
# Download
|
||||
try:
|
||||
if head:
|
||||
utils.log(f'Checking: {url}', 3)
|
||||
r = s.head(url, timeout=timeout)
|
||||
else:
|
||||
utils.log(f'Download: {url}', 3)
|
||||
r = s.get(url, timeout=timeout)
|
||||
|
||||
except Exception as e:
|
||||
utils.log(f'Download: {url} ({e})', 3)
|
||||
config.dnscache = {}
|
||||
config.neterr += 1
|
||||
return None
|
||||
|
||||
else:
|
||||
config.neterr = 0
|
||||
|
||||
if r.ok:
|
||||
utils.log(f'Download: {url} ({r.status_code})', 3)
|
||||
return r.content
|
||||
else:
|
||||
utils.log(f'Download: {url} ({r.status_code})', 2)
|
||||
config.dnscache = {}
|
||||
return None
|
||||
|
||||
# Get data
|
||||
def getdata(type, loc, map=None):
|
||||
|
||||
# URL
|
||||
if type == 'weather':
|
||||
url = config.map_api.get(type).format(map[0], map[1])
|
||||
elif type == 'airquality':
|
||||
url = config.map_api.get(type).format(map[0], map[1])
|
||||
elif type == 'sun':
|
||||
url = config.map_api.get(type).format(map[0], map[1], map[2])
|
||||
elif type == 'moon':
|
||||
url = config.map_api.get(type).format(map[0], map[1], map[2])
|
||||
|
||||
# Weather
|
||||
file = f'{config.addon_cache}/{loc}/{type}.json'
|
||||
data = geturl(url)
|
||||
|
||||
if data:
|
||||
with open(Path(file), 'wb') as f:
|
||||
f.write(data)
|
||||
|
||||
# Get Map ( 0:loc, 1:type, 2:count, 3:z, 4:x, 5:y, 6:xtile 7:ytile, 8:path, 9:time, 10-13 bbox )
|
||||
def getmap(map, head=False):
|
||||
|
||||
if map[1] == 'osm':
|
||||
url = config.map_api.get(map[1]).format(map[3], map[4], map[5])
|
||||
elif map[1] == 'rvradar':
|
||||
url = config.map_api.get(map[1]).format(map[8], map[3], map[4], map[5])
|
||||
elif map[1] == 'rvsatellite':
|
||||
url = config.map_api.get(map[1]).format(map[8], map[3], map[4], map[5])
|
||||
elif map[1] == 'gctemp':
|
||||
url = config.map_api.get(map[1]).format(map[10], map[11], map[12], map[13])
|
||||
elif map[1] == 'gcwind':
|
||||
url = config.map_api.get(map[1]).format(map[10], map[11], map[12], map[13])
|
||||
|
||||
# HEAD
|
||||
if head:
|
||||
data = geturl(url, head=True)
|
||||
return data
|
||||
|
||||
# GET
|
||||
data = geturl(url)
|
||||
|
||||
if data:
|
||||
config.mapcache[map[1]][map[2]] = data
|
||||
else:
|
||||
with open(Path(f'{config.addon_path}/resources/tile.png'), 'rb') as f:
|
||||
tile = f.read()
|
||||
|
||||
config.mapcache[map[1]][map[2]] = tile
|
||||
|
||||
# Map merge
|
||||
def mapmerge(map):
|
||||
image = Image.new("RGBA", (756, 756), None)
|
||||
|
||||
for item in map:
|
||||
|
||||
try:
|
||||
tile = Image.open(io.BytesIO(config.mapcache[item[1]][item[2]]))
|
||||
except:
|
||||
tile = Image.open(f'{config.addon_path}/resources/tile.png')
|
||||
else:
|
||||
image.paste( tile, (item[6], item[7]))
|
||||
|
||||
if map[0][1] == 'osm':
|
||||
image.save(f'{config.addon_cache}/{map[0][0]}/{map[0][1]}.png')
|
||||
else:
|
||||
image.save(f'{config.addon_cache}/{map[0][0]}/{map[0][1]}_{map[0][9]}.png')
|
||||
|
||||
# Get rvdata
|
||||
def getrvindex(type):
|
||||
try:
|
||||
data = json.loads(geturl(config.map_api.get('rvindex')))
|
||||
map = config.map_layers.get(type)
|
||||
time = data[map[0]][map[1]][-1]['time']
|
||||
path = data[map[0]][map[1]][-1]['path']
|
||||
except:
|
||||
return None, None
|
||||
else:
|
||||
return time, path
|
||||
|
||||
# Get location (GeoIP)
|
||||
def getloc(locid):
|
||||
utils.log(f'Geolocation ...')
|
||||
utils.setsetting('geoip', 'true')
|
||||
|
||||
# Get location
|
||||
try:
|
||||
data = json.loads(geturl(config.map_api.get('geoip')))
|
||||
city = data['city']
|
||||
region = data.get('region_name')
|
||||
country = data.get('country_code')
|
||||
|
||||
# Search
|
||||
data = json.loads(geturl(config.map_api.get('search').format(city)))
|
||||
location = data['results'][0]
|
||||
|
||||
for item in data['results']:
|
||||
|
||||
if country and region:
|
||||
if country in location['country_code'] and region in location['admin1']:
|
||||
location = item
|
||||
break
|
||||
|
||||
if country:
|
||||
if country in location['country_code']:
|
||||
location = item
|
||||
break
|
||||
except Exception as e:
|
||||
utils.log(f'Geolocation: Unknown ({e})')
|
||||
else:
|
||||
utils.log(f'Geolocation: {location["name"]}, {location["admin1"]}, {location["country_code"]} [{location["latitude"]}, {location["longitude"]}]')
|
||||
utils.setsetting(f'loc{locid}', f'{location["name"]}, {location["admin1"]}, {location["country_code"]}')
|
||||
utils.setsetting(f'loc{locid}lat', str(location["latitude"]))
|
||||
utils.setsetting(f'loc{locid}lon', str(location["longitude"]))
|
||||
utils.setsetting(f'loc{locid}tz', str(location["timezone"]))
|
||||
|
||||
# Clear location
|
||||
def clearloc(locid, last=False):
|
||||
if last:
|
||||
utils.setsetting(f'loc{locid}data', '321318000')
|
||||
utils.setsetting(f'loc{locid}map', '321318000')
|
||||
utils.setsetting(f'loc{locid}rv', '321318000')
|
||||
utils.setsetting(f'loc{locid}gc', '321318000')
|
||||
else:
|
||||
utils.setsetting(f'loc{locid}', '')
|
||||
utils.setsetting(f'loc{locid}user', '')
|
||||
utils.setsetting(f'loc{locid}alert', 'true')
|
||||
utils.setsetting(f'loc{locid}utz', 'false')
|
||||
utils.setsetting(f'loc{locid}tz', '')
|
||||
utils.setsetting(f'loc{locid}lat', '0')
|
||||
utils.setsetting(f'loc{locid}lon', '0')
|
||||
|
||||
# Set location
|
||||
def setloc (locid):
|
||||
utils.log(f'Search dialog ...')
|
||||
|
||||
dialog = xbmcgui.Dialog()
|
||||
input = utils.setting(f'loc{locid}')
|
||||
keyboard = xbmc.Keyboard(input, utils.loc(14024), False)
|
||||
keyboard.doModal()
|
||||
|
||||
if keyboard.isConfirmed():
|
||||
search = keyboard.getText()
|
||||
|
||||
# No changes
|
||||
if search == input:
|
||||
utils.log(f'[LOC{locid}] No changes')
|
||||
|
||||
# Remove location
|
||||
elif search == '':
|
||||
check = utils.setting(f'loc{int(locid)+1}')
|
||||
|
||||
if not check:
|
||||
utils.log(f'[LOC{locid}] Removed')
|
||||
clearloc(locid)
|
||||
clearloc(locid, True)
|
||||
|
||||
# Search location
|
||||
else:
|
||||
try:
|
||||
locs = []
|
||||
url = config.map_api.get('search').format(search)
|
||||
data = json.loads(geturl(url))['results']
|
||||
except:
|
||||
utils.log('[LOC{locid}] No results')
|
||||
dialog.ok('Open-meteo', utils.loc(284))
|
||||
else:
|
||||
for item in data:
|
||||
li = xbmcgui.ListItem(f'{item.get("name")}, {item.get("admin1")}, {item.get("country_code")} (Lat: {item.get("latitude")}, Lon: {item.get("longitude")})')
|
||||
locs.append(li)
|
||||
|
||||
select = dialog.select(utils.loc(396), locs, useDetails=True)
|
||||
|
||||
if select != -1:
|
||||
|
||||
# Cleanup cache dir
|
||||
dir = f'{config.addon_cache}/{locid}'
|
||||
files = sorted(list(Path(dir).glob('*')))
|
||||
|
||||
for file in files:
|
||||
os.remove(file)
|
||||
|
||||
# Set location
|
||||
utils.log(f'Location {locid}: {data[select].get("name")}, {data[select].get("admin1")}, {data[select].get("country_code")} {data[select].get("latitude")} {data[select].get("longitude")}')
|
||||
utils.setsetting(f'loc{locid}', f'{data[select].get("name")}, {data[select].get("admin1")}, {data[select].get("country_code")}')
|
||||
utils.setsetting(f'loc{locid}lat', data[select]["latitude"])
|
||||
utils.setsetting(f'loc{locid}lon', data[select]["longitude"])
|
||||
utils.setsetting(f'loc{locid}tz', data[select]["timezone"])
|
||||
|
||||
# Wait for settings dialog
|
||||
while xbmcgui.getCurrentWindowDialogId() == 10140:
|
||||
utils.log(f'Waiting for settings dialog ...')
|
||||
utils.monitor.waitForAbort(1)
|
||||
|
||||
if utils.monitor.abortRequested():
|
||||
return
|
||||
|
||||
# Cleanup lastupdate
|
||||
clearloc(locid, True)
|
||||
|
||||
# Refresh
|
||||
if int(utils.settingrpc("weather.currentlocation")) == int(locid):
|
||||
weather.Main(str(locid), mode='download')
|
||||
weather.Main(str(locid), mode='update')
|
||||
else:
|
||||
weather.Main(str(locid), mode='download')
|
||||
weather.Main(str(locid), mode='updatelocs')
|
||||
|
||||
802
Kodi/Lenovo/addons/weather.openmeteo/lib/config.py
Normal file
@@ -0,0 +1,802 @@
|
||||
import os
|
||||
import xbmc
|
||||
import xbmcvfs
|
||||
import xbmcaddon
|
||||
|
||||
from . import utils
|
||||
|
||||
# API
|
||||
map_api = {
|
||||
'search': 'https://geocoding-api.open-meteo.com/v1/search?name={}&count=10&language=en&format=json',
|
||||
'geoip': 'https://api.openht.org/geoipweather',
|
||||
'weather': 'https://api.open-meteo.com/v1/forecast?latitude={}&longitude={}¤t=temperature_2m,relative_humidity_2m,apparent_temperature,is_day,precipitation,weather_code,cloud_cover,pressure_msl,surface_pressure,wind_speed_10m,wind_direction_10m,wind_gusts_10m,dew_point_2m,precipitation_probability,visibility,uv_index,direct_radiation&hourly=temperature_2m,relative_humidity_2m,dew_point_2m,apparent_temperature,precipitation_probability,precipitation,weather_code,pressure_msl,surface_pressure,cloud_cover,visibility,wind_speed_10m,wind_direction_10m,wind_gusts_10m,uv_index,is_day,direct_radiation&daily=weather_code,temperature_2m_max,temperature_2m_min,sunrise,sunset,daylight_duration,sunshine_duration,uv_index_max,precipitation_hours&timeformat=unixtime&forecast_days=9&past_days=2',
|
||||
'airquality': 'https://air-quality-api.open-meteo.com/v1/air-quality?latitude={}&longitude={}¤t=european_aqi,us_aqi,pm10,pm2_5,carbon_monoxide,ozone,dust,nitrogen_dioxide,sulphur_dioxide,alder_pollen,birch_pollen,grass_pollen,mugwort_pollen,olive_pollen,ragweed_pollen&hourly=pm10,pm2_5,carbon_monoxide,ozone,dust,european_aqi,us_aqi,nitrogen_dioxide,sulphur_dioxide,alder_pollen,birch_pollen,grass_pollen,mugwort_pollen,olive_pollen,ragweed_pollen&timeformat=unixtime&forecast_days=7&past_days=2',
|
||||
'sun': 'https://api.met.no/weatherapi/sunrise/3.0/sun?lat={}&lon={}&date={}',
|
||||
'moon': 'https://api.met.no/weatherapi/sunrise/3.0/moon?lat={}&lon={}&date={}',
|
||||
'osm': 'https://tile.openstreetmap.org/{}/{}/{}.png',
|
||||
'rvindex': 'https://api.rainviewer.com/public/weather-maps.json',
|
||||
'rvradar': 'https://tilecache.rainviewer.com{}/256/{}/{}/{}/4/1_1.png',
|
||||
'rvsatellite': 'https://tilecache.rainviewer.com{}/256/{}/{}/{}/0/0_0.png',
|
||||
'gctemp': 'https://geo.weather.gc.ca/geomet?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetMap&BBOX={},{},{},{}&CRS=EPSG:4326&WIDTH=256&HEIGHT=256&LAYERS=GDPS.ETA_TT&FORMAT=image/png',
|
||||
'gcwind': 'https://geo.weather.gc.ca/geomet?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetMap&BBOX={},{},{},{}&CRS=EPSG:4326&WIDTH=256&HEIGHT=256&LAYERS=GDPS.ETA_UU&FORMAT=image/png',
|
||||
}
|
||||
|
||||
# Limits
|
||||
maxdays = 8
|
||||
mindays = 2
|
||||
maxhours = 73
|
||||
minhours = 25
|
||||
mindata = 0
|
||||
maxdata = 300
|
||||
|
||||
# ADDON
|
||||
addon_ua = {'user-agent': f'{xbmc.getUserAgent()} (weather.openmeteo v{utils.xbmcaddon.Addon().getAddonInfo("version")}, support@openht.org)'}
|
||||
addon_info = f'{xbmc.getUserAgent()} (weather.openmeteo v{utils.xbmcaddon.Addon().getAddonInfo("version")})'
|
||||
addon_data = xbmcvfs.translatePath(xbmcaddon.Addon().getAddonInfo('profile'))
|
||||
addon_cache = xbmcvfs.translatePath(xbmcaddon.Addon().getAddonInfo('profile')) + "cache"
|
||||
addon_icons = xbmcvfs.translatePath(xbmcaddon.Addon().getAddonInfo('path')) + "resources/icons"
|
||||
addon_path = xbmcvfs.translatePath(xbmcaddon.Addon().getAddonInfo('path'))
|
||||
neterr = 0
|
||||
|
||||
# Cache
|
||||
dnscache = {}
|
||||
mapcache = {}
|
||||
|
||||
# Modules (disabled)
|
||||
# sys.path.append(f'{addon_path}/lib/modules')
|
||||
|
||||
# Mapping (Weather)
|
||||
map_weather = [
|
||||
[ "current", [ 'latitude' ], [ 'current', 'latitude' ], 'round2' ],
|
||||
[ "current", [ 'longitude' ], [ 'current', 'longitude' ], 'round2' ],
|
||||
[ "current", [ 'elevation' ], [ 'current', 'elevation' ], 'round' ],
|
||||
|
||||
[ "current", [ 'current_units', 'wind_speed_10m' ], [ 'unit', 'speed' ], 'unitspeed' ],
|
||||
[ "current", [ 'current_units', 'temperature_2m' ], [ 'unit', 'temperature' ], 'unittemperature' ],
|
||||
[ "current", [ 'current_units', 'precipitation' ], [ 'unit', 'precipitation' ], 'unitprecipitation' ],
|
||||
[ "current", [ 'current_units', 'pressure_msl' ], [ 'unit', 'pressure' ], 'unitpressure' ],
|
||||
[ "current", [ 'current_units', 'relative_humidity_2m' ], [ 'unit', 'percent' ], 'unitpercent' ],
|
||||
[ "current", [ 'hourly_units', 'visibility' ], [ 'unit', 'distance' ], 'unitdistance' ],
|
||||
[ "current", [ 'hourly_units', 'direct_radiation' ], [ 'unit', 'radiation' ], 'unitradiation' ],
|
||||
[ "current", [ 'hourly_units', 'direct_radiation' ], [ 'unit', 'solarradiation' ], 'unitradiation' ],
|
||||
|
||||
[ "current", [ 'current', "time" ], [ 'current', "date" ], "date" ],
|
||||
[ "hourly", [ 'hourly', "time" ], [ 'hourly', "date" ], "date" ],
|
||||
[ "hourly", [ 'hourly', "time" ], [ 'hourly', "shortdate" ], "date" ],
|
||||
|
||||
[ "current", [ 'current', "time" ], [ 'current', "time" ], "time" ],
|
||||
[ "hourly", [ 'hourly', "time" ], [ 'hourly', "time" ], "time" ],
|
||||
|
||||
[ "current", [ 'current', "time" ], [ 'current', "hour" ], "hour" ],
|
||||
[ "hourly", [ 'hourly', "time" ], [ 'hourly', "hour" ], "hour" ],
|
||||
|
||||
[ "current", [ 'current', "temperature_2m" ], [ 'current', "temperatureaddon" ], "temperature" ],
|
||||
[ "current", [ 'current', "apparent_temperature" ], [ 'current', "feelslikeaddon" ], "temperature" ],
|
||||
[ "current", [ 'current', "dew_point_2m" ], [ 'current', "dewpointaddon" ], "temperature" ],
|
||||
|
||||
[ "current", [ 'current', "temperature_2m" ], [ 'current', "temperature" ], "temperaturekodi" ],
|
||||
[ "currentkodi",[ 'current', "temperature_2m" ], [ 'current', "temperature" ], "round" ],
|
||||
[ "current", [ 'current', "apparent_temperature" ], [ 'current', "feelslike" ], "temperaturekodi" ],
|
||||
[ "currentkodi",[ 'current', "apparent_temperature" ], [ 'current', "feelslike" ], "round" ],
|
||||
[ "current", [ 'current', "dew_point_2m" ], [ 'current', "dewpoint" ], "temperaturekodi" ],
|
||||
[ "currentkodi",[ 'current', "dew_point_2m" ], [ 'current', "dewpoint" ], "round" ],
|
||||
|
||||
[ "hourly", [ 'hourly', "temperature_2m" ], [ 'hourly', "temperature" ], "temperatureunit" ],
|
||||
[ "hourlyskin", [ 'hourly', "temperature_2m" ], [ 'hourly', "temperature" ], "temperature" ],
|
||||
[ "hourly", [ 'hourly', "temperature_2m" ], [ 'hourly', "temperaturegraph" ], "graph", "50", "temperature" ],
|
||||
|
||||
[ "hourly", [ 'hourly', "apparent_temperature" ], [ 'hourly', "feelslike" ], "temperatureunit" ],
|
||||
[ "hourlyskin", [ 'hourly', "apparent_temperature" ], [ 'hourly', "feelslike" ], "temperature" ],
|
||||
[ "hourly", [ 'hourly', "apparent_temperature" ], [ 'hourly', "feelslikegraph" ], "graph", "50", "temperature" ],
|
||||
|
||||
[ "hourly", [ 'hourly', "dew_point_2m" ], [ 'hourly', "dewpoint" ], "temperatureunit" ],
|
||||
[ "hourlyskin", [ 'hourly', "dew_point_2m" ], [ 'hourly', "dewpoint" ], "temperature" ],
|
||||
[ "hourly", [ 'hourly', "dew_point_2m" ], [ 'hourly', "dewpointgraph" ], "graph", "50", "temperature" ],
|
||||
|
||||
[ "current", [ 'current', "relative_humidity_2m" ], [ 'current', "humidity" ], "%" ],
|
||||
[ "currentkodi",[ 'current', "relative_humidity_2m" ], [ 'current', "humidity" ], "round" ],
|
||||
[ "hourly", [ 'hourly', "relative_humidity_2m" ], [ 'hourly', "humidity" ], "roundpercent" ],
|
||||
[ "hourlyskin", [ 'hourly', "relative_humidity_2m" ], [ 'hourly', "humidity" ], "round" ],
|
||||
[ "hourly", [ 'hourly', "relative_humidity_2m" ], [ 'hourly', "humiditygraph" ], "graph", "100" ],
|
||||
|
||||
[ "current", [ 'current', "precipitation" ], [ 'current', "precip" ], "precipitation" ],
|
||||
[ "hourly", [ 'hourly', "precipitation" ], [ 'hourly', "precip" ], "precipitation" ],
|
||||
[ "hourly", [ 'hourly', "precipitation" ], [ 'hourly', "precipitationgraph" ], "graph", "100" ],
|
||||
[ "current", [ 'current', "precipitation_probability" ], [ 'current', "precipitation" ], "roundpercent" ],
|
||||
[ "hourly", [ 'hourly', "precipitation_probability" ], [ 'hourly', "precipitation" ], "roundpercent" ],
|
||||
[ "hourly", [ 'hourly', "precipitation_probability" ], [ 'hourly', "precipitationprobabilitygraph" ], "graph", "100" ],
|
||||
|
||||
[ "currentskin",[ 'current', "precipitation" ], [ 'current', "precipitation" ], "precipitation" ],
|
||||
[ "hourlyskin", [ 'hourly', "precipitation" ], [ 'hourly', "precipitation" ], "precipitation" ],
|
||||
[ "currentskin",[ 'current', "precipitation_probability" ], [ 'current', "precipitationprobability" ], "round" ],
|
||||
[ "hourlyskin", [ 'hourly', "precipitation_probability" ], [ 'hourly', "precipitationprobability" ], "round" ],
|
||||
|
||||
[ "current", [ 'current', "pressure_msl" ], [ 'current', "pressure" ], "pressure" ],
|
||||
[ "hourly", [ 'hourly', "pressure_msl" ], [ 'hourly', "pressure" ], "pressure" ],
|
||||
[ "hourly", [ 'hourly', "pressure_msl" ], [ 'hourly', "pressuregraph" ], "graph", "100", "pressure" ],
|
||||
|
||||
[ "current", [ 'current', "surface_pressure" ], [ 'current', "pressuresurface" ], "pressure" ],
|
||||
[ "hourly", [ 'hourly', "surface_pressure" ], [ 'hourly', "pressuresurface" ], "pressure" ],
|
||||
[ "hourly", [ 'hourly', "surface_pressure" ], [ 'hourly', "pressuresurfacegraph" ], "graph", "100", "pressure" ],
|
||||
|
||||
[ "current", [ 'current', "wind_speed_10m" ], [ 'current', "wind" ], "windkodi" ],
|
||||
[ "currentkodi",[ 'current', "wind_speed_10m" ], [ 'current', "wind" ], "round" ],
|
||||
|
||||
[ "current", [ 'current', "wind_speed_10m" ], [ 'current', "windaddon" ], "windaddon" ],
|
||||
[ "current", [ 'current', "wind_speed_10m" ], [ 'current', "windspeed" ], "speed" ],
|
||||
[ "hourly", [ 'hourly', "wind_speed_10m" ], [ 'hourly', "windspeed" ], "speed" ],
|
||||
[ "hourly", [ 'hourly', "wind_speed_10m" ], [ 'hourly', "windspeedgraph" ], "graph", "100" ],
|
||||
|
||||
[ "current", [ 'current', "wind_direction_10m" ], [ 'current', "winddirection" ], "direction" ],
|
||||
[ "current", [ 'current', "wind_direction_10m" ], [ 'current', "winddirectiondegree" ], "round" ],
|
||||
[ "hourly", [ 'hourly', "wind_direction_10m" ], [ 'hourly', "winddirection" ], "direction" ],
|
||||
[ "hourly", [ 'hourly', "wind_direction_10m" ], [ 'hourly', "winddirectiondegree" ], "round" ],
|
||||
|
||||
[ "current", [ 'current', "wind_gusts_10m" ], [ 'current', "windgust" ], "speed" ],
|
||||
[ "hourly", [ 'hourly', "wind_gusts_10m" ], [ 'hourly', "windgust" ], "speed" ],
|
||||
[ "hourly", [ 'hourly', "wind_gusts_10m" ], [ 'hourly', "windgustgraph" ], "graph", "100" ],
|
||||
|
||||
[ "current", [ 'current', "weather_code" ], [ 'current', "condition" ], "wmocond" ],
|
||||
[ "current", [ 'current', "weather_code" ], [ 'current', "outlookicon" ], "image" ],
|
||||
[ "current", [ 'current', "weather_code" ], [ 'current', "outlookiconwmo" ], "wmoimage" ],
|
||||
[ "current", [ 'current', "weather_code" ], [ 'current', "fanartcode" ], "code" ],
|
||||
[ "current", [ 'current', "weather_code" ], [ 'current', "fanartcodewmo" ], "wmocode" ],
|
||||
[ "hourly", [ 'hourly', "weather_code" ], [ 'hourly', "outlook" ], "wmocond" ],
|
||||
[ "hourly", [ 'hourly', "weather_code" ], [ 'hourly', "outlookicon" ], "image" ],
|
||||
[ "hourly", [ 'hourly', "weather_code" ], [ 'hourly', "outlookiconwmo" ], "wmoimage" ],
|
||||
[ "hourly", [ 'hourly', "weather_code" ], [ 'hourly', "fanartcode" ], "code" ],
|
||||
[ "hourly", [ 'hourly', "weather_code" ], [ 'hourly', "fanartcodewmo" ], "wmocode" ],
|
||||
|
||||
[ "hourly", [ 'hourly', "weather_code" ], [ 'hourly', "condition" ], "wmocond" ],
|
||||
[ "hourly", [ 'hourly', "weather_code" ], [ 'hourly', "conditiongraph" ], "graph", "100" ],
|
||||
|
||||
[ "current", [ 'current', "cloud_cover" ], [ 'current', "cloudiness" ], "roundpercent" ],
|
||||
[ "currentskin",[ 'current', "cloud_cover" ], [ 'current', "cloudiness" ], "round" ],
|
||||
[ "hourly", [ 'hourly', "cloud_cover" ], [ 'hourly', "cloudiness" ], "roundpercent" ],
|
||||
[ "hourlyskin", [ 'hourly', "cloud_cover" ], [ 'hourly', "cloudiness" ], "round" ],
|
||||
[ "hourly", [ 'hourly', "cloud_cover" ], [ 'hourly', "cloudinessgraph" ], "graph", "100" ],
|
||||
|
||||
[ "current", [ 'current', "is_day" ], [ 'current', "isday" ], "bool" ],
|
||||
[ "hourly", [ 'hourly', "is_day" ], [ 'hourly', "isday" ], "bool" ],
|
||||
|
||||
[ "current", [ 'current', "visibility" ], [ 'current', "visibility" ], "distance" ],
|
||||
[ "hourly", [ 'hourly', "visibility" ], [ 'hourly', "visibility" ], "distance" ],
|
||||
[ "hourly", [ 'hourly', "visibility" ], [ 'hourly', "visibilitygraph" ], "graph", "100", "divide1000" ],
|
||||
|
||||
[ "current", [ 'current', "uv_index" ], [ 'current', "uvindex" ], "uvindex" ],
|
||||
[ "hourly", [ 'hourly', "uv_index" ], [ 'hourly', "uvindex" ], "uvindex" ],
|
||||
[ "hourly", [ 'hourly', "uv_index" ], [ 'hourly', "uvindexgraph" ], "graph", "10" ],
|
||||
|
||||
[ "current", [ 'current', "direct_radiation" ], [ 'current', "solarradiation" ], "radiation" ],
|
||||
[ "hourly", [ 'hourly', "direct_radiation" ], [ 'hourly', "solarradiation" ], "radiation" ],
|
||||
[ "hourly", [ 'hourly', "direct_radiation" ], [ 'hourly', "solarradiationgraph" ], "graph", "100", "divide10" ],
|
||||
|
||||
[ "daily", [ 'daily', "time" ], [ 'day', "title" ], "weekday" ],
|
||||
[ "daily", [ 'daily', "time" ], [ 'day', "date" ], "date" ],
|
||||
[ "daily", [ 'daily', "time" ], [ 'day', "shortdate" ], "date" ],
|
||||
[ "daily", [ 'daily', "time" ], [ 'day', "shortday" ], "weekdayshort" ],
|
||||
[ "daily", [ 'daily', "time" ], [ 'day', "longday" ], "weekday" ],
|
||||
[ "daily", [ 'daily', "weather_code" ], [ 'day', "condition" ], "wmocond" ],
|
||||
[ "daily", [ 'daily', "weather_code" ], [ 'day', "outlook" ], "wmocond" ],
|
||||
[ "daily", [ 'daily', "weather_code" ], [ 'day', "outlookicon" ], "image" ],
|
||||
[ "daily", [ 'daily', "weather_code" ], [ 'day', "outlookiconwmo" ], "wmoimage" ],
|
||||
[ "daily", [ 'daily', "weather_code" ], [ 'day', "fanartcode" ], "code" ],
|
||||
[ "daily", [ 'daily', "weather_code" ], [ 'day', "fanartcodewmo" ], "wmocode" ],
|
||||
|
||||
[ "daily", [ 'daily', "temperature_2m_max" ], [ 'day', "hightemp" ], "temperaturekodi" ],
|
||||
[ "daily", [ 'daily', "temperature_2m_min" ], [ 'day', "lowtemp" ], "temperaturekodi" ],
|
||||
[ "dailykodi", [ 'daily', "temperature_2m_max" ], [ 'day', "hightemp" ], "round" ],
|
||||
[ "dailykodi", [ 'daily', "temperature_2m_min" ], [ 'day', "lowtemp" ], "round" ],
|
||||
|
||||
[ "daily", [ 'daily', "temperature_2m_max" ], [ 'day', "hightemperature" ], "temperatureunit" ],
|
||||
[ "daily", [ 'daily', "temperature_2m_min" ], [ 'day', "lowtemperature" ], "temperatureunit" ],
|
||||
[ "dailyskin", [ 'daily', "temperature_2m_max" ], [ 'day', "hightemperature" ], "temperature" ],
|
||||
[ "dailyskin", [ 'daily', "temperature_2m_min" ], [ 'day', "lowtemperature" ], "temperature" ],
|
||||
|
||||
[ "daily", [ 'daily', "sunrise" ], [ 'day', "sunrise" ], "time" ],
|
||||
[ "daily", [ 'daily', "sunset" ], [ 'day', "sunset" ], "time" ],
|
||||
[ "current", [ 'daily', "sunrise", 1 ], [ 'today', "sunrise" ], "time" ],
|
||||
[ "current", [ 'daily', "sunset", 1 ], [ 'today', "sunset" ], "time" ],
|
||||
|
||||
[ "daily", [ 'daily', "daylight_duration" ], [ 'day', "daylight" ], "time" ],
|
||||
[ "daily", [ 'daily', "sunshine_duration" ], [ 'day', "sunshine" ], "time" ],
|
||||
[ "daily", [ 'daily', "precipitation_hours" ], [ 'day', "precipitationhours" ], "time" ],
|
||||
[ "daily", [ 'daily', "uv_index_max" ], [ 'day', "uvindex" ], "uvindex" ],
|
||||
]
|
||||
|
||||
map_airquality = [
|
||||
[ "current", [ 'current_units', "pm10" ], [ 'unit', "particles" ], "unitparticles" ],
|
||||
[ "current", [ 'current_units', "alder_pollen" ], [ 'unit', "pollen" ], "unitpollen" ],
|
||||
|
||||
[ "current", [ 'current', "time" ], [ 'current', "aqdate" ], "date" ],
|
||||
[ "current", [ 'current', "time" ], [ 'current', "aqtime" ], "time" ],
|
||||
[ "current", [ 'current', "time" ], [ 'current', "aqhour" ], "hour" ],
|
||||
|
||||
[ "current", [ 'current', "pm2_5" ], [ 'current', "pm25" ], "particles" ],
|
||||
[ "hourly", [ 'hourly', "pm2_5" ], [ 'hourly', "pm25" ], "particles" ],
|
||||
[ "hourly", [ 'hourly', "pm2_5" ], [ 'hourly', "pm25graph" ], "graph", "100" ],
|
||||
|
||||
[ "current", [ 'current', "pm10" ], [ 'current', "pm10" ], "particles" ],
|
||||
[ "hourly", [ 'hourly', "pm10" ], [ 'hourly', "pm10" ], "particles" ],
|
||||
[ "hourly", [ 'hourly', "pm10" ], [ 'hourly', "pm10graph" ], "graph", "100" ],
|
||||
|
||||
[ "current", [ 'current', "carbon_monoxide" ], [ 'current', "co" ], "particles" ],
|
||||
[ "hourly", [ 'hourly', "carbon_monoxide" ], [ 'hourly', "co" ], "particles" ],
|
||||
[ "hourly", [ 'hourly', "carbon_monoxide" ], [ 'hourly', "cograph" ], "graph", "100", "divide10" ],
|
||||
|
||||
[ "current", [ 'current', "ozone" ], [ 'current', "ozone" ], "particles" ],
|
||||
[ "hourly", [ 'hourly', "ozone" ], [ 'hourly', "ozone" ], "particles" ],
|
||||
[ "hourly", [ 'hourly', "ozone" ], [ 'hourly', "ozonegraph" ], "graph", "100" ],
|
||||
|
||||
[ "current", [ 'current', "dust" ], [ 'current', "dust" ], "particles" ],
|
||||
[ "hourly", [ 'hourly', "dust" ], [ 'hourly', "dust" ], "particles" ],
|
||||
[ "hourly", [ 'hourly', "dust" ], [ 'hourly', "dustgraph" ], "graph", "100" ],
|
||||
|
||||
[ "current", [ 'current', "nitrogen_dioxide" ], [ 'current', "no2" ], "particles" ],
|
||||
[ "hourly", [ 'hourly', "nitrogen_dioxide" ], [ 'hourly', "no2" ], "particles" ],
|
||||
[ "hourly", [ 'hourly', "nitrogen_dioxide" ], [ 'hourly', "no2graph" ], "graph", "100" ],
|
||||
|
||||
[ "current", [ 'current', "sulphur_dioxide" ], [ 'current', "so2" ], "particles" ],
|
||||
[ "hourly", [ 'hourly', "sulphur_dioxide" ], [ 'hourly', "so2" ], "particles" ],
|
||||
[ "hourly", [ 'hourly', "sulphur_dioxide" ], [ 'hourly', "so2graph" ], "graph", "100" ],
|
||||
|
||||
[ "current", [ 'current', "european_aqi" ], [ 'current', "aqieu" ], "round" ],
|
||||
[ "hourly", [ 'hourly', "european_aqi" ], [ 'hourly', "aqieu" ], "round" ],
|
||||
[ "hourly", [ 'hourly', "european_aqi" ], [ 'hourly', "aqieugraph" ], "graph", "100" ],
|
||||
|
||||
[ "current", [ 'current', "us_aqi" ], [ 'current', "aqius" ], "round" ],
|
||||
[ "hourly", [ 'hourly', "us_aqi" ], [ 'hourly', "aqius" ], "round" ],
|
||||
[ "hourly", [ 'hourly', "us_aqi" ], [ 'hourly', "aqiusgraph" ], "graph", "100" ],
|
||||
|
||||
[ "current", [ 'current', "alder_pollen" ], [ 'current', "alder" ], "pollen" ],
|
||||
[ "hourly", [ 'hourly', "alder_pollen" ], [ 'hourly', "alder" ], "pollen" ],
|
||||
[ "hourly", [ 'hourly', "alder_pollen" ], [ 'hourly', "aldergraph" ], "graph", "100" ],
|
||||
|
||||
[ "current", [ 'current', "birch_pollen" ], [ 'current', "birch" ], "pollen" ],
|
||||
[ "hourly", [ 'hourly', "birch_pollen" ], [ 'hourly', "birch" ], "pollen" ],
|
||||
[ "hourly", [ 'hourly', "birch_pollen" ], [ 'hourly', "birchgraph" ], "graph", "100" ],
|
||||
|
||||
[ "current", [ 'current', "grass_pollen" ], [ 'current', "grass" ], "pollen" ],
|
||||
[ "hourly", [ 'hourly', "grass_pollen" ], [ 'hourly', "grass" ], "pollen" ],
|
||||
[ "hourly", [ 'hourly', "grass_pollen" ], [ 'hourly', "grassgraph" ], "graph", "100" ],
|
||||
|
||||
[ "current", [ 'current', "mugwort_pollen" ], [ 'current', "mugwort" ], "pollen" ],
|
||||
[ "hourly", [ 'hourly', "mugwort_pollen" ], [ 'hourly', "mugwort" ], "pollen" ],
|
||||
[ "hourly", [ 'hourly', "mugwort_pollen" ], [ 'hourly', "mugwortgraph" ], "graph", "100" ],
|
||||
|
||||
[ "current", [ 'current', "olive_pollen" ], [ 'current', "olive" ], "pollen" ],
|
||||
[ "hourly", [ 'hourly', "olive_pollen" ], [ 'hourly', "olive" ], "pollen" ],
|
||||
[ "hourly", [ 'hourly', "olive_pollen" ], [ 'hourly', "olivegraph" ], "graph", "100" ],
|
||||
|
||||
[ "current", [ 'current', "ragweed_pollen" ], [ 'current', "ragweed" ], "pollen" ],
|
||||
[ "hourly", [ 'hourly', "ragweed_pollen" ], [ 'hourly', "ragweed" ], "pollen" ],
|
||||
[ "hourly", [ 'hourly', "ragweed_pollen" ], [ 'hourly', "ragweedgraph" ], "graph", "100" ],
|
||||
]
|
||||
|
||||
map_moon = [
|
||||
[ "current", [ 'properties', 'moonrise', 'time' ], [ 'today', "moonrise" ], "timeiso" ],
|
||||
[ "current", [ 'properties', 'moonrise', 'azimuth' ], [ 'today', "moonriseazimuth" ], "round" ],
|
||||
[ "current", [ 'properties', 'moonset', 'time' ], [ 'today', "moonset" ], "timeiso" ],
|
||||
[ "current", [ 'properties', 'moonset', 'azimuth' ], [ 'today', "moonsetazimuth" ], "round" ],
|
||||
[ "current", [ 'properties', 'moonphase' ], [ 'today', "moonphase" ], "moonphase" ],
|
||||
[ "current", [ 'properties', 'moonphase' ], [ 'today', "moonphaseimage" ], "moonphaseimage" ],
|
||||
[ "current", [ 'properties', 'moonphase' ], [ 'today', "moonphasedegree" ], "round" ],
|
||||
]
|
||||
|
||||
map = {
|
||||
'weather': map_weather,
|
||||
'airquality': map_airquality,
|
||||
'moon': map_moon,
|
||||
}
|
||||
|
||||
# Alert (Condition)
|
||||
map_alert_condition = {
|
||||
45: 'fog',
|
||||
48: 'fog',
|
||||
|
||||
51: 'rain',
|
||||
53: 'rain',
|
||||
55: 'rain',
|
||||
61: 'rain',
|
||||
63: 'rain',
|
||||
65: 'rain',
|
||||
80: 'rain',
|
||||
81: 'rain',
|
||||
82: 'rain',
|
||||
56: 'rain',
|
||||
57: 'rain',
|
||||
66: 'rain',
|
||||
67: 'rain',
|
||||
|
||||
71: 'snow',
|
||||
73: 'snow',
|
||||
75: 'snow',
|
||||
77: 'snow',
|
||||
85: 'snow',
|
||||
86: 'snow',
|
||||
|
||||
95: 'storm',
|
||||
96: 'storm',
|
||||
99: 'storm',
|
||||
}
|
||||
|
||||
# Mapping (Rainviewer)
|
||||
map_rvradar = [ 'radar', 'past' ]
|
||||
map_rvsatellite = [ 'satellite', 'infrared' ]
|
||||
map_rv = { 'rvradar': map_rvradar, 'rvsatellite': map_rvsatellite }
|
||||
map_gc = { 'gctemp': '', 'gcwind': '' }
|
||||
map_layers = { **map_rv, **map_gc }
|
||||
|
||||
# Mapping WMO to KODI
|
||||
map_wmo = {
|
||||
'0d': 32,
|
||||
'0n': 31,
|
||||
'1d': 34,
|
||||
'1n': 33,
|
||||
'2d': 30,
|
||||
'2n': 29,
|
||||
'3d': 26,
|
||||
'3n': 26,
|
||||
'45d': 20,
|
||||
'45n': 20,
|
||||
'48d': 20,
|
||||
'48n': 20,
|
||||
'51d': 9,
|
||||
'51n': 9,
|
||||
'53d': 12,
|
||||
'53n': 12,
|
||||
'55d': 18,
|
||||
'55n': 18,
|
||||
'56d': 8,
|
||||
'56n': 8,
|
||||
'57d': 8,
|
||||
'57n': 8,
|
||||
'61d': 9,
|
||||
'61n': 9,
|
||||
'63d': 12,
|
||||
'63n': 12,
|
||||
'65d': 18,
|
||||
'65n': 18,
|
||||
'66d': 8,
|
||||
'66n': 8,
|
||||
'67d': 8,
|
||||
'67n': 8,
|
||||
'71d': 14,
|
||||
'71n': 14,
|
||||
'73d': 16,
|
||||
'73n': 16,
|
||||
'75d': 16,
|
||||
'75n': 16,
|
||||
'77d': 13,
|
||||
'77n': 13,
|
||||
'80d': 9,
|
||||
'80n': 9,
|
||||
'81d': 12,
|
||||
'81n': 12,
|
||||
'82d': 18,
|
||||
'82n': 18,
|
||||
'85d': 5,
|
||||
'85n': 5,
|
||||
'86d': 5,
|
||||
'86n': 5,
|
||||
'95d': 4,
|
||||
'95n': 4,
|
||||
'96d': 3,
|
||||
'96n': 3,
|
||||
'99d': 3,
|
||||
'99n': 3,
|
||||
}
|
||||
|
||||
# Graph (Resolution)
|
||||
map_height = {
|
||||
720: 720,
|
||||
1080: 1080,
|
||||
1440: 1440,
|
||||
2160: 2160,
|
||||
800: 720,
|
||||
1200: 1080,
|
||||
1600: 1440,
|
||||
2400: 2160
|
||||
}
|
||||
|
||||
# Pressure
|
||||
map_pressure = { 950: 0, 951: 1, 952: 2, 953: 3, 954: 4, 955: 5, 956: 6, 957: 7, 958: 8, 959: 9, 960: 10, 961: 11, 962: 12, 963: 13, 964: 14, 965: 15, 966: 16, 967: 17, 968: 18, 969: 19, 970: 20, 971: 21, 972: 22, 973: 23, 974: 24, 975: 25, 976: 26, 977: 27, 978: 28, 979: 29, 980: 30, 981: 31, 982: 32, 983: 33, 984: 34, 985: 35, 986: 36, 987: 37, 988: 38, 989: 39, 990: 40, 991: 41, 992: 42, 993: 43, 994: 44, 995: 45, 996: 46, 997: 47, 998: 48, 999: 49, 1000: 50, 1001: 51, 1002: 52, 1003: 53, 1004: 54, 1005: 55, 1006: 56, 1007: 57, 1008: 58, 1009: 59, 1010: 60, 1011: 61, 1012: 62, 1013: 63, 1014: 64, 1015: 65, 1016: 66, 1017: 67, 1018: 68, 1019: 69, 1020: 70, 1021: 71, 1022: 72, 1023: 73, 1024: 74, 1025: 75, 1026: 76, 1027: 77, 1028: 78, 1029: 79, 1030: 80, 1031: 81, 1032: 82, 1033: 83, 1034: 84, 1035: 85, 1036: 86, 1037: 87, 1038: 88, 1039: 89, 1040: 90, 1041: 91, 1042: 92, 1043: 93, 1044: 94, 1045: 95, 1046: 96, 1047: 97, 1048: 98, 1049: 99, 1050: 100 }
|
||||
|
||||
# Dynamic localization mapping
|
||||
def localization():
|
||||
|
||||
localization.wmo = {
|
||||
'0d': utils.locaddon(32200),
|
||||
'0n': utils.locaddon(32250),
|
||||
'1d': utils.locaddon(32201),
|
||||
'1n': utils.locaddon(32251),
|
||||
'2d': utils.locaddon(32202),
|
||||
'2n': utils.locaddon(32202),
|
||||
'3d': utils.locaddon(32203),
|
||||
'3n': utils.locaddon(32203),
|
||||
'45d': utils.locaddon(32204),
|
||||
'45n': utils.locaddon(32204),
|
||||
'48d': utils.locaddon(32205),
|
||||
'48n': utils.locaddon(32205),
|
||||
'51d': utils.locaddon(32206),
|
||||
'51n': utils.locaddon(32206),
|
||||
'53d': utils.locaddon(32207),
|
||||
'53n': utils.locaddon(32207),
|
||||
'55d': utils.locaddon(32208),
|
||||
'55n': utils.locaddon(32208),
|
||||
'56d': utils.locaddon(32209),
|
||||
'56n': utils.locaddon(32209),
|
||||
'57d': utils.locaddon(32210),
|
||||
'57n': utils.locaddon(32210),
|
||||
'61d': utils.locaddon(32211),
|
||||
'61n': utils.locaddon(32211),
|
||||
'63d': utils.locaddon(32212),
|
||||
'63n': utils.locaddon(32212),
|
||||
'65d': utils.locaddon(32213),
|
||||
'65n': utils.locaddon(32213),
|
||||
'66d': utils.locaddon(32214),
|
||||
'66n': utils.locaddon(32214),
|
||||
'67d': utils.locaddon(32215),
|
||||
'67n': utils.locaddon(32215),
|
||||
'71d': utils.locaddon(32216),
|
||||
'71n': utils.locaddon(32216),
|
||||
'73d': utils.locaddon(32217),
|
||||
'73n': utils.locaddon(32217),
|
||||
'75d': utils.locaddon(32218),
|
||||
'75n': utils.locaddon(32218),
|
||||
'77d': utils.locaddon(32219),
|
||||
'77n': utils.locaddon(32219),
|
||||
'80d': utils.locaddon(32220),
|
||||
'80n': utils.locaddon(32220),
|
||||
'81d': utils.locaddon(32221),
|
||||
'81n': utils.locaddon(32221),
|
||||
'82d': utils.locaddon(32222),
|
||||
'82n': utils.locaddon(32222),
|
||||
'85d': utils.locaddon(32223),
|
||||
'85n': utils.locaddon(32223),
|
||||
'86d': utils.locaddon(32224),
|
||||
'86n': utils.locaddon(32224),
|
||||
'95d': utils.locaddon(32225),
|
||||
'95n': utils.locaddon(32225),
|
||||
'96d': utils.locaddon(32226),
|
||||
'96n': utils.locaddon(32226),
|
||||
'99d': utils.locaddon(32227),
|
||||
'99n': utils.locaddon(32227)
|
||||
}
|
||||
|
||||
localization.weekday = {
|
||||
'1': utils.loc(11),
|
||||
'2': utils.loc(12),
|
||||
'3': utils.loc(13),
|
||||
'4': utils.loc(14),
|
||||
'5': utils.loc(15),
|
||||
'6': utils.loc(16),
|
||||
'7': utils.loc(17)
|
||||
}
|
||||
|
||||
localization.weekdayshort = {
|
||||
'1': utils.loc(41),
|
||||
'2': utils.loc(42),
|
||||
'3': utils.loc(43),
|
||||
'4': utils.loc(44),
|
||||
'5': utils.loc(45),
|
||||
'6': utils.loc(46),
|
||||
'7': utils.loc(47)
|
||||
}
|
||||
|
||||
localization.layers = {
|
||||
'rvradar': utils.locaddon(32400),
|
||||
'rvsatellite': utils.locaddon(32401),
|
||||
'gctemp': utils.locaddon(32320),
|
||||
'gcwind': utils.locaddon(32323),
|
||||
}
|
||||
|
||||
# Dynamic settings
|
||||
def alert(cache=False):
|
||||
|
||||
alert.map = {
|
||||
'temperaturegraph': {
|
||||
'type': 'temperature',
|
||||
'loc': 32320,
|
||||
'alert_temperature_high_1': utils.setting('alert_temperature_high_1', 'str', cache),
|
||||
'alert_temperature_high_2': utils.setting('alert_temperature_high_2', 'str', cache),
|
||||
'alert_temperature_high_3': utils.setting('alert_temperature_high_3', 'str', cache),
|
||||
'alert_temperature_low_1': utils.setting('alert_temperature_low_1', 'str', cache),
|
||||
'alert_temperature_low_2': utils.setting('alert_temperature_low_2', 'str', cache),
|
||||
'alert_temperature_low_3': utils.setting('alert_temperature_low_3', 'str', cache),
|
||||
},
|
||||
'precipitationgraph': {
|
||||
'type': 'precipitation',
|
||||
'loc': 32321,
|
||||
'alert_precipitation_high_1': utils.setting('alert_precipitation_high_1', 'str', cache),
|
||||
'alert_precipitation_high_2': utils.setting('alert_precipitation_high_2', 'str', cache),
|
||||
'alert_precipitation_high_3': utils.setting('alert_precipitation_high_3', 'str', cache),
|
||||
},
|
||||
'conditiongraph': {
|
||||
'type': 'condition',
|
||||
'loc': 32322,
|
||||
'alert_condition_wmo_1': utils.setting('alert_condition_wmo_1', 'str', cache),
|
||||
'alert_condition_wmo_2': utils.setting('alert_condition_wmo_2', 'str', cache),
|
||||
'alert_condition_wmo_3': utils.setting('alert_condition_wmo_3', 'str', cache),
|
||||
},
|
||||
'windspeedgraph': {
|
||||
'type': 'windspeed',
|
||||
'loc': 32323,
|
||||
'alert_windspeed_high_1': utils.setting('alert_windspeed_high_1', 'str', cache),
|
||||
'alert_windspeed_high_2': utils.setting('alert_windspeed_high_2', 'str', cache),
|
||||
'alert_windspeed_high_3': utils.setting('alert_windspeed_high_3', 'str', cache),
|
||||
},
|
||||
'windgustgraph': {
|
||||
'type': 'windgust',
|
||||
'loc': 32324,
|
||||
'alert_windgust_high_1': utils.setting('alert_windgust_high_1', 'str', cache),
|
||||
'alert_windgust_high_2': utils.setting('alert_windgust_high_2', 'str', cache),
|
||||
'alert_windgust_high_3': utils.setting('alert_windgust_high_3', 'str', cache),
|
||||
},
|
||||
'feelslikegraph': {
|
||||
'type': 'feelslike',
|
||||
'loc': 32332,
|
||||
'alert_feelslike_high_1': utils.setting('alert_feelslike_high_1', 'str', cache),
|
||||
'alert_feelslike_high_2': utils.setting('alert_feelslike_high_2', 'str', cache),
|
||||
'alert_feelslike_high_3': utils.setting('alert_feelslike_high_3', 'str', cache),
|
||||
},
|
||||
'dewpointgraph': {
|
||||
'type': 'dewpoint',
|
||||
'loc': 32333,
|
||||
'alert_dewpoint_high_1': utils.setting('alert_dewpoint_high_1', 'str', cache),
|
||||
'alert_dewpoint_high_2': utils.setting('alert_dewpoint_high_2', 'str', cache),
|
||||
'alert_dewpoint_high_3': utils.setting('alert_dewpoint_high_3', 'str', cache),
|
||||
},
|
||||
'cloudinessgraph': {
|
||||
'type': 'cloudiness',
|
||||
'loc': 32334,
|
||||
'alert_cloudiness_high_1': utils.setting('alert_cloudiness_high_1', 'str', cache),
|
||||
'alert_cloudiness_high_2': utils.setting('alert_cloudiness_high_2', 'str', cache),
|
||||
'alert_cloudiness_high_3': utils.setting('alert_cloudiness_high_3', 'str', cache),
|
||||
},
|
||||
'humiditygraph': {
|
||||
'type': 'humidity',
|
||||
'loc': 32346,
|
||||
'alert_humidity_high_1': utils.setting('alert_humidity_high_1', 'str', cache),
|
||||
'alert_humidity_high_2': utils.setting('alert_humidity_high_2', 'str', cache),
|
||||
'alert_humidity_high_3': utils.setting('alert_humidity_high_3', 'str', cache),
|
||||
},
|
||||
'precipitationprobabilitygraph': {
|
||||
'type': 'precipitationprobability',
|
||||
'loc': 32321,
|
||||
'alert_precipitationprobability_high_1': utils.setting('alert_precipitationprobability_high_1', 'str', cache),
|
||||
'alert_precipitationprobability_high_2': utils.setting('alert_precipitationprobability_high_2', 'str', cache),
|
||||
'alert_precipitationprobability_high_3': utils.setting('alert_precipitationprobability_high_3', 'str', cache),
|
||||
},
|
||||
'pressuregraph': {
|
||||
'type': 'pressure',
|
||||
'loc': 32347,
|
||||
'alert_pressure_high_1': utils.setting('alert_pressure_high_1', 'str', cache),
|
||||
'alert_pressure_high_2': utils.setting('alert_pressure_high_2', 'str', cache),
|
||||
'alert_pressure_high_3': utils.setting('alert_pressure_high_3', 'str', cache),
|
||||
},
|
||||
'pressuresurfacegraph': {
|
||||
'type': 'pressuresurface',
|
||||
'loc': 32347,
|
||||
'alert_pressuresurface_high_1': utils.setting('alert_pressuresurface_high_1', 'str', cache),
|
||||
'alert_pressuresurface_high_2': utils.setting('alert_pressuresurface_high_2', 'str', cache),
|
||||
'alert_pressuresurface_high_3': utils.setting('alert_pressuresurface_high_3', 'str', cache),
|
||||
},
|
||||
'solarradiationgraph': {
|
||||
'type': 'solarradiation',
|
||||
'loc': 32348,
|
||||
'alert_solarradiation_high_1': utils.setting('alert_solarradiation_high_1', 'str', cache),
|
||||
'alert_solarradiation_high_2': utils.setting('alert_solarradiation_high_2', 'str', cache),
|
||||
'alert_solarradiation_high_3': utils.setting('alert_solarradiation_high_3', 'str', cache),
|
||||
},
|
||||
'visibilitygraph': {
|
||||
'type': 'visibility',
|
||||
'loc': 32349,
|
||||
'alert_visibility_low_1': utils.setting('alert_visibility_low_1', 'str', cache),
|
||||
'alert_visibility_low_2': utils.setting('alert_visibility_low_2', 'str', cache),
|
||||
'alert_visibility_low_3': utils.setting('alert_visibility_low_3', 'str', cache),
|
||||
},
|
||||
'aqieugraph': {
|
||||
'type': 'aqieu',
|
||||
'loc': 32325,
|
||||
'alert_aqieu_high_1': utils.setting('alert_aqieu_high_1', 'str', cache),
|
||||
'alert_aqieu_high_2': utils.setting('alert_aqieu_high_2', 'str', cache),
|
||||
'alert_aqieu_high_3': utils.setting('alert_aqieu_high_3', 'str', cache),
|
||||
},
|
||||
'aqiusgraph': {
|
||||
'type': 'aqius',
|
||||
'loc': 32326,
|
||||
'alert_aqius_high_1': utils.setting('alert_aqius_high_1', 'str', cache),
|
||||
'alert_aqius_high_2': utils.setting('alert_aqius_high_2', 'str', cache),
|
||||
'alert_aqius_high_3': utils.setting('alert_aqius_high_3', 'str', cache),
|
||||
},
|
||||
'pm25graph': {
|
||||
'type': 'pm25',
|
||||
'loc': 32327,
|
||||
'alert_pm25_high_1': utils.setting('alert_pm25_high_1', 'str', cache),
|
||||
'alert_pm25_high_2': utils.setting('alert_pm25_high_2', 'str', cache),
|
||||
'alert_pm25_high_3': utils.setting('alert_pm25_high_3', 'str', cache),
|
||||
},
|
||||
'pm10graph': {
|
||||
'type': 'pm10',
|
||||
'loc': 32328,
|
||||
'alert_pm10_high_1': utils.setting('alert_pm10_high_1', 'str', cache),
|
||||
'alert_pm10_high_2': utils.setting('alert_pm10_high_2', 'str', cache),
|
||||
'alert_pm10_high_3': utils.setting('alert_pm10_high_3', 'str', cache),
|
||||
},
|
||||
'cograph': {
|
||||
'type': 'co',
|
||||
'loc': 32337,
|
||||
'alert_co_high_1': utils.setting('alert_co_high_1', 'str', cache),
|
||||
'alert_co_high_2': utils.setting('alert_co_high_2', 'str', cache),
|
||||
'alert_co_high_3': utils.setting('alert_co_high_3', 'str', cache),
|
||||
},
|
||||
'ozonegraph': {
|
||||
'type': 'ozone',
|
||||
'loc': 32338,
|
||||
'alert_ozone_high_1': utils.setting('alert_ozone_high_1', 'str', cache),
|
||||
'alert_ozone_high_2': utils.setting('alert_ozone_high_2', 'str', cache),
|
||||
'alert_ozone_high_3': utils.setting('alert_ozone_high_3', 'str', cache),
|
||||
},
|
||||
'dustgraph': {
|
||||
'type': 'dust',
|
||||
'loc': 32339,
|
||||
'alert_dust_high_1': utils.setting('alert_dust_high_1', 'str', cache),
|
||||
'alert_dust_high_2': utils.setting('alert_dust_high_2', 'str', cache),
|
||||
'alert_dust_high_3': utils.setting('alert_dust_high_3', 'str', cache),
|
||||
},
|
||||
'no2graph': {
|
||||
'type': 'no2',
|
||||
'loc': 32330,
|
||||
'alert_no2_high_1': utils.setting('alert_no2_high_1', 'str', cache),
|
||||
'alert_no2_high_2': utils.setting('alert_no2_high_2', 'str', cache),
|
||||
'alert_no2_high_3': utils.setting('alert_no2_high_3', 'str', cache),
|
||||
},
|
||||
'so2graph': {
|
||||
'type': 'so2',
|
||||
'loc': 32331,
|
||||
'alert_so2_high_1': utils.setting('alert_so2_high_1', 'str', cache),
|
||||
'alert_so2_high_2': utils.setting('alert_so2_high_2', 'str', cache),
|
||||
'alert_so2_high_3': utils.setting('alert_so2_high_3', 'str', cache),
|
||||
},
|
||||
'uvindexgraph': {
|
||||
'type': 'uvindex',
|
||||
'loc': 32329,
|
||||
'alert_uvindex_high_1': utils.setting('alert_uvindex_high_1', 'str', cache),
|
||||
'alert_uvindex_high_2': utils.setting('alert_uvindex_high_2', 'str', cache),
|
||||
'alert_uvindex_high_3': utils.setting('alert_uvindex_high_3', 'str', cache),
|
||||
},
|
||||
'aldergraph': {
|
||||
'type': 'alder',
|
||||
'loc': 32450,
|
||||
'alert_alder_high_1': utils.setting('alert_alder_high_1', 'str', cache),
|
||||
'alert_alder_high_2': utils.setting('alert_alder_high_2', 'str', cache),
|
||||
'alert_alder_high_3': utils.setting('alert_alder_high_3', 'str', cache),
|
||||
},
|
||||
'birchgraph': {
|
||||
'type': 'birch',
|
||||
'loc': 32451,
|
||||
'alert_birch_high_1': utils.setting('alert_birch_high_1', 'str', cache),
|
||||
'alert_birch_high_2': utils.setting('alert_birch_high_2', 'str', cache),
|
||||
'alert_birch_high_3': utils.setting('alert_birch_high_3', 'str', cache),
|
||||
},
|
||||
'grassgraph': {
|
||||
'type': 'grass',
|
||||
'loc': 32452,
|
||||
'alert_grass_high_1': utils.setting('alert_grass_high_1', 'str', cache),
|
||||
'alert_grass_high_2': utils.setting('alert_grass_high_2', 'str', cache),
|
||||
'alert_grass_high_3': utils.setting('alert_grass_high_3', 'str', cache),
|
||||
},
|
||||
'mugwortgraph': {
|
||||
'type': 'mugwort',
|
||||
'loc': 32453,
|
||||
'alert_mugwort_high_1': utils.setting('alert_mugwort_high_1', 'str', cache),
|
||||
'alert_mugwort_high_2': utils.setting('alert_mugwort_high_2', 'str', cache),
|
||||
'alert_mugwort_high_3': utils.setting('alert_mugwort_high_3', 'str', cache),
|
||||
},
|
||||
'olivegraph': {
|
||||
'type': 'olive',
|
||||
'loc': 32454,
|
||||
'alert_olive_high_1': utils.setting('alert_olive_high_1', 'str', cache),
|
||||
'alert_olive_high_2': utils.setting('alert_olive_high_2', 'str', cache),
|
||||
'alert_olive_high_3': utils.setting('alert_olive_high_3', 'str', cache),
|
||||
},
|
||||
'ragweedgraph': {
|
||||
'type': 'ragweed',
|
||||
'loc': 32455,
|
||||
'alert_ragweed_high_1': utils.setting('alert_ragweed_high_1', 'str', cache),
|
||||
'alert_ragweed_high_2': utils.setting('alert_ragweed_high_2', 'str', cache),
|
||||
'alert_ragweed_high_3': utils.setting('alert_ragweed_high_3', 'str', cache),
|
||||
},
|
||||
}
|
||||
|
||||
def addon(cache=False):
|
||||
|
||||
# Vars
|
||||
addon.settings = utils.settings()
|
||||
addon.alerts = 0
|
||||
addon.msgqueue = []
|
||||
addon.scalecache = {}
|
||||
|
||||
# Bool
|
||||
addon.debug = utils.setting('debug', 'bool', cache)
|
||||
addon.verbose = utils.setting('verbose', 'bool', cache)
|
||||
addon.enablehour = utils.setting('enablehour', 'bool', cache)
|
||||
|
||||
# Str
|
||||
addon.icons = utils.setting('icons', 'str', cache)
|
||||
addon.unitsep = utils.setting('unitsep', 'str', cache)
|
||||
addon.temp = utils.setting('unittemp', 'str', cache)
|
||||
addon.tempdp = utils.setting('unittempdp', 'str', cache)
|
||||
addon.speed = utils.setting('unitspeed', 'str', cache)
|
||||
addon.speeddp = utils.setting('unitspeeddp', 'str', cache)
|
||||
addon.precip = utils.setting('unitprecip', 'str', cache)
|
||||
addon.precipdp = utils.setting('unitprecipdp', 'str', cache)
|
||||
addon.distance = utils.setting('unitdistance', 'str', cache)
|
||||
addon.distancedp = utils.setting('unitdistancedp', 'str', cache)
|
||||
addon.particlesdp = utils.setting('unitparticlesdp', 'str', cache)
|
||||
addon.pollendp = utils.setting('unitpollendp', 'str', cache)
|
||||
addon.uvindexdp = utils.setting('unituvindexdp', 'str', cache)
|
||||
addon.pressure = utils.setting('unitpressure', 'str', cache)
|
||||
addon.pressuredp = utils.setting('unitpressuredp', 'str', cache)
|
||||
addon.radiationdp = utils.setting('unitradiationdp', 'str', cache)
|
||||
addon.cdefault = utils.setting('colordefault', 'str', cache)
|
||||
addon.cnegative = utils.setting('colornegative', 'str', cache)
|
||||
addon.cnormal = utils.setting('colornormal', 'str', cache)
|
||||
addon.cnotice = utils.setting('colornotice', 'str', cache)
|
||||
addon.ccaution = utils.setting('colorcaution', 'str', cache)
|
||||
addon.cdanger = utils.setting('colordanger', 'str', cache)
|
||||
|
||||
# Int
|
||||
addon.mapzoom = utils.setting('mapzoom', 'int', cache)
|
||||
addon.maphistory = utils.setting('maphistory', 'int', cache)
|
||||
addon.alerthours = utils.setting('alert_hours', 'int', cache)
|
||||
|
||||
# Maxlocs
|
||||
if utils.setting('explocations', 'bool', cache):
|
||||
addon.maxlocs = 6
|
||||
else:
|
||||
addon.maxlocs = 4
|
||||
|
||||
# Addon mode
|
||||
# Note (v0.9.4): Remove "skin.estuary.openht" in a future update
|
||||
skin = utils.settingrpc('lookandfeel.skin')
|
||||
|
||||
if skin == utils.winprop('openmeteo') or skin == 'skin.estuary.openht':
|
||||
addon.skin = True
|
||||
else:
|
||||
addon.skin = False
|
||||
|
||||
|
||||
def kodi():
|
||||
kodi.long = utils.region('datelong')
|
||||
kodi.date = utils.region('dateshort')
|
||||
kodi.time = utils.region('time')
|
||||
kodi.meri = utils.region('meridiem')
|
||||
kodi.speed = utils.region('speedunit')
|
||||
kodi.temp = utils.region('tempunit')
|
||||
kodi.height = 1080
|
||||
|
||||
def loc(locid, cache=False):
|
||||
loc.id = locid
|
||||
loc.cid = str(utils.settingrpc("weather.currentlocation"))
|
||||
loc.name = utils.setting(f'loc{locid}', 'str')
|
||||
loc.user = utils.setting(f'loc{locid}user', 'str')
|
||||
loc.lat = utils.setting(f'loc{locid}lat', 'float')
|
||||
loc.lon = utils.setting(f'loc{locid}lon', 'float')
|
||||
loc.utz = utils.setting(f'loc{locid}utz', 'bool')
|
||||
|
||||
try:
|
||||
loc.tz = utils.timezone(utils.setting(f'loc{locid}tz'))
|
||||
except:
|
||||
loc.tz = utils.timezone('UTC')
|
||||
|
||||
def init(cache=False):
|
||||
kodi()
|
||||
localization()
|
||||
addon(cache)
|
||||
alert(cache)
|
||||
|
||||
# Directory
|
||||
utils.createdir()
|
||||
|
||||
384
Kodi/Lenovo/addons/weather.openmeteo/lib/conv.py
Normal file
@@ -0,0 +1,384 @@
|
||||
from . import utils
|
||||
from . import config
|
||||
|
||||
# Time
|
||||
def time(unit, value):
|
||||
content = ''
|
||||
|
||||
if unit == 'timeiso':
|
||||
data = utils.dt('isoloc', value)
|
||||
else:
|
||||
data = utils.dt('stamploc', value)
|
||||
|
||||
# Hour
|
||||
if config.kodi.meri != '/':
|
||||
content += str(int(data.strftime('%I')))
|
||||
else:
|
||||
content += str(int(data.strftime('%H')))
|
||||
|
||||
# Minute
|
||||
if unit.startswith('time'):
|
||||
content += data.strftime(':%M')
|
||||
|
||||
# Meri
|
||||
if config.kodi.meri != '/':
|
||||
content += data.strftime('%p')
|
||||
|
||||
return content
|
||||
|
||||
# Temperature
|
||||
def tempconv(value, unit, kodi=False):
|
||||
if value is not False:
|
||||
value = float(value)
|
||||
|
||||
if unit == '°F':
|
||||
v = value * 1.8 + 32
|
||||
elif unit == 'K':
|
||||
v = value + 273.15
|
||||
elif unit == '°Ré':
|
||||
v = value * 0.8
|
||||
elif unit == '°Ra':
|
||||
v = value * 1.8 + 491.67
|
||||
elif unit == '°Rø':
|
||||
v = value * 0.525 + 7.5
|
||||
elif unit == '°D':
|
||||
v = value / -0.667 + 150
|
||||
elif unit == '°N':
|
||||
v = value * 0.33
|
||||
else:
|
||||
v = value
|
||||
|
||||
if config.addon.tempdp == '0' or kodi is True:
|
||||
return round(v)
|
||||
else:
|
||||
if config.addon.unitsep == ',':
|
||||
return str(round(v,int(config.addon.tempdp))).replace('.',',')
|
||||
else:
|
||||
return round(v,int(config.addon.tempdp))
|
||||
|
||||
else:
|
||||
|
||||
if unit == '°F':
|
||||
v = '°F'
|
||||
elif unit == 'K':
|
||||
v = 'K'
|
||||
elif unit == '°Ré':
|
||||
v = '°Ré'
|
||||
elif unit == '°Ra':
|
||||
v = '°Ra'
|
||||
elif unit == '°Rø':
|
||||
v = '°Rø'
|
||||
elif unit == '°D':
|
||||
v = '°D'
|
||||
elif unit == '°N':
|
||||
v = '°N'
|
||||
else:
|
||||
v = '°C'
|
||||
|
||||
return v
|
||||
|
||||
def temp(value=False, kodi=False):
|
||||
|
||||
if config.addon.temp == 'app' or kodi is True:
|
||||
return tempconv(value, config.kodi.temp, kodi)
|
||||
else:
|
||||
return tempconv(value, config.addon.temp)
|
||||
|
||||
# Speed
|
||||
def beaufort(value):
|
||||
value = int(value)
|
||||
|
||||
if (value < 1.0):
|
||||
v = '0'
|
||||
elif (value >= 1.0) and (value < 5.6):
|
||||
v = '1'
|
||||
elif (value >= 5.6) and (value < 12.0):
|
||||
v = '2'
|
||||
elif (value >= 12.0) and (value < 20.0):
|
||||
v = '3'
|
||||
elif (value >= 20.0) and (value < 29.0):
|
||||
v = '4'
|
||||
elif (value >= 29.0) and (value < 39.0):
|
||||
v = '5'
|
||||
elif (value >= 39.0) and (value < 50.0):
|
||||
v = '6'
|
||||
elif (value >= 50.0) and (value < 62.0):
|
||||
v = '7'
|
||||
elif (value >= 62.0) and (value < 75.0):
|
||||
v = '8'
|
||||
elif (value >= 75.0) and (value < 89.0):
|
||||
v = '9'
|
||||
elif (value >= 89.0) and (value < 103.0):
|
||||
v = '10'
|
||||
elif (value >= 103.0) and (value < 118.0):
|
||||
v = '11'
|
||||
elif (value >= 118.0):
|
||||
v = '12'
|
||||
else:
|
||||
v = ''
|
||||
|
||||
return v
|
||||
|
||||
def speedconv(value, unit):
|
||||
if value is not False:
|
||||
value = float(value)
|
||||
|
||||
if unit == 'mph':
|
||||
v = value / 1.609344
|
||||
elif unit == 'm/min':
|
||||
v = value * 16,667
|
||||
elif unit == 'm/s':
|
||||
v = value / 3.6
|
||||
elif unit == 'ft/h':
|
||||
v = value * 3281
|
||||
elif unit == 'ft/min':
|
||||
v = value * 54.681
|
||||
elif unit == 'ft/s':
|
||||
v = value / 1.0971
|
||||
elif unit == 'kts':
|
||||
v = value / 1.852
|
||||
elif unit == 'beaufort':
|
||||
v = beaufort(value)
|
||||
elif unit == 'inch/s':
|
||||
v = value * 10.936
|
||||
elif unit == 'yard/s':
|
||||
v = value / 3.292
|
||||
elif unit == 'Furlong/Fortnight':
|
||||
v = value * 1670
|
||||
else:
|
||||
v = value
|
||||
|
||||
if config.addon.speeddp == '0':
|
||||
return round(v)
|
||||
else:
|
||||
if config.addon.unitsep == ',':
|
||||
return str(round(v,int(config.addon.speeddp))).replace('.',',')
|
||||
else:
|
||||
return round(v,int(config.addon.speeddp))
|
||||
|
||||
else:
|
||||
|
||||
if unit == 'mph':
|
||||
v = 'mph'
|
||||
elif unit == 'm/min':
|
||||
v = 'm/min'
|
||||
elif unit == 'm/s':
|
||||
v = 'm/s'
|
||||
elif unit == 'ft/h':
|
||||
v = 'ft/h'
|
||||
elif unit == 'ft/min':
|
||||
v = 'ft/min'
|
||||
elif unit == 'ft/s':
|
||||
v = 'ft/s'
|
||||
elif unit == 'kts':
|
||||
v = 'kts'
|
||||
elif unit == 'beaufort':
|
||||
v = 'beaufort'
|
||||
elif unit == 'inch/s':
|
||||
v = 'inch/s'
|
||||
elif unit == 'yard/s':
|
||||
v = 'yard/s'
|
||||
elif unit == 'Furlong/Fortnight':
|
||||
v = 'Furlong/Fortnight'
|
||||
else:
|
||||
v = 'km/h'
|
||||
|
||||
return v
|
||||
|
||||
def speed(value=False, kodi=False):
|
||||
|
||||
if config.addon.speed == 'app' or kodi is True:
|
||||
return speedconv(value, config.kodi.speed)
|
||||
else:
|
||||
return speedconv(value, config.addon.speed)
|
||||
|
||||
# Direction
|
||||
def direction(deg):
|
||||
if deg >= 349 or deg <= 11:
|
||||
return utils.loc(71)
|
||||
elif deg >= 12 and deg <= 33:
|
||||
return utils.loc(72)
|
||||
elif deg >= 34 and deg <= 56:
|
||||
return utils.loc(73)
|
||||
elif deg >= 57 and deg <= 78:
|
||||
return utils.loc(74)
|
||||
elif deg >= 79 and deg <= 101:
|
||||
return utils.loc(75)
|
||||
elif deg >= 102 and deg <= 123:
|
||||
return utils.loc(76)
|
||||
elif deg >= 124 and deg <= 146:
|
||||
return utils.loc(77)
|
||||
elif deg >= 147 and deg <= 168:
|
||||
return utils.loc(78)
|
||||
elif deg >= 169 and deg <= 191:
|
||||
return utils.loc(79)
|
||||
elif deg >= 192 and deg <= 213:
|
||||
return utils.loc(80)
|
||||
elif deg >= 214 and deg <= 236:
|
||||
return utils.loc(81)
|
||||
elif deg >= 237 and deg <= 258:
|
||||
return utils.loc(82)
|
||||
elif deg >= 259 and deg <= 281:
|
||||
return utils.loc(83)
|
||||
elif deg >= 282 and deg <= 303:
|
||||
return utils.loc(84)
|
||||
elif deg >= 304 and deg <= 326:
|
||||
return utils.loc(85)
|
||||
elif deg >= 327 and deg <= 348:
|
||||
return utils.loc(86)
|
||||
|
||||
# Distance
|
||||
def distanceconv(value, unit):
|
||||
if value is not False:
|
||||
value = float(value)
|
||||
|
||||
if unit == 'km':
|
||||
v = value / 1000
|
||||
elif unit == 'mi':
|
||||
v = value / 1609
|
||||
else:
|
||||
v = value
|
||||
|
||||
if config.addon.distancedp == '0':
|
||||
return round(v)
|
||||
else:
|
||||
if config.addon.unitsep == ',':
|
||||
return str(round(v,int(config.addon.distancedp))).replace('.',',')
|
||||
else:
|
||||
return round(v,int(config.addon.distancedp))
|
||||
|
||||
else:
|
||||
|
||||
if unit == 'km':
|
||||
v = 'km'
|
||||
elif unit == 'mi':
|
||||
v = 'mi'
|
||||
else:
|
||||
v = 'm'
|
||||
|
||||
return v
|
||||
|
||||
def distance(value=False):
|
||||
return distanceconv(value, config.addon.distance)
|
||||
|
||||
# Precipitation
|
||||
def precipconv(value, unit):
|
||||
if value is not False:
|
||||
value = float(value)
|
||||
|
||||
if unit == 'in':
|
||||
v = value * 0.039
|
||||
else:
|
||||
v = value
|
||||
|
||||
if config.addon.precipdp == '0':
|
||||
return round(v)
|
||||
else:
|
||||
if config.addon.unitsep == ',':
|
||||
return str(round(v,int(config.addon.precipdp))).replace('.',',')
|
||||
else:
|
||||
return round(v,int(config.addon.precipdp))
|
||||
|
||||
else:
|
||||
|
||||
if unit == 'in':
|
||||
v = 'in'
|
||||
else:
|
||||
v = 'mm'
|
||||
|
||||
return v
|
||||
|
||||
def precip(value=False):
|
||||
return precipconv(value, config.addon.precip)
|
||||
|
||||
# Pressure
|
||||
def pressureconv(value, unit):
|
||||
if value is not False:
|
||||
value = float(value)
|
||||
|
||||
if unit == 'kPa':
|
||||
v = value * 0.1
|
||||
elif unit == 'mmHg':
|
||||
v = value * 0.7500637554
|
||||
elif unit == 'inHg':
|
||||
v = value * 0.02953
|
||||
elif unit == 'psi':
|
||||
v = value * 0.0145037738
|
||||
else:
|
||||
v = value
|
||||
|
||||
if config.addon.pressuredp == '0':
|
||||
return round(v)
|
||||
else:
|
||||
if config.addon.unitsep == ',':
|
||||
return str(round(v,int(config.addon.pressuredp))).replace('.',',')
|
||||
else:
|
||||
return round(v,int(config.addon.pressuredp))
|
||||
|
||||
else:
|
||||
|
||||
if unit == 'kPa':
|
||||
v = 'kPa'
|
||||
elif unit == 'mmHg':
|
||||
v = 'mmHg'
|
||||
elif unit == 'inHg':
|
||||
v = 'inHg'
|
||||
elif unit == 'psi':
|
||||
v = 'psi'
|
||||
else:
|
||||
v = 'hPa'
|
||||
|
||||
return v
|
||||
|
||||
def pressure(value=False):
|
||||
return pressureconv(value, config.addon.pressure)
|
||||
|
||||
# Decimal places
|
||||
def dp(value, setting):
|
||||
value = float(value)
|
||||
|
||||
if setting == '0':
|
||||
return round(value)
|
||||
else:
|
||||
if config.addon.unitsep == ',':
|
||||
return str(round(value, int(setting))).replace('.',',')
|
||||
else:
|
||||
return round(value, int(setting))
|
||||
|
||||
# Moonphase
|
||||
def moonphase(deg):
|
||||
if deg == 358 or deg == 359 or deg == 0 or deg == 1 or deg == 2:
|
||||
return utils.locaddon(32440)
|
||||
elif deg >= 3 and deg <= 87:
|
||||
return utils.locaddon(32441)
|
||||
elif deg == 88 or deg == 89 or deg == 90 or deg == 91 or deg == 92:
|
||||
return utils.locaddon(32442)
|
||||
elif deg >= 93 and deg <= 177:
|
||||
return utils.locaddon(32443)
|
||||
elif deg == 178 or deg == 179 or deg == 180 or deg == 181 or deg == 182:
|
||||
return utils.locaddon(32444)
|
||||
elif deg >= 183 and deg <= 267:
|
||||
return utils.locaddon(32445)
|
||||
elif deg == 268 or deg == 269 or deg == 270 or deg == 271 or deg == 272:
|
||||
return utils.locaddon(32446)
|
||||
elif deg >= 273 and deg <= 357:
|
||||
return utils.locaddon(32447)
|
||||
|
||||
def moonphaseimage(deg):
|
||||
if deg == 358 or deg == 359 or deg == 0 or deg == 1 or deg == 2:
|
||||
return '0.png'
|
||||
elif deg >= 3 and deg <= 87:
|
||||
return '1.png'
|
||||
elif deg == 88 or deg == 89 or deg == 90 or deg == 91 or deg == 92:
|
||||
return '2.png'
|
||||
elif deg >= 93 and deg <= 177:
|
||||
return '3.png'
|
||||
elif deg == 178 or deg == 179 or deg == 180 or deg == 181 or deg == 182:
|
||||
return '4.png'
|
||||
elif deg >= 183 and deg <= 267:
|
||||
return '5.png'
|
||||
elif deg == 268 or deg == 269 or deg == 270 or deg == 271 or deg == 272:
|
||||
return '6.png'
|
||||
elif deg >= 273 and deg <= 357:
|
||||
return '7.png'
|
||||
|
||||
46
Kodi/Lenovo/addons/weather.openmeteo/lib/monitor.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import xbmc
|
||||
|
||||
from . import config
|
||||
from . import utils
|
||||
from . import weather
|
||||
|
||||
### MONITOR
|
||||
class Main(xbmc.Monitor):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.old = None
|
||||
|
||||
def onSettingsChanged(self):
|
||||
current = utils.settings(changed=True)
|
||||
|
||||
if self.old:
|
||||
if self.old != current:
|
||||
utils.log('Settings changed, refreshing ...')
|
||||
config.init()
|
||||
weather.Main(str(utils.settingrpc("weather.currentlocation")), mode='update')
|
||||
|
||||
# Map zoom
|
||||
if current.get('mapzoom') != self.old.get('mapzoom'):
|
||||
utils.log('Map zoom changed, re-downloading ...')
|
||||
|
||||
for locid in range(1, config.addon.maxlocs):
|
||||
if utils.setting(f'loc{locid}'):
|
||||
utils.setsetting(f'loc{locid}map', '321318000')
|
||||
utils.setsetting(f'loc{locid}rv', '321318000')
|
||||
utils.setsetting(f'loc{locid}gc', '321318000')
|
||||
|
||||
self.old = current
|
||||
|
||||
def waitForService(self):
|
||||
sleep = 0
|
||||
|
||||
while not self.abortRequested() and utils.setting('service') != 'idle':
|
||||
utils.log(f'Waiting for service thread: {utils.setting("service")}', 3)
|
||||
sleep += 1
|
||||
|
||||
if sleep == 30:
|
||||
utils.log(f'Service thread not responding ...', 2)
|
||||
return
|
||||
|
||||
self.waitForAbort(1)
|
||||
|
||||
75
Kodi/Lenovo/addons/weather.openmeteo/lib/service.py
Normal file
@@ -0,0 +1,75 @@
|
||||
import os
|
||||
|
||||
from . import weather
|
||||
from . import config
|
||||
from . import utils
|
||||
from . import api
|
||||
|
||||
def Main():
|
||||
startup = True
|
||||
utils.log(f'Starting service ...')
|
||||
utils.log(config.addon_info, 3)
|
||||
|
||||
# Geolocation
|
||||
if not utils.setting('geoip') and not utils.setting('loc1'):
|
||||
utils.setsetting('geoip', 'true')
|
||||
utils.setsetting('service', 'running')
|
||||
weather.Main('1', mode='geoip')
|
||||
weather.Main('1', mode='download')
|
||||
utils.setsetting('service', 'idle')
|
||||
|
||||
# Service
|
||||
while not utils.monitor.abortRequested():
|
||||
utils.setsetting('service', 'running')
|
||||
|
||||
# Init
|
||||
if utils.settingrpc('weather.addon') == 'weather.openmeteo':
|
||||
utils.log(f'Running service ...', 3)
|
||||
|
||||
if not startup:
|
||||
config.init(cache=True)
|
||||
|
||||
start = utils.time.time()
|
||||
current = utils.settingrpc("weather.currentlocation")
|
||||
|
||||
# Download
|
||||
for locid in range(1, config.addon.maxlocs):
|
||||
if utils.setting(f'loc{locid}'):
|
||||
weather.Main(str(locid), mode='download')
|
||||
|
||||
# Update
|
||||
weather.Main(str(current), mode='update')
|
||||
utils.setsetting('service', 'idle')
|
||||
|
||||
# Notification
|
||||
if startup or utils.lastupdate('alert_notification') >= utils.setting('alert_interval', 'int') * 60:
|
||||
utils.setupdate('alert_notification')
|
||||
|
||||
# Queue
|
||||
for locid in range(1, config.addon.maxlocs):
|
||||
if utils.setting(f'loc{locid}') and utils.setting(f'loc{locid}alert', 'bool'):
|
||||
weather.Main(str(locid), mode='msgqueue')
|
||||
|
||||
# Send
|
||||
weather.Main(str(current), mode='msgsend')
|
||||
|
||||
# Finish
|
||||
utils.log(f'Finished ({round(utils.time.time() - start, 3)} sec)', 3)
|
||||
|
||||
else:
|
||||
utils.log('Addon not enabled ...', 4)
|
||||
utils.setsetting('service', 'idle')
|
||||
|
||||
# Sleep
|
||||
startup = False
|
||||
utils.monitor.waitForAbort(300)
|
||||
|
||||
utils.log(f'Stopping service ...')
|
||||
api.s.close()
|
||||
|
||||
# Workaround KODI issue (v0.9.5)
|
||||
try:
|
||||
utils.setsetting('service', 'stopped')
|
||||
except:
|
||||
pass
|
||||
|
||||
696
Kodi/Lenovo/addons/weather.openmeteo/lib/utils.py
Normal file
@@ -0,0 +1,696 @@
|
||||
import os
|
||||
import sys
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
import math
|
||||
import json
|
||||
import time
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from datetime import datetime
|
||||
from pytz import timezone
|
||||
from pathlib import Path
|
||||
|
||||
from . import config
|
||||
from . import conv
|
||||
from . import weather
|
||||
from . import monitor
|
||||
|
||||
# Monitor
|
||||
monitor = monitor.Main()
|
||||
|
||||
# Logging
|
||||
def log(msg, level=0):
|
||||
if level == 1:
|
||||
xbmc.log(msg=f'[weather.openmeteo]: [W] {msg}', level=xbmc.LOGINFO)
|
||||
elif level == 2:
|
||||
xbmc.log(msg=f'[weather.openmeteo]: [E] {msg}', level=xbmc.LOGINFO)
|
||||
elif level == 3:
|
||||
if config.addon.debug:
|
||||
xbmc.log(msg=f'[weather.openmeteo]: [D] {msg}', level=xbmc.LOGINFO)
|
||||
elif level == 4:
|
||||
if config.addon.verbose:
|
||||
xbmc.log(msg=f'[weather.openmeteo]: [V] {msg}', level=xbmc.LOGINFO)
|
||||
else:
|
||||
xbmc.log(msg=f'[weather.openmeteo]: [I] {msg}', level=xbmc.LOGINFO)
|
||||
|
||||
# Setting
|
||||
def setting(arg, type='str', cache=False):
|
||||
|
||||
# Cache
|
||||
if cache:
|
||||
try:
|
||||
value = config.addon.settings[arg]
|
||||
except:
|
||||
value = xbmcaddon.Addon().getSetting(arg)
|
||||
else:
|
||||
value = xbmcaddon.Addon().getSetting(arg)
|
||||
|
||||
# Type
|
||||
if type == 'int':
|
||||
return int(value)
|
||||
|
||||
elif type == 'float':
|
||||
return float(value)
|
||||
|
||||
elif type == 'bool':
|
||||
|
||||
if value == 'true':
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
else:
|
||||
return str(value)
|
||||
|
||||
def setsetting(arg, value):
|
||||
xbmcaddon.Addon().setSetting(arg, str(value))
|
||||
|
||||
def settingrpc(setting):
|
||||
try:
|
||||
r = json.loads(xbmc.executeJSONRPC('{{"jsonrpc":"2.0","id":1,"method":"Settings.GetSettingValue", "params": {{"setting": "{}"}} }}'.format(setting)))
|
||||
except:
|
||||
return None
|
||||
else:
|
||||
return r.get('result').get('value')
|
||||
|
||||
def settings(changed=False):
|
||||
dict = {}
|
||||
skip = [ 'alert_notification', 'service', 'geoip' ]
|
||||
|
||||
try:
|
||||
tree = ET.parse(f'{config.addon_data}settings.xml')
|
||||
except:
|
||||
return dict
|
||||
else:
|
||||
root = tree.getroot()
|
||||
|
||||
for item in root:
|
||||
id = item.attrib['id']
|
||||
|
||||
if changed:
|
||||
if not 'loc' in id and not id in skip:
|
||||
dict[id] = item.text
|
||||
else:
|
||||
dict[id] = item.text
|
||||
|
||||
return dict
|
||||
|
||||
def region(arg):
|
||||
return xbmc.getRegion(arg)
|
||||
|
||||
# Localization
|
||||
def loc(arg):
|
||||
return xbmc.getLocalizedString(arg)
|
||||
|
||||
def locaddon(arg):
|
||||
return xbmcaddon.Addon().getLocalizedString(arg)
|
||||
|
||||
# Notification
|
||||
def notification(header, msg, icon, locid):
|
||||
log(f'[LOC{locid}] Notification: {header} - {msg}')
|
||||
|
||||
duration = (int(setting('alert_duration')) - 2) * 1000
|
||||
xbmcgui.Dialog().notification(header, msg, icon, int(duration))
|
||||
|
||||
# Datetime
|
||||
def dt(arg, stamp=0):
|
||||
|
||||
if arg == 'stamputc':
|
||||
return datetime.fromtimestamp(int(stamp), tz=timezone('UTC'))
|
||||
elif arg == 'stamploc':
|
||||
if config.loc.utz:
|
||||
return datetime.fromtimestamp(int(stamp), tz=timezone('UTC')).astimezone(config.loc.tz)
|
||||
else:
|
||||
return datetime.fromtimestamp(int(stamp), tz=timezone('UTC')).astimezone()
|
||||
elif arg == 'nowutc':
|
||||
return datetime.now(tz=timezone('UTC'))
|
||||
elif arg == 'nowutcstamp':
|
||||
return int(datetime.now(tz=timezone('UTC')).timestamp())
|
||||
elif arg == 'nowloc':
|
||||
if config.loc.utz:
|
||||
return datetime.now(tz=timezone('UTC')).astimezone(config.loc.tz)
|
||||
else:
|
||||
return datetime.now(tz=timezone('UTC')).astimezone()
|
||||
elif arg == 'isoutc':
|
||||
return datetime.fromisoformat(stamp)
|
||||
elif arg == 'isoloc':
|
||||
if config.loc.utz:
|
||||
return datetime.fromisoformat(stamp).astimezone(config.loc.tz)
|
||||
else:
|
||||
return datetime.fromisoformat(stamp).astimezone()
|
||||
|
||||
# Last update
|
||||
def lastupdate(arg):
|
||||
try:
|
||||
time1 = setting(arg)
|
||||
time2 = dt('nowutcstamp')
|
||||
return int(time2) - int(time1)
|
||||
except:
|
||||
return 321318000
|
||||
|
||||
def setupdate(arg):
|
||||
setsetting(arg, dt('nowutcstamp'))
|
||||
|
||||
# Window property
|
||||
def clrprop(property):
|
||||
log(f'CLR: {property}', 4)
|
||||
xbmcgui.Window(12600).clearProperty(property)
|
||||
|
||||
def winprop(property):
|
||||
log(f'GET: {property}', 4)
|
||||
return xbmcgui.Window(12600).getProperty(property)
|
||||
|
||||
# Window property (Set)
|
||||
def setprop(property, data):
|
||||
log(f'SET: {property} = {data}', 4)
|
||||
xbmcgui.Window(12600).setProperty(property, str(data))
|
||||
|
||||
# Window property (Get)
|
||||
def getprop(data, map, idx, count):
|
||||
|
||||
# Content
|
||||
if len(map[1]) == 1:
|
||||
if idx is not None:
|
||||
content = data[map[1][0]][idx]
|
||||
else:
|
||||
content = data[map[1][0]]
|
||||
elif len(map[1]) == 2:
|
||||
if idx is not None:
|
||||
content = data[map[1][0]][map[1][1]][idx]
|
||||
else:
|
||||
content = data[map[1][0]][map[1][1]]
|
||||
elif len(map[1]) == 3:
|
||||
if idx is not None:
|
||||
content = data[map[1][0]][map[1][1]][map[1][2]][idx]
|
||||
else:
|
||||
content = data[map[1][0]][map[1][1]][map[1][2]]
|
||||
|
||||
if content is None:
|
||||
raise TypeError('No data')
|
||||
|
||||
# Unit
|
||||
unit = map[3]
|
||||
|
||||
# WMO (isday)
|
||||
if unit.startswith('wmo') or unit == 'image' or unit == 'code':
|
||||
if idx:
|
||||
try:
|
||||
if data[map[1][0]]['is_day'][idx] == 1:
|
||||
isday = 'd'
|
||||
else:
|
||||
isday = 'n'
|
||||
except:
|
||||
isday = 'd'
|
||||
else:
|
||||
try:
|
||||
if data[map[1][0]]['is_day'] == 1:
|
||||
isday = 'd'
|
||||
else:
|
||||
isday = 'n'
|
||||
except:
|
||||
isday = 'd'
|
||||
|
||||
# Tools
|
||||
if unit == 'round':
|
||||
content = int(round(content))
|
||||
elif unit == 'roundpercent':
|
||||
content = f'{int(round(content))}%'
|
||||
elif unit == 'round2':
|
||||
content = round(content, 2)
|
||||
elif unit == 'wmocond':
|
||||
content = config.localization.wmo.get(f'{content}{isday}')
|
||||
elif unit == 'wmoimage':
|
||||
content = f'{config.addon_icons}/{config.addon.icons}/{content}{isday}.png'
|
||||
elif unit == 'wmocode':
|
||||
content = f'{content}{isday}'
|
||||
elif unit == 'image':
|
||||
# KODI workaround for DayX.OutlookIcon, add "resource://resource.images.weathericons.default" to path
|
||||
if map[2][0] == 'day':
|
||||
content = f'resource://resource.images.weathericons.default/{config.map_wmo.get(f"{content}{isday}")}.png'
|
||||
else:
|
||||
content = f'{config.map_wmo.get(f"{content}{isday}")}.png'
|
||||
elif unit == 'code':
|
||||
content = config.map_wmo.get(f'{content}{isday}')
|
||||
elif unit == 'date':
|
||||
content = dt('stamploc', content).strftime(config.kodi.date)
|
||||
elif unit == 'time':
|
||||
content = conv.time('time', content)
|
||||
elif unit == 'timeiso':
|
||||
content = conv.time('timeiso', content)
|
||||
elif unit == 'hour':
|
||||
content = conv.time('hour', content)
|
||||
elif unit == 'weekday':
|
||||
content = config.localization.weekday.get(dt('stamploc', content).strftime('%u'))
|
||||
elif unit == 'weekdayshort':
|
||||
content = config.localization.weekdayshort.get(dt('stamploc', content).strftime('%u'))
|
||||
elif unit == '%':
|
||||
content = f'{content}%'
|
||||
|
||||
# Temperature
|
||||
elif unit == 'temperature':
|
||||
content = conv.temp(content)
|
||||
elif unit == 'temperaturekodi':
|
||||
content = conv.temp(content, True)
|
||||
elif unit == 'temperatureunit':
|
||||
content = f'{conv.temp(content)}{conv.temp()}'
|
||||
elif unit == 'unittemperature':
|
||||
content = conv.temp()
|
||||
|
||||
# Speed
|
||||
elif unit == 'speed':
|
||||
content = conv.speed(content)
|
||||
elif unit == 'unitspeed':
|
||||
content = conv.speed()
|
||||
|
||||
# Precipitation
|
||||
elif unit == 'precipitation':
|
||||
content = conv.precip(content)
|
||||
elif unit == 'unitprecipitation':
|
||||
content = conv.precip()
|
||||
|
||||
# Distance
|
||||
elif unit == 'distance':
|
||||
content = conv.distance(content)
|
||||
elif unit == 'unitdistance':
|
||||
content = conv.distance()
|
||||
|
||||
# UVIndex
|
||||
elif unit == 'uvindex':
|
||||
content = conv.dp(content, config.addon.uvindexdp)
|
||||
|
||||
# Particles
|
||||
elif unit == 'particles':
|
||||
content = conv.dp(content, config.addon.particlesdp)
|
||||
elif unit == 'unitparticles':
|
||||
content = 'μg/m³'
|
||||
|
||||
# Pollen
|
||||
elif unit == 'pollen':
|
||||
content = conv.dp(content, config.addon.pollendp)
|
||||
elif unit == 'unitpollen':
|
||||
content = f'{locaddon(32456)}/m³'
|
||||
|
||||
# Radiation
|
||||
elif unit == 'radiation':
|
||||
content = conv.dp(content, config.addon.radiationdp)
|
||||
elif unit == 'unitradiation':
|
||||
content = 'W/m²'
|
||||
|
||||
# Pressure
|
||||
elif unit == 'pressure':
|
||||
content = conv.pressure(content)
|
||||
elif unit == 'unitpressure':
|
||||
content = conv.pressure()
|
||||
|
||||
# Direction
|
||||
elif unit == 'direction':
|
||||
content = conv.direction(content)
|
||||
|
||||
# Percent
|
||||
elif unit == 'unitpercent':
|
||||
content = '%'
|
||||
|
||||
# Wind
|
||||
elif unit == 'windkodi':
|
||||
speed = round(conv.speed(data['current']['wind_speed_10m']), True)
|
||||
unit = conv.speed(False, True)
|
||||
direction = conv.direction(data['current']['wind_direction_10m'])
|
||||
content = loc(434).format(direction, int(speed), unit)
|
||||
|
||||
# Moonphase
|
||||
elif unit == 'moonphase':
|
||||
content = conv.moonphase(int(content))
|
||||
|
||||
elif unit == 'moonphaseimage':
|
||||
content = f'{config.addon_icons}/moon/{conv.moonphaseimage(int(content))}'
|
||||
|
||||
# Graphs
|
||||
elif unit == 'graph':
|
||||
property = f'{map[2][0]}.{count}.{map[2][1]}'
|
||||
time = map[2][0]
|
||||
type = map[2][1]
|
||||
mscale = map[4]
|
||||
alert = 0
|
||||
scale = 0
|
||||
scaleneg = False
|
||||
|
||||
# Content
|
||||
try:
|
||||
calc = map[5]
|
||||
except:
|
||||
calc = False
|
||||
else:
|
||||
if calc == 'temperature':
|
||||
|
||||
if conv.temp() == '°F':
|
||||
mscale = '100'
|
||||
|
||||
elif calc == 'divide10':
|
||||
content = content/10
|
||||
|
||||
elif calc == 'divide1000':
|
||||
content = content/1000
|
||||
|
||||
elif calc == 'pressure':
|
||||
content = config.map_pressure.get(int(content),0)
|
||||
|
||||
# Autoscale
|
||||
try:
|
||||
ascale = config.addon.scalecache[f'{time}{type}']
|
||||
nscale = config.addon.scalecache[f'{time}{type}neg']
|
||||
except:
|
||||
_idx_ = idx
|
||||
|
||||
for _count_ in range(0,25):
|
||||
|
||||
if calc == 'temperature':
|
||||
v = conv.temp(data[map[1][0]][map[1][1]][_idx_+_count_], True)
|
||||
|
||||
# Negative temperature
|
||||
if v < 0:
|
||||
scaleneg = True
|
||||
|
||||
check = abs(v)
|
||||
|
||||
elif calc == 'divide10':
|
||||
check = abs(data[map[1][0]][map[1][1]][_idx_+_count_]/10)
|
||||
elif calc == 'divide1000':
|
||||
check = abs(data[map[1][0]][map[1][1]][_idx_+_count_]/1000)
|
||||
elif calc == 'pressure':
|
||||
check = abs(config.map_pressure.get(int(data[map[1][0]][map[1][1]][_idx_+_count_])))
|
||||
else:
|
||||
check = abs(data[map[1][0]][map[1][1]][_idx_+_count_])
|
||||
|
||||
if check > scale:
|
||||
scale = check
|
||||
|
||||
if scale < 1:
|
||||
config.addon.scalecache[f'{time}{type}'] = 1
|
||||
elif scale >= 101 and scale <= 150:
|
||||
config.addon.scalecache[f'{time}{type}'] = 150
|
||||
elif scale >= 151 and scale <= 200:
|
||||
config.addon.scalecache[f'{time}{type}'] = 200
|
||||
else:
|
||||
config.addon.scalecache[f'{time}{type}'] = math.ceil(scale/10.0)*10
|
||||
|
||||
# Negative values
|
||||
config.addon.scalecache[f'{time}{type}neg'] = scaleneg
|
||||
|
||||
# Set cache
|
||||
ascale = config.addon.scalecache[f'{time}{type}']
|
||||
nscale = config.addon.scalecache[f'{time}{type}neg']
|
||||
|
||||
log(f'Scale {type} ({time}) = {ascale}', 4)
|
||||
|
||||
# Alert
|
||||
for _alert_ in config.alert.map[type]:
|
||||
|
||||
if not 'alert' in _alert_:
|
||||
continue
|
||||
|
||||
if 'wmo' in _alert_:
|
||||
limit = list(config.alert.map[type][_alert_].split(' '))
|
||||
else:
|
||||
limit = float(config.alert.map[type][_alert_])
|
||||
|
||||
if not limit:
|
||||
continue
|
||||
|
||||
if 'high' in _alert_:
|
||||
if content >= int(limit):
|
||||
alert = int(_alert_[-1])
|
||||
elif 'low' in _alert_:
|
||||
if content <= int(limit):
|
||||
alert = int(_alert_[-1])
|
||||
elif 'wmo' in _alert_:
|
||||
for wmo in limit:
|
||||
if content == int(wmo):
|
||||
alert = int(alert[-1])
|
||||
|
||||
setprop(f'{property}alert', alert)
|
||||
|
||||
# Content
|
||||
if calc == 'temperature':
|
||||
content = conv.temp(content, True)
|
||||
|
||||
if ascale == 1:
|
||||
content = round(content,1)
|
||||
else:
|
||||
content = round(content)
|
||||
|
||||
# Set properties
|
||||
if nscale:
|
||||
setprop(f'{property}image', f'{config.addon_icons}/graph/{config.kodi.height}/scaleneg{mscale}_{content}.png')
|
||||
setprop(f'{property}imagescale', f'{config.addon_icons}/graph/{config.kodi.height}/scaleneg{ascale}_{content}.png')
|
||||
setprop(f'{property}scale', f'{ascale}n')
|
||||
else:
|
||||
setprop(f'{property}image', f'{config.addon_icons}/graph/{config.kodi.height}/scale{mscale}_{content}.png')
|
||||
setprop(f'{property}imagescale', f'{config.addon_icons}/graph/{config.kodi.height}/scale{ascale}_{content}.png')
|
||||
setprop(f'{property}scale', f'{ascale}')
|
||||
|
||||
# Color
|
||||
if alert == 0:
|
||||
if content < 0:
|
||||
setprop(f'{property}color', config.addon.cnegative)
|
||||
setprop(f'{property}colornormal', config.addon.cnegative)
|
||||
else:
|
||||
setprop(f'{property}color', config.addon.cdefault)
|
||||
setprop(f'{property}colornormal', config.addon.cnormal)
|
||||
|
||||
elif alert == 1:
|
||||
setprop(f'{property}color', config.addon.cnotice)
|
||||
setprop(f'{property}colornormal', config.addon.cnotice)
|
||||
|
||||
elif alert == 2:
|
||||
setprop(f'{property}color', config.addon.ccaution)
|
||||
setprop(f'{property}colornormal', config.addon.ccaution)
|
||||
|
||||
elif alert == 3:
|
||||
setprop(f'{property}color', config.addon.cdanger)
|
||||
setprop(f'{property}colornormal', config.addon.cdanger)
|
||||
|
||||
# Return data
|
||||
return content
|
||||
|
||||
# Set alert
|
||||
def setalert(data, map, idx, locid, curid, loc, mode):
|
||||
winprops = [ 'name', 'value', 'icon', 'unit', 'time', 'hours', 'status' ]
|
||||
type = map[2][1]
|
||||
prop = config.alert.map[type]['type']
|
||||
name = locaddon(config.alert.map[type]['loc'])
|
||||
shours = config.addon.alerthours
|
||||
hours = { '1': 0, '2': 0, '3': 0 }
|
||||
code = 0
|
||||
value = 0
|
||||
unit = ''
|
||||
|
||||
log(f'[LOC{locid}] Checking alert: {prop}', 3)
|
||||
|
||||
for index in range(idx, idx+shours):
|
||||
|
||||
try:
|
||||
content = int(data[map[1][0]][map[1][1]][index])
|
||||
except:
|
||||
if locid == curid:
|
||||
setprop(f'alert.{prop}', 0)
|
||||
for winprop in winprops:
|
||||
setprop(f'alert.{prop}.{winprop}', '')
|
||||
|
||||
return
|
||||
|
||||
# Alert
|
||||
for alert in config.alert.map[type]:
|
||||
|
||||
if not 'alert' in alert:
|
||||
continue
|
||||
|
||||
if 'wmo' in alert:
|
||||
limit = list(config.alert.map[type][alert].split(' '))
|
||||
else:
|
||||
limit = int(config.alert.map[type][alert])
|
||||
|
||||
if not limit:
|
||||
continue
|
||||
|
||||
if 'high' in alert:
|
||||
if content >= limit:
|
||||
hours[f'{alert[-1]}'] += 1
|
||||
if content >= value:
|
||||
if content >= limit:
|
||||
code = int(alert[-1])
|
||||
value = content
|
||||
stamp = data[map[1][0]]['time'][index]
|
||||
|
||||
elif 'low' in alert:
|
||||
if content <= limit:
|
||||
hours[f'{alert[-1]}'] += 1
|
||||
if content <= value:
|
||||
if content <= limit:
|
||||
code = int(alert[-1])
|
||||
value = content
|
||||
stamp = data[map[1][0]]['time'][index]
|
||||
|
||||
elif 'wmo' in alert:
|
||||
for wmo in limit:
|
||||
if content == int(wmo):
|
||||
hours[f'{alert[-1]}'] += 1
|
||||
|
||||
if content > value:
|
||||
code = int(alert[-1])
|
||||
value = content
|
||||
stamp = data[map[1][0]]['time'][index]
|
||||
|
||||
# Check alert code
|
||||
if code != 0:
|
||||
icon = f'{prop}{code}'
|
||||
time = conv.time('time', stamp)
|
||||
|
||||
if prop == 'temperature':
|
||||
value = conv.temp(value)
|
||||
unit = conv.temp()
|
||||
elif prop == 'windspeed' or prop == 'windgust':
|
||||
value = conv.speed(value)
|
||||
unit = conv.speed()
|
||||
elif prop == 'condition':
|
||||
icon = f'condition{config.map_alert_condition.get(value)}{code}'
|
||||
value = config.localization.wmo.get(f'{value}d')
|
||||
|
||||
# Set alert properties for current location
|
||||
if locid == curid:
|
||||
|
||||
if setting(f'alert_{prop}_enabled', 'bool'):
|
||||
log(f'[LOC{locid}] Updating alert: {prop} = {code}', 3)
|
||||
config.addon.alerts += 1
|
||||
|
||||
setprop(f'alert.{prop}', code)
|
||||
setprop(f'alert.{prop}.name', name)
|
||||
setprop(f'alert.{prop}.time', time)
|
||||
setprop(f'alert.{prop}.hours', hours[str(code)])
|
||||
setprop(f'alert.{prop}.icon', f'{config.addon_icons}/alert/{icon}.png')
|
||||
setprop(f'alert.{prop}.value', value)
|
||||
setprop(f'alert.{prop}.unit', unit)
|
||||
if code == 1:
|
||||
setprop(f'alert.{prop}.status', locaddon(32340))
|
||||
elif code == 2:
|
||||
setprop(f'alert.{prop}.status', locaddon(32341))
|
||||
elif code == 3:
|
||||
setprop(f'alert.{prop}.status', locaddon(32342))
|
||||
else:
|
||||
setprop(f'alert.{prop}', '')
|
||||
for winprop in winprops:
|
||||
setprop(f'alert.{prop}.{winprop}', '')
|
||||
|
||||
# Notification
|
||||
if mode == 'msgqueue':
|
||||
if code == 1 and setting(f'alert_{prop}_notice', 'bool'):
|
||||
config.addon.msgqueue.append([ f'{loc} - {locaddon(32340)} ({hours[str(code)]} {locaddon(32288)})', f'({time}) {name}: {value} {unit}', f'{config.addon_icons}/alert/{icon}.png' ])
|
||||
elif code == 2 and setting(f'alert_{prop}_caution', 'bool'):
|
||||
config.addon.msgqueue.append([ f'{loc} - {locaddon(32341)} ({hours[str(code)]} {locaddon(32288)})', f'({time}) {name}: {value} {unit}', f'{config.addon_icons}/alert/{icon}.png' ])
|
||||
elif code == 3 and setting(f'alert_{prop}_danger', 'bool'):
|
||||
config.addon.msgqueue.append([ f'{loc} - {locaddon(32342)} ({hours[str(code)]} {locaddon(32288)})', f'({time}) {name}: {value} {unit}', f'{config.addon_icons}/alert/{icon}.png' ])
|
||||
|
||||
else:
|
||||
if locid == curid:
|
||||
setprop(f'alert.{prop}', 0)
|
||||
for winprop in winprops:
|
||||
setprop(f'alert.{prop}.{winprop}', '')
|
||||
|
||||
# Get file
|
||||
def getfile(file):
|
||||
try:
|
||||
# Note: Changing language throws an exception without enforcing utf8
|
||||
with open(Path(f'{config.addon_cache}/{file}'), 'r', encoding='utf8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
except Exception as e:
|
||||
log(f'{e}', 2)
|
||||
return None
|
||||
|
||||
else:
|
||||
return data
|
||||
|
||||
# Index
|
||||
def index(arg, data):
|
||||
if arg == 'now':
|
||||
match = dt('nowloc').strftime('%Y-%m-%d %H')
|
||||
elif arg == 'mid':
|
||||
match = dt('nowloc').strftime('%Y-%m-%d 00')
|
||||
elif arg == 'day':
|
||||
match = dt('nowloc').strftime('%Y-%m-%d')
|
||||
|
||||
for idx in range(config.mindata, config.maxdata):
|
||||
|
||||
try:
|
||||
if arg == 'day':
|
||||
timecheck = dt('stamploc', data['daily']['time'][idx]).strftime('%Y-%m-%d')
|
||||
else:
|
||||
timecheck = dt('stamploc', data['hourly']['time'][idx]).strftime('%Y-%m-%d %H')
|
||||
except:
|
||||
return None
|
||||
else:
|
||||
if timecheck == match:
|
||||
return idx
|
||||
|
||||
# Directory
|
||||
def createdir():
|
||||
file = config.addon_data + 'w.txt'
|
||||
|
||||
try:
|
||||
os.makedirs(config.addon_cache, exist_ok=True)
|
||||
with open(file, 'w') as f:
|
||||
f.write('w')
|
||||
except Exception as e:
|
||||
log(f'Addon data directory not writeable: {config.addon_data} {e}', 2)
|
||||
xbmcgui.Dialog().notification('Weather Open-Meteo', 'Weather data directory not writeable, check log ...', xbmcgui.NOTIFICATION_ERROR, 15000)
|
||||
sys.exit(1)
|
||||
else:
|
||||
os.remove(file)
|
||||
|
||||
# Locations
|
||||
def locations():
|
||||
locs = 0
|
||||
for count in range(1,6):
|
||||
loc = setting(f'loc{count}')
|
||||
if loc:
|
||||
setprop(f'location{count}', loc)
|
||||
locs += 1
|
||||
|
||||
setprop('locations', locs)
|
||||
|
||||
# LatLon2Coords
|
||||
def lat2coords(lat_deg, lon_deg, zoom):
|
||||
lat_rad = math.radians(lat_deg)
|
||||
n = 1 << zoom
|
||||
xtile = int((lon_deg + 180.0) / 360.0 * n)
|
||||
ytile = int((1.0 - math.asinh(math.tan(lat_rad)) / math.pi) / 2.0 * n)
|
||||
return xtile, ytile
|
||||
|
||||
def numTiles(z):
|
||||
return(pow(2,z))
|
||||
|
||||
def latEdges(y,z):
|
||||
n = numTiles(z)
|
||||
unit = 1 / n
|
||||
relY1 = y * unit
|
||||
relY2 = relY1 + unit
|
||||
lat1 = mercatorToLat(math.pi * (1 - 2 * relY1))
|
||||
lat2 = mercatorToLat(math.pi * (1 - 2 * relY2))
|
||||
return(lat1,lat2)
|
||||
|
||||
def lonEdges(x,z):
|
||||
n = numTiles(z)
|
||||
unit = 360 / n
|
||||
lon1 = -180 + x * unit
|
||||
lon2 = lon1 + unit
|
||||
return(lon1,lon2)
|
||||
|
||||
def mercatorToLat(mercatorY):
|
||||
return(math.degrees(math.atan(math.sinh(mercatorY))))
|
||||
|
||||
def coords2bbox(x,y,z):
|
||||
lat1,lat2 = latEdges(y,z)
|
||||
lon1,lon2 = lonEdges(x,z)
|
||||
return((lat2, lon1, lat1, lon2)) # S,W,N,E
|
||||
|
||||
460
Kodi/Lenovo/addons/weather.openmeteo/lib/weather.py
Normal file
@@ -0,0 +1,460 @@
|
||||
import os
|
||||
import xbmc
|
||||
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
from . import config
|
||||
from . import utils
|
||||
|
||||
class Main():
|
||||
|
||||
### MAIN
|
||||
def __init__(self, locid, mode='kodi'):
|
||||
|
||||
if utils.monitor.abortRequested():
|
||||
return
|
||||
|
||||
# Import API only when needed
|
||||
if mode == 'download' or mode == 'geoip' or locid.startswith('loc'):
|
||||
global api
|
||||
from . import api
|
||||
|
||||
# GeoIP
|
||||
if mode == 'geoip':
|
||||
api.getloc(locid)
|
||||
return
|
||||
|
||||
# Search
|
||||
if locid.startswith('loc'):
|
||||
api.setloc(locid[3])
|
||||
return
|
||||
|
||||
# Init
|
||||
self.init(locid, mode)
|
||||
|
||||
if not config.loc.lat or not config.loc.lon:
|
||||
utils.log(f'[LOC{locid}] Not configured', 1)
|
||||
return
|
||||
|
||||
# Download
|
||||
if self.mode == 'download':
|
||||
|
||||
# Weather
|
||||
if utils.lastupdate(f'loc{locid}data') >= 3600:
|
||||
with ThreadPoolExecutor(3) as pool:
|
||||
pool.map(self.getdata, config.map)
|
||||
|
||||
if api.network():
|
||||
utils.setupdate(f'loc{locid}data')
|
||||
|
||||
# Map
|
||||
if utils.lastupdate(f'loc{locid}map') >= 604800:
|
||||
self.getmap('osm')
|
||||
|
||||
if api.network():
|
||||
utils.setupdate(f'loc{locid}map')
|
||||
|
||||
# Rv
|
||||
if utils.lastupdate(f'loc{locid}rv') >= 3600:
|
||||
with ThreadPoolExecutor(2) as pool:
|
||||
pool.map(self.getmap, config.map_rv)
|
||||
|
||||
if api.network():
|
||||
utils.setupdate(f'loc{locid}rv')
|
||||
|
||||
# Gc
|
||||
if utils.lastupdate(f'loc{locid}gc') >= 10800:
|
||||
with ThreadPoolExecutor(2) as pool:
|
||||
pool.map(self.getmap, config.map_gc)
|
||||
|
||||
if api.network():
|
||||
utils.setupdate(f'loc{locid}gc')
|
||||
|
||||
# Update
|
||||
elif self.mode == 'update' or self.mode == 'kodi':
|
||||
|
||||
# Wait for service thread
|
||||
if self.mode == 'kodi':
|
||||
utils.monitor.waitForService()
|
||||
|
||||
# Note: Setting window properties is CPU bound, using threads seems to be slower
|
||||
# This needs more testing ...
|
||||
# with ThreadPoolExecutor(3) as pool:
|
||||
# pool.map(self.setdata, config.map)
|
||||
for map in config.map:
|
||||
self.setdata(map)
|
||||
|
||||
# Alerts
|
||||
for map in config.map:
|
||||
self.setalert(map)
|
||||
|
||||
# Properties
|
||||
self.setprop()
|
||||
|
||||
# Update locs
|
||||
elif self.mode == 'updatelocs':
|
||||
self.setlocs()
|
||||
|
||||
# Notification (Queue)
|
||||
elif self.mode == 'msgqueue':
|
||||
for map in config.map:
|
||||
self.setalert(map)
|
||||
|
||||
# Notification (Send)
|
||||
elif self.mode == 'msgsend':
|
||||
self.notification()
|
||||
|
||||
### INIT
|
||||
def init(self, locid, mode):
|
||||
|
||||
if mode == 'download':
|
||||
utils.log(f'[LOC{locid}] Initialising: mode={mode}, neterr={config.neterr}, net={api.network()}, dnscache={len(config.dnscache)}', 3)
|
||||
else:
|
||||
utils.log(f'[LOC{locid}] Initialising: mode={mode}', 3)
|
||||
|
||||
# Location
|
||||
config.loc(locid)
|
||||
|
||||
# Vars
|
||||
self.mode = mode
|
||||
self.data = {}
|
||||
self.today = utils.dt('nowloc').strftime('%Y-%m-%d')
|
||||
|
||||
# Directory
|
||||
os.makedirs(f'{config.addon_cache}/{locid}', exist_ok=True)
|
||||
|
||||
### GET DATA
|
||||
def getdata(self, type):
|
||||
utils.log(f'[LOC{config.loc.id}] Downloading data: {type}', 3)
|
||||
api.getdata(type, config.loc.id, [ config.loc.lat, config.loc.lon, self.today ])
|
||||
|
||||
### SET DATA
|
||||
def setdata(self, type):
|
||||
|
||||
# Data
|
||||
self.data[type] = utils.getfile(f'{config.loc.id}/{type}.json')
|
||||
if not self.data[type]:
|
||||
utils.log(f'No {type} data for location {config.loc.id}', 2)
|
||||
return
|
||||
|
||||
# Index
|
||||
indexnow = utils.index("now", self.data[type])
|
||||
indexmid = utils.index("mid", self.data[type])
|
||||
indexday = utils.index("day", self.data[type])
|
||||
|
||||
# Update data
|
||||
utils.log(f'[LOC{config.loc.id}] Updating data: {type}', 3)
|
||||
|
||||
for map in config.map.get(type):
|
||||
|
||||
# Current (Compatibility)
|
||||
if map[0] == 'current':
|
||||
self.setmap(type, map)
|
||||
|
||||
# Current (Advanced)
|
||||
elif map[0] == 'currentskin' and config.addon.skin:
|
||||
self.setmap(type, map)
|
||||
|
||||
# Current (KODI)
|
||||
elif map[0] == 'currentkodi' and self.mode == 'kodi':
|
||||
self.setmap(type, map)
|
||||
|
||||
# Hourly (Compatibility)
|
||||
elif map[0] == 'hourly':
|
||||
self.setmulti(type, [ map, 'hourly', indexnow, config.maxhours, config.minhours, 'hourly' ])
|
||||
|
||||
if config.addon.enablehour:
|
||||
self.setmulti(type, [ map, 'hourly', indexmid, config.maxhours, config.minhours, 'hour' ])
|
||||
|
||||
# Hourly (Advanced)
|
||||
elif map[0] == 'hourlyskin' and config.addon.skin:
|
||||
self.setmulti(type, [ map, 'hourly', indexnow, config.maxhours, config.minhours, 'hourly' ])
|
||||
|
||||
if config.addon.enablehour:
|
||||
self.setmulti(type, [ map, 'hourly', indexmid, config.maxhours, config.minhours, 'hour' ])
|
||||
|
||||
# Daily (Compatibility)
|
||||
elif map[0] == 'daily':
|
||||
self.setmulti(type, [ map, 'daily', indexday, config.maxdays, config.mindays, 'daily' ])
|
||||
self.setmulti(type, [ map, 'daily', indexday, config.maxdays, config.mindays, 'day' ])
|
||||
|
||||
# Daily (Advanced)
|
||||
elif map[0] == 'dailyskin' and config.addon.skin:
|
||||
self.setmulti(type, [ map, 'daily', indexday, config.maxdays, config.mindays, 'daily' ])
|
||||
self.setmulti(type, [ map, 'daily', indexday, config.maxdays, config.mindays, 'day' ])
|
||||
|
||||
# Daily (KODI)
|
||||
elif map[0] == 'dailykodi' and self.mode == 'kodi':
|
||||
self.setmulti(type, [ map, 'daily', indexday, config.maxdays, config.mindays, 'daily' ])
|
||||
self.setmulti(type, [ map, 'daily', indexday, config.maxdays, config.mindays, 'day' ])
|
||||
|
||||
### SET CURRENT
|
||||
def setcurrent(self, type, locid):
|
||||
|
||||
# Data
|
||||
self.data[type] = utils.getfile(f'{locid}/{type}.json')
|
||||
if not self.data[type]:
|
||||
utils.log(f'No {type} data for location {locid}', 2)
|
||||
return
|
||||
|
||||
# Update data
|
||||
utils.log(f'[LOC{locid}] Updating current: {type}', 3)
|
||||
|
||||
for map in config.map.get(type):
|
||||
|
||||
# Current (Compatibility)
|
||||
if map[0] == 'current':
|
||||
self.setmap(type, map, locid=locid)
|
||||
|
||||
# Current (Advanced)
|
||||
elif map[0] == 'currentskin' and config.addon.skin:
|
||||
self.setmap(type, map, locid=locid)
|
||||
|
||||
### SET LOCATIONS
|
||||
def setlocs(self):
|
||||
locs = 0
|
||||
for locid in range(1, config.addon.maxlocs):
|
||||
loc = utils.setting(f'loc{locid}')
|
||||
locuser = utils.setting(f'loc{locid}user')
|
||||
|
||||
if loc:
|
||||
locs += 1
|
||||
|
||||
# Set "Current.X" only if called from service
|
||||
if self.mode != 'kodi':
|
||||
config.loc(locid)
|
||||
for map in config.map:
|
||||
self.setcurrent(map, locid)
|
||||
|
||||
if locuser:
|
||||
utils.setprop(f'location{locid}', locuser)
|
||||
else:
|
||||
utils.setprop(f'location{locid}', loc)
|
||||
else:
|
||||
utils.setprop(f'location{locid}', '')
|
||||
|
||||
utils.setprop('locations', locs)
|
||||
|
||||
### SET ALERT
|
||||
def setalert(self, type):
|
||||
|
||||
# Data
|
||||
self.data[type] = utils.getfile(f'{config.loc.id}/{type}.json')
|
||||
if not self.data[type]:
|
||||
utils.log(f'[Alert] No {type} data for location {config.loc.id}', 4)
|
||||
return
|
||||
|
||||
# Index
|
||||
idx = utils.index("now", self.data[type])
|
||||
if not idx:
|
||||
utils.log(f'[Alert] No {type} index for location {config.loc.id}', 4)
|
||||
return
|
||||
|
||||
# Notification
|
||||
loc = utils.setting(f'loc{config.loc.id}').split(',')[0]
|
||||
|
||||
for map in config.map.get(type):
|
||||
if map[3] == 'graph':
|
||||
utils.setalert(self.data[type], map, idx, config.loc.id, config.loc.cid, loc, self.mode)
|
||||
|
||||
### SET MULTI
|
||||
def setmulti(self, src, map):
|
||||
data = self.data[src]
|
||||
time = map[1]
|
||||
idx = map[2]
|
||||
max = map[3]
|
||||
min = map[4]
|
||||
prop = map[5]
|
||||
|
||||
if config.addon.skin is False and ( prop == 'hourly' or prop == 'daily' ):
|
||||
count = 1
|
||||
else:
|
||||
count = 0
|
||||
|
||||
for index in range(idx, idx + max, 1):
|
||||
map[0][2][0] = prop
|
||||
self.setmap(src, map[0], index, count)
|
||||
count += 1
|
||||
|
||||
count = -1
|
||||
for index in range(idx - 1, idx - min, -1):
|
||||
map[0][2][0] = prop
|
||||
self.setmap(src, map[0], index, count)
|
||||
count -= 1
|
||||
|
||||
### SET MAP
|
||||
def setmap(self, src, map, idx=None, count=None, locid=None):
|
||||
data = self.data[src]
|
||||
|
||||
# Property
|
||||
if idx is not None:
|
||||
if map[2][0] == 'day':
|
||||
property = f'{map[2][0]}{count}.{map[2][1]}'
|
||||
else:
|
||||
property = f'{map[2][0]}.{count}.{map[2][1]}'
|
||||
else:
|
||||
if locid:
|
||||
property = f'{map[2][0]}.{locid}.{map[2][1]}'
|
||||
else:
|
||||
property = f'{map[2][0]}.{map[2][1]}'
|
||||
|
||||
# Content
|
||||
try:
|
||||
content = utils.getprop(data, map, idx, count)
|
||||
except TypeError as e:
|
||||
utils.log(f'{property}: {type(e).__name__} {e}', 4)
|
||||
utils.clrprop(property)
|
||||
except Exception as e:
|
||||
utils.log(f'{property}: {type(e).__name__} {e}', 3)
|
||||
utils.clrprop(property)
|
||||
else:
|
||||
utils.setprop(property, content)
|
||||
|
||||
### GET MAP
|
||||
def getmap(self, type):
|
||||
|
||||
if not utils.setting(f'map{type}', 'bool'):
|
||||
return
|
||||
|
||||
# Check connectivity
|
||||
if not api.network():
|
||||
utils.log(f'[LOC{config.loc.id}] No network connectivity, maps not available ...', 3)
|
||||
return
|
||||
|
||||
# Download
|
||||
utils.log(f'[LOC{config.loc.id}] Downloading map: {type}', 3)
|
||||
|
||||
map = []
|
||||
x, y = utils.lat2coords(config.loc.lat, config.loc.lon, config.addon.mapzoom)
|
||||
tiles = [ [ x-1, y-1, 0, 0 ], [ x, y-1, 256, 0 ], [ x+1, y-1, 512, 0 ], [ x-1, y, 0, 256 ], [ x, y, 256, 256 ], [ x+1, y, 512, 256 ], [ x-1, y+1, 0, 512 ], [ x, y+1, 256, 512 ], [ x+1, y+1, 512, 512 ] ]
|
||||
|
||||
config.mapcache[type] = {}
|
||||
|
||||
# RV Index
|
||||
if type.startswith('rv'):
|
||||
time, path = api.getrvindex(type)
|
||||
|
||||
if time is None or path is None:
|
||||
utils.log(f'[LOC{config.loc.id}] RVIndex {type} currently not available ...', 3)
|
||||
return
|
||||
|
||||
# Other
|
||||
else:
|
||||
time = utils.dt('nowutcstamp')
|
||||
path = None
|
||||
|
||||
# Queue
|
||||
for count in range(0,9):
|
||||
s, w, n, e = utils.coords2bbox(tiles[count][0], tiles[count][1], config.addon.mapzoom)
|
||||
map.append([ config.loc.id, type, count, config.addon.mapzoom, tiles[count][0], tiles[count][1], tiles[count][2], tiles[count][3], path, time, s, w, n, e ])
|
||||
|
||||
# Download
|
||||
with ThreadPoolExecutor(3) as pool:
|
||||
pool.map(api.getmap, map)
|
||||
|
||||
# Merge
|
||||
api.mapmerge(map)
|
||||
|
||||
# Cleanup
|
||||
config.mapcache[type] = {}
|
||||
|
||||
dir = f'{config.addon_cache}/{config.loc.id}'
|
||||
files = sorted(list(Path(dir).glob(f'{type}_*')), reverse=True)
|
||||
history = config.addon.maphistory * 2
|
||||
|
||||
for idx in range(0,100):
|
||||
|
||||
try:
|
||||
file = files[idx]
|
||||
except:
|
||||
break
|
||||
else:
|
||||
if idx >= history:
|
||||
utils.log(f'[LOC{config.loc.id}] Removing old map: {file.stem}', 3)
|
||||
os.remove(file)
|
||||
|
||||
### PROPERTIES
|
||||
def setprop(self):
|
||||
|
||||
# Maps
|
||||
index = 1
|
||||
for layer in config.map_layers:
|
||||
|
||||
if not utils.setting(f'map{layer}', 'bool'):
|
||||
continue
|
||||
|
||||
dir = f'{config.addon_cache}/{config.loc.id}'
|
||||
files = sorted(list(Path(dir).glob(f'{layer}_*')), reverse=True)
|
||||
history = config.addon.maphistory * 2
|
||||
|
||||
# Area
|
||||
if files:
|
||||
ut = int(files[0].stem.split('_')[1])
|
||||
tz = utils.dt('stamploc', ut)
|
||||
date = tz.strftime(config.kodi.date)
|
||||
time = tz.strftime(config.kodi.time)
|
||||
|
||||
utils.setprop(f'Map.{index}.Area', f'{dir}/osm.png')
|
||||
utils.setprop(f'Map.{index}.Layer', f'{dir}/{layer}_{ut}.png')
|
||||
utils.setprop(f'Map.{index}.Heading', config.localization.layers.get(layer))
|
||||
utils.setprop(f'Map.{index}.Time', f'{date} {time}')
|
||||
utils.setprop(f'Map.{index}.Legend', '')
|
||||
else:
|
||||
for item in [ 'area', 'layer', 'heading', 'time', 'legend' ]:
|
||||
utils.setprop(f'Map.{index}.{item}', '')
|
||||
|
||||
# Layers
|
||||
for idx in range(0, history):
|
||||
|
||||
try:
|
||||
file = files[idx]
|
||||
except:
|
||||
utils.setprop(f'Map.{index}.Layer.{idx}', '')
|
||||
utils.setprop(f'Map.{index}.Time.{idx}', '')
|
||||
else:
|
||||
ut = int(file.stem.split('_')[1])
|
||||
tz = utils.dt('stamploc', ut)
|
||||
date = tz.strftime(config.kodi.date)
|
||||
time = tz.strftime(config.kodi.time)
|
||||
|
||||
utils.setprop(f'Map.{index}.Layer.{idx}', f'{dir}/{layer}_{ut}.png')
|
||||
utils.setprop(f'Map.{index}.Time.{idx}', f'{date} {time}')
|
||||
|
||||
index += 1
|
||||
|
||||
# Locations
|
||||
if config.loc.user:
|
||||
utils.setprop('current.location', config.loc.user)
|
||||
utils.setprop('location', config.loc.user)
|
||||
else:
|
||||
utils.setprop('current.location', config.loc.name.split(',')[0])
|
||||
utils.setprop('location', config.loc.name)
|
||||
|
||||
self.setlocs()
|
||||
|
||||
# Fetched
|
||||
for prop in [ 'current', 'weather', 'hourly', 'daily', 'map' ]:
|
||||
utils.setprop(f'{prop}.isfetched', 'true')
|
||||
|
||||
# Other
|
||||
utils.setprop('alerts', config.addon.alerts)
|
||||
utils.setprop('addon.icons', config.addon.icons)
|
||||
utils.setprop('addon.iconsdir', config.addon_icons)
|
||||
utils.setprop('WeatherProvider', 'open-meteo.com, rainviewer.com, weather.gc.ca, met.no')
|
||||
utils.setprop('WeatherProviderLogo', f'{config.addon_path}/resources/banner.png')
|
||||
|
||||
### NOTIFICATION
|
||||
def notification(self):
|
||||
queue = config.addon.msgqueue
|
||||
duration = utils.setting('alert_duration', 'int')
|
||||
|
||||
if queue:
|
||||
for alert in queue:
|
||||
utils.notification(alert[0], alert[1], alert[2], config.loc.id)
|
||||
utils.monitor.waitForAbort(duration)
|
||||
if utils.monitor.abortRequested():
|
||||
utils.log(f'Abort requested ...', 3)
|
||||
break
|
||||
|
||||
BIN
Kodi/Lenovo/addons/weather.openmeteo/resources/banner.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
Kodi/Lenovo/addons/weather.openmeteo/resources/fanart.jpg
Normal file
|
After Width: | Height: | Size: 309 KiB |
BIN
Kodi/Lenovo/addons/weather.openmeteo/resources/icon.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 14 KiB |