commit 58bbbaf6de8540830500169a0bfda61a50e60c19 Author: zhongjin Date: Sat Sep 22 10:26:24 2018 +0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e4a6c09 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +node_modules +.idea +tmp +admin/i18n/flat.txt +admin/i18n/*/flat.txt +iob_npm.done +package-lock.json \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..0f2ae68 --- /dev/null +++ b/.npmignore @@ -0,0 +1,9 @@ +gulpfile.js +tasks +tmp +test +.travis.yml +appveyor.yml +admin/i18n +iob_npm.done +package-lock.json \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..e04170f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +os: + - linux + - osx +language: node_js +node_js: + - '6' + - '8' + - '10' +before_script: + - npm install winston@2.3.1 + - 'npm install https://github.com/yunkong2/yunkong2.js-controller/tarball/master --production' +env: + - CXX=g++-4.8 +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-4.8 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2394c68 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 hobbyquaker + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7404cc0 --- /dev/null +++ b/README.md @@ -0,0 +1,123 @@ +![Logo](admin/yr.png) +# yunkong2.yr +================================= +[![NPM version](http://img.shields.io/npm/v/yunkong2.yr.svg)](https://www.npmjs.com/package/yunkong2.yr) +[![Downloads](https://img.shields.io/npm/dm/yunkong2.yr.svg)](https://www.npmjs.com/package/yunkong2.yr) +[![Tests](https://travis-ci.org/yunkong2/yunkong2.yr.svg?branch=master)](https://travis-ci.org/yunkong2/yunkong2.yr) + +[![NPM](https://nodei.co/npm/yunkong2.yr.png?downloads=true)](https://nodei.co/npm/yunkong2.yr/) + +fetches 48h weather forecast from [yr.no](yr.no) + +[yr.no](yr.no) is a joint service by the [Norwegian Meteorological Institute](met.no) and the [Norwegian Broadcasting Corporation](nrk.no) + +http://om.yr.no/verdata/free-weather-data/ + +**Note** - if _"Send missing translations to yunkong2.net"_ is activated (default) missing translations will be sent to yunkong2.net server. No ips or any additional info will be stored or analysed. Just missing translation. + +## Icons +Icons are taken from here [https://github.com/YR/weather-symbols](https://github.com/YR/weather-symbols) and belongs to yr.no. + +## Changelog +### 2.0.2 [2018-08-01] +* (bluefox) Warning! Breaking changes! States are renamed. +* (bluefox) Refactoring of states and roles + +### 1.0.6 [2017-05-27] +* (Andre) Update iconset + +### 1.0.5 [2016-10-10] +* (bluefox) move weather widgets to this adapter + +## 1.0.4 [2016-07-06] +* (bluefox) fix link to readme + +### 1.0.3 [2016-05-17] +* (bluefox) change readme path + +### 1.0.2 [2016-05-16] +* (bluefox) add translations + +### 1.0.1 [2016-04-25] +* (bluefox) add translations + +### 1.0.0 [2016-03-15] +* (bluefox) change parsing of cities + +### 0.1.9 [2015-10-28] +* (bluefox) fix error with translations + +### 0.1.8 [2015-10-27] +* (bluefox) translations +* (bluefox) automatically upload of missing translations to yunkong2.net + +### 0.1.7 [2015-07-10] +* (bluefox) make yr works with metro widgets + +### 0.1.6 [2015-06-12] +* (bluefox) translations + +### 0.1.5 [2015-03-26] +* (bluefox) translations + +### 0.1.4 [2015-03-24] +* (bluefox) remove unit "%" for "wind direction" + +### 0.1.3 [2015-03-22] +* (bluefox) fix error with tomorrow and day after tomorrow + +### 0.1.2 [2015-03-08] +* (bluefox) correct links to yr.no web site + +### 0.1.1 +* (bluefox) add translates for the weather states in other languages + +### 0.1.0 +* (bluefox) update yr on the new objects model + +### 0.0.4 +* (hobbyquaker) prepend "forecast." to state IDs + +### 0.0.3 +* (hobbyquaker) settings ui with autocomplete for location +* (hobbyquaker) renamed yr_forecast to forecast +* (hobbyquaker) added children attribute +* (hobbyquaker) decreased log verbosity +* (hobbyquaker) fixes + +### 0.0.2 + +* (hobbyquaker) fixes + + +### 0.0.1 + +* (hobbyquaker) first release + +## Todo + +* setState forecast_object + +## License + +The MIT License (MIT) + +Copyright (c) 2014-2018 hobbyquaker + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/admin/icons/01d.svg b/admin/icons/01d.svg new file mode 100644 index 0000000..e8f7541 --- /dev/null +++ b/admin/icons/01d.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/01m.svg b/admin/icons/01m.svg new file mode 100644 index 0000000..e6afadc --- /dev/null +++ b/admin/icons/01m.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/01n.svg b/admin/icons/01n.svg new file mode 100644 index 0000000..23f9b5e --- /dev/null +++ b/admin/icons/01n.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/02d.svg b/admin/icons/02d.svg new file mode 100644 index 0000000..2daaed3 --- /dev/null +++ b/admin/icons/02d.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/02m.svg b/admin/icons/02m.svg new file mode 100644 index 0000000..1d557ca --- /dev/null +++ b/admin/icons/02m.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/02n.svg b/admin/icons/02n.svg new file mode 100644 index 0000000..1814563 --- /dev/null +++ b/admin/icons/02n.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/03d.svg b/admin/icons/03d.svg new file mode 100644 index 0000000..827e688 --- /dev/null +++ b/admin/icons/03d.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/03m.svg b/admin/icons/03m.svg new file mode 100644 index 0000000..cb3647b --- /dev/null +++ b/admin/icons/03m.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/03n.svg b/admin/icons/03n.svg new file mode 100644 index 0000000..eeb643f --- /dev/null +++ b/admin/icons/03n.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/04.svg b/admin/icons/04.svg new file mode 100644 index 0000000..eb98fe8 --- /dev/null +++ b/admin/icons/04.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/05d.svg b/admin/icons/05d.svg new file mode 100644 index 0000000..0226b47 --- /dev/null +++ b/admin/icons/05d.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/05m.svg b/admin/icons/05m.svg new file mode 100644 index 0000000..80ede84 --- /dev/null +++ b/admin/icons/05m.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/05n.svg b/admin/icons/05n.svg new file mode 100644 index 0000000..58fe847 --- /dev/null +++ b/admin/icons/05n.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/06d.svg b/admin/icons/06d.svg new file mode 100644 index 0000000..e5da5a1 --- /dev/null +++ b/admin/icons/06d.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/06m.svg b/admin/icons/06m.svg new file mode 100644 index 0000000..4fca933 --- /dev/null +++ b/admin/icons/06m.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/06n.svg b/admin/icons/06n.svg new file mode 100644 index 0000000..1eb876d --- /dev/null +++ b/admin/icons/06n.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/07d.svg b/admin/icons/07d.svg new file mode 100644 index 0000000..97e5afa --- /dev/null +++ b/admin/icons/07d.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/07m.svg b/admin/icons/07m.svg new file mode 100644 index 0000000..e233169 --- /dev/null +++ b/admin/icons/07m.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/07n.svg b/admin/icons/07n.svg new file mode 100644 index 0000000..c43a73b --- /dev/null +++ b/admin/icons/07n.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/08d.svg b/admin/icons/08d.svg new file mode 100644 index 0000000..b80f82a --- /dev/null +++ b/admin/icons/08d.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/08m.svg b/admin/icons/08m.svg new file mode 100644 index 0000000..90e936a --- /dev/null +++ b/admin/icons/08m.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/08n.svg b/admin/icons/08n.svg new file mode 100644 index 0000000..b502064 --- /dev/null +++ b/admin/icons/08n.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/09.svg b/admin/icons/09.svg new file mode 100644 index 0000000..1da5ea7 --- /dev/null +++ b/admin/icons/09.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/10.svg b/admin/icons/10.svg new file mode 100644 index 0000000..9a85ccd --- /dev/null +++ b/admin/icons/10.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/11.svg b/admin/icons/11.svg new file mode 100644 index 0000000..0e73a8c --- /dev/null +++ b/admin/icons/11.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/12.svg b/admin/icons/12.svg new file mode 100644 index 0000000..28a297f --- /dev/null +++ b/admin/icons/12.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/13.svg b/admin/icons/13.svg new file mode 100644 index 0000000..25fed85 --- /dev/null +++ b/admin/icons/13.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/14.svg b/admin/icons/14.svg new file mode 100644 index 0000000..c955133 --- /dev/null +++ b/admin/icons/14.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/15.svg b/admin/icons/15.svg new file mode 100644 index 0000000..3cb13b4 --- /dev/null +++ b/admin/icons/15.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/20d.svg b/admin/icons/20d.svg new file mode 100644 index 0000000..01cb6e7 --- /dev/null +++ b/admin/icons/20d.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/20m.svg b/admin/icons/20m.svg new file mode 100644 index 0000000..1b53807 --- /dev/null +++ b/admin/icons/20m.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/20n.svg b/admin/icons/20n.svg new file mode 100644 index 0000000..904f987 --- /dev/null +++ b/admin/icons/20n.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/21d.svg b/admin/icons/21d.svg new file mode 100644 index 0000000..08752db --- /dev/null +++ b/admin/icons/21d.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/21m.svg b/admin/icons/21m.svg new file mode 100644 index 0000000..7fc3610 --- /dev/null +++ b/admin/icons/21m.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/21n.svg b/admin/icons/21n.svg new file mode 100644 index 0000000..37e7df2 --- /dev/null +++ b/admin/icons/21n.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/22.svg b/admin/icons/22.svg new file mode 100644 index 0000000..6a99951 --- /dev/null +++ b/admin/icons/22.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/23.svg b/admin/icons/23.svg new file mode 100644 index 0000000..907428f --- /dev/null +++ b/admin/icons/23.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/24d.svg b/admin/icons/24d.svg new file mode 100644 index 0000000..016786a --- /dev/null +++ b/admin/icons/24d.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/24m.svg b/admin/icons/24m.svg new file mode 100644 index 0000000..e7fff4f --- /dev/null +++ b/admin/icons/24m.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/24n.svg b/admin/icons/24n.svg new file mode 100644 index 0000000..3831919 --- /dev/null +++ b/admin/icons/24n.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/25d.svg b/admin/icons/25d.svg new file mode 100644 index 0000000..e2922a2 --- /dev/null +++ b/admin/icons/25d.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/25m.svg b/admin/icons/25m.svg new file mode 100644 index 0000000..e559dd6 --- /dev/null +++ b/admin/icons/25m.svg @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/25n.svg b/admin/icons/25n.svg new file mode 100644 index 0000000..4389a49 --- /dev/null +++ b/admin/icons/25n.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/26d.svg b/admin/icons/26d.svg new file mode 100644 index 0000000..bdca36d --- /dev/null +++ b/admin/icons/26d.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/26m.svg b/admin/icons/26m.svg new file mode 100644 index 0000000..42308f9 --- /dev/null +++ b/admin/icons/26m.svg @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/26n.svg b/admin/icons/26n.svg new file mode 100644 index 0000000..4b8be4d --- /dev/null +++ b/admin/icons/26n.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/27d.svg b/admin/icons/27d.svg new file mode 100644 index 0000000..dbe2715 --- /dev/null +++ b/admin/icons/27d.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/27m.svg b/admin/icons/27m.svg new file mode 100644 index 0000000..18ed7fc --- /dev/null +++ b/admin/icons/27m.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/27n.svg b/admin/icons/27n.svg new file mode 100644 index 0000000..234bac9 --- /dev/null +++ b/admin/icons/27n.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/28d.svg b/admin/icons/28d.svg new file mode 100644 index 0000000..62bf909 --- /dev/null +++ b/admin/icons/28d.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/28m.svg b/admin/icons/28m.svg new file mode 100644 index 0000000..58d0649 --- /dev/null +++ b/admin/icons/28m.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/28n.svg b/admin/icons/28n.svg new file mode 100644 index 0000000..6b0b6d0 --- /dev/null +++ b/admin/icons/28n.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/29d.svg b/admin/icons/29d.svg new file mode 100644 index 0000000..2e296a3 --- /dev/null +++ b/admin/icons/29d.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/29m.svg b/admin/icons/29m.svg new file mode 100644 index 0000000..9abac75 --- /dev/null +++ b/admin/icons/29m.svg @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/29n.svg b/admin/icons/29n.svg new file mode 100644 index 0000000..47c25ab --- /dev/null +++ b/admin/icons/29n.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/30.svg b/admin/icons/30.svg new file mode 100644 index 0000000..cdab6e2 --- /dev/null +++ b/admin/icons/30.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/31.svg b/admin/icons/31.svg new file mode 100644 index 0000000..503811e --- /dev/null +++ b/admin/icons/31.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/32.svg b/admin/icons/32.svg new file mode 100644 index 0000000..2729d5c --- /dev/null +++ b/admin/icons/32.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/33.svg b/admin/icons/33.svg new file mode 100644 index 0000000..d8b8d25 --- /dev/null +++ b/admin/icons/33.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/34.svg b/admin/icons/34.svg new file mode 100644 index 0000000..719ed5d --- /dev/null +++ b/admin/icons/34.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/40d.svg b/admin/icons/40d.svg new file mode 100644 index 0000000..f02e940 --- /dev/null +++ b/admin/icons/40d.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/40m.svg b/admin/icons/40m.svg new file mode 100644 index 0000000..2648cb4 --- /dev/null +++ b/admin/icons/40m.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/40n.svg b/admin/icons/40n.svg new file mode 100644 index 0000000..5a483fc --- /dev/null +++ b/admin/icons/40n.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/41d.svg b/admin/icons/41d.svg new file mode 100644 index 0000000..89f60eb --- /dev/null +++ b/admin/icons/41d.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/41m.svg b/admin/icons/41m.svg new file mode 100644 index 0000000..b97fbe2 --- /dev/null +++ b/admin/icons/41m.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/41n.svg b/admin/icons/41n.svg new file mode 100644 index 0000000..adb85ef --- /dev/null +++ b/admin/icons/41n.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/42d.svg b/admin/icons/42d.svg new file mode 100644 index 0000000..7a162bd --- /dev/null +++ b/admin/icons/42d.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/42m.svg b/admin/icons/42m.svg new file mode 100644 index 0000000..d31bb3f --- /dev/null +++ b/admin/icons/42m.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/42n.svg b/admin/icons/42n.svg new file mode 100644 index 0000000..cd283b5 --- /dev/null +++ b/admin/icons/42n.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/43d.svg b/admin/icons/43d.svg new file mode 100644 index 0000000..d9e146d --- /dev/null +++ b/admin/icons/43d.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/43m.svg b/admin/icons/43m.svg new file mode 100644 index 0000000..c275176 --- /dev/null +++ b/admin/icons/43m.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/43n.svg b/admin/icons/43n.svg new file mode 100644 index 0000000..aa71b14 --- /dev/null +++ b/admin/icons/43n.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/44d.svg b/admin/icons/44d.svg new file mode 100644 index 0000000..0b9f99e --- /dev/null +++ b/admin/icons/44d.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/44m.svg b/admin/icons/44m.svg new file mode 100644 index 0000000..e2d8eb8 --- /dev/null +++ b/admin/icons/44m.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/44n.svg b/admin/icons/44n.svg new file mode 100644 index 0000000..54b0cf2 --- /dev/null +++ b/admin/icons/44n.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/45d.svg b/admin/icons/45d.svg new file mode 100644 index 0000000..06ab067 --- /dev/null +++ b/admin/icons/45d.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/45m.svg b/admin/icons/45m.svg new file mode 100644 index 0000000..a1321a8 --- /dev/null +++ b/admin/icons/45m.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/45n.svg b/admin/icons/45n.svg new file mode 100644 index 0000000..fadfc89 --- /dev/null +++ b/admin/icons/45n.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/46.svg b/admin/icons/46.svg new file mode 100644 index 0000000..67a1c8e --- /dev/null +++ b/admin/icons/46.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/47.svg b/admin/icons/47.svg new file mode 100644 index 0000000..eaf49fe --- /dev/null +++ b/admin/icons/47.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/48.svg b/admin/icons/48.svg new file mode 100644 index 0000000..e67ff4e --- /dev/null +++ b/admin/icons/48.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/49.svg b/admin/icons/49.svg new file mode 100644 index 0000000..4ece601 --- /dev/null +++ b/admin/icons/49.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/50.svg b/admin/icons/50.svg new file mode 100644 index 0000000..6e6a05d --- /dev/null +++ b/admin/icons/50.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/icons/LICENSE.txt b/admin/icons/LICENSE.txt new file mode 100644 index 0000000..efa0e4a --- /dev/null +++ b/admin/icons/LICENSE.txt @@ -0,0 +1,24 @@ +- https://github.com/YR/weather-symbols + +The MIT License (MIT) + +Copyright (c) 2015-2017 Yr + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/admin/index.html b/admin/index.html new file mode 100644 index 0000000..1f87da4 --- /dev/null +++ b/admin/index.html @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + +
+ + + +

yr.no adapter settings

+

+ +

+

+ + +

+

+ +

+

yrDescription

+
+ + diff --git a/admin/index_m.html b/admin/index_m.html new file mode 100644 index 0000000..dafae6a --- /dev/null +++ b/admin/index_m.html @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + Use english metric system +
+
+
+
+ + Send missing translations to yunkong2.net +
+
+
+
+ yrDescription +
+
+
+ + diff --git a/admin/words.js b/admin/words.js new file mode 100644 index 0000000..5239fee --- /dev/null +++ b/admin/words.js @@ -0,0 +1,12 @@ +// DO NOT EDIT THIS FILE!!! IT WILL BE AUTOMATICALLY GENERATED FROM /admin/i18n +/* global systemDictionary:true */ +'use strict'; + +systemDictionary = { + "yr.no adapter settings": { "en": "yr.no adapter settings", "de": "Yr.no Adaptereinstellungen", "ru": "Настройки адаптера yr.no", "pt": "yr.no configurações do adaptador", "nl": "yr.no adapter-instellingen", "fr": "yr.no paramètres de la carte", "it": "yr.no impostazioni della scheda", "es": "configuración del adaptador yr.no", "pl": "yr.no ustawienia adaptera"}, + "Place": { "en": "Place", "de": "Ort", "ru": "Место", "pt": "Lugar, colocar", "nl": "Plaats", "fr": "Endroit", "it": "Posto", "es": "Lugar", "pl": "Miejsce"}, + "Language": { "en": "Language", "de": "Sprache", "ru": "Язык", "pt": "Língua", "nl": "Taal", "fr": "La langue", "it": "linguaggio", "es": "Idioma", "pl": "Język"}, + "Send missing translations to yunkong2.net": { "en": "Send missing translations to yunkong2.net", "de": "Senden Sie fehlende Übersetzungen an yunkong2.net", "ru": "Отправить недостающие переводы на yunkong2.net", "pt": "Envie traduções faltando para yunkong2.net", "nl": "Verzend ontbrekende vertalingen naar yunkong2.net", "fr": "Envoyer des traductions manquantes à yunkong2.net", "it": "Invia traduzioni mancanti a yunkong2.net", "es": "Envía traducciones faltantes a yunkong2.net", "pl": "Wyślij brakujące tłumaczenia do yunkong2.net"}, + "Use english metric system": { "en": "Use english metric system(°F, miles)", "de": "Verwenden Sie das englische metrische System (°F, Meilen)", "ru": "Используйте английскую метрическую систему (°F, мили)", "pt": "Use o sistema métrico inglês (°F, milhas)", "nl": "Gebruik het Engelse metrische systeem (°F, mijl)", "fr": "Utiliser le système métrique anglais (°F, miles)", "it": "Usa il sistema metrico inglese (°F, miglia)", "es": "Utilice el sistema métrico inglés (°F, millas)", "pl": "Użyj angielskiego systemu metrycznego (°F, mile)"}, + "yrDescription": { "en": "yr.no is a joint service by the Norwegian Meteorological Institute and the Norwegian Broadcasting Corporation", "de": "yr.no ist ein gemeinsamer Service des Norwegisches Meteorologisches Institut und die Norwegische Rundfunkgesellschaft", "ru": "yr.no является совместным сервисом с помощью Норвежский метеорологический институт и Норвежская вещательная корпорация", "pt": "yr.no é um serviço conjunto da Instituto Meteorológico Norueguês e a Corporação Norueguesa de Radiodifusão", "nl": "yr.no is een gezamenlijke service van het doel Norwegian Meteorological Institute en de Norwegian Broadcasting Corporation", "fr": "yr.no est un service commun de la cible Norwegian Meteorological Institute et Norwegian Broadcasting Corporation", "it": "yr.no è un servizio comune di Norwegian Meteorological Institute e Norwegian Broadcasting Corporation", "es": "yr.no es un servicio conjunto del objetivo Norwegian Meteorological Institute y la Corporación noruega de radiodifusión", "pl": "yr.no to usługa wspólna według celu Norweski Instytut Meteorologiczny i Norweska korporacja radiowa"} +}; \ No newline at end of file diff --git a/admin/yr.png b/admin/yr.png new file mode 100644 index 0000000..ce5087e Binary files /dev/null and b/admin/yr.png differ diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..afce81d --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,21 @@ +version: 'test-{build}' +environment: + matrix: + - nodejs_version: '6' + - nodejs_version: '8' + - nodejs_version: '10' +platform: + - x86 + - x64 +clone_folder: 'c:\projects\%APPVEYOR_PROJECT_NAME%' +install: + - ps: 'Install-Product node $env:nodejs_version $env:platform' + - npm install + - npm install winston@2.3.1 + - 'npm install https://github.com/yunkong2/yunkong2.js-controller/tarball/master --production' +test_script: + - echo %cd% + - node --version + - npm --version + - npm test +build: 'off' diff --git a/forecast.xml b/forecast.xml new file mode 100644 index 0000000..770842d --- /dev/null +++ b/forecast.xml @@ -0,0 +1,403 @@ + + + + Karlsruhe + Administration centre + Germany + + + + + + + + + + + + + + + + + 2015-01-30T16:39:15 + 2015-01-31T05:00:00 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..bb9412e --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,426 @@ +'use strict'; + +const gulp = require('gulp'); +const fs = require('fs'); +const pkg = require('./package.json'); +const iopackage = require('./io-package.json'); +const version = (pkg && pkg.version) ? pkg.version : iopackage.common.version; +/*const appName = getAppName(); + +function getAppName() { + const parts = __dirname.replace(/\\/g, '/').split('/'); + return parts[parts.length - 1].split('.')[0].toLowerCase(); +} +*/ +const fileName = 'words.js'; +const languages = { + en: {}, + de: {}, + ru: {}, + pt: {}, + nl: {}, + fr: {}, + it: {}, + es: {}, + pl: {} +}; + +function lang2data(lang, isFlat) { + let str = isFlat ? '' : '{\n'; + let count = 0; + for (const w in lang) { + if (lang.hasOwnProperty(w)) { + count++; + if (isFlat) { + str += (lang[w] === '' ? (isFlat[w] || w) : lang[w]) + '\n'; + } else { + const key = ' "' + w.replace(/"/g, '\\"') + '": '; + str += key + '"' + lang[w].replace(/"/g, '\\"') + '",\n'; + } + } + } + if (!count) return isFlat ? '' : '{\n}'; + if (isFlat) { + return str; + } else { + return str.substring(0, str.length - 2) + '\n}'; + } +} + +function readWordJs(src) { + try { + let words; + if (fs.existsSync(src + 'js/' + fileName)) { + words = fs.readFileSync(src + 'js/' + fileName).toString(); + } else { + words = fs.readFileSync(src + fileName).toString(); + } + + const lines = words.split(/\r\n|\r|\n/g); + let i = 0; + while (!lines[i].match(/^systemDictionary = {/) && !lines[i].match(/^module.exports = {/)) { + i++; + } + lines.splice(0, i); + + // remove last empty lines + i = lines.length - 1; + while (!lines[i]) { + i--; + } + if (i < lines.length - 1) { + lines.splice(i + 1); + } + + lines[0] = lines[0].replace('systemDictionary = ', ''); + lines[0] = lines[0].replace('module.exports = ', ''); + lines[lines.length - 1] = lines[lines.length - 1].trim().replace(/};$/, '}'); + words = lines.join('\n'); + const resultFunc = new Function('return ' + words + ';'); + + return resultFunc(); + } catch (e) { + return null; + } +} +function padRight(text, totalLength) { + return text + (text.length < totalLength ? new Array(totalLength - text.length).join(' ') : ''); +} +function writeWordJs(data, src, isModule) { + let text = '// DO NOT EDIT THIS FILE!!! IT WILL BE AUTOMATICALLY GENERATED FROM ' + src.replace(/^\./, '') + 'i18n\n'; + text += '/* global systemDictionary:true */\n'; + text += '\'use strict\';\n\n'; + + if (isModule) { + text += 'module.exports = {\n'; + } else { + text += 'systemDictionary = {\n'; + } + for (const word in data) { + if (data.hasOwnProperty(word)) { + text += ' ' + padRight('"' + word.replace(/"/g, '\\"') + '": {', 50); + let line = ''; + for (const lang in data[word]) { + if (data[word].hasOwnProperty(lang)) { + line += '"' + lang + '": "' + padRight(data[word][lang].replace(/"/g, '\\"') + '",', 50) + ' '; + } + } + if (line) { + line = line.trim(); + line = line.substring(0, line.length - 1); + } + text += line + '},\n'; + } + } + text = text.replace(/},\n$/, '}\n'); + text += '};'; + + if (fs.existsSync(src + 'js/' + fileName)) { + fs.writeFileSync(src + 'js/' + fileName, text); + } else { + fs.writeFileSync(src + '' + fileName, text); + } +} + +const EMPTY = ''; + +function words2languages(src) { + const langs = Object.assign({}, languages); + const data = readWordJs(src); + if (data) { + for (const word in data) { + if (data.hasOwnProperty(word)) { + for (const lang in data[word]) { + if (data[word].hasOwnProperty(lang)) { + langs[lang][word] = data[word][lang]; + // pre-fill all other languages + for (const j in langs) { + if (langs.hasOwnProperty(j)) { + langs[j][word] = langs[j][word] || EMPTY; + } + } + } + } + } + } + if (!fs.existsSync(src + 'i18n/')) { + fs.mkdirSync(src + 'i18n/'); + } + for (const l in langs) { + if (!langs.hasOwnProperty(l)) continue; + const keys = Object.keys(langs[l]); + //keys.sort(); + const obj = {}; + for (let k = 0; k < keys.length; k++) { + obj[keys[k]] = langs[l][keys[k]]; + } + if (!fs.existsSync(src + 'i18n/' + l)) { + fs.mkdirSync(src + 'i18n/' + l); + } + + fs.writeFileSync(src + 'i18n/' + l + '/translations.json', lang2data(obj)); + } + } else { + console.error('Cannot read or parse ' + fileName); + } +} +function words2languagesFlat(src) { + const langs = Object.assign({}, languages); + const data = readWordJs(src); + if (data) { + for (const word in data) { + if (data.hasOwnProperty(word)) { + for (const lang in data[word]) { + if (data[word].hasOwnProperty(lang)) { + langs[lang][word] = data[word][lang]; + // pre-fill all other languages + for (const j in langs) { + if (langs.hasOwnProperty(j)) { + langs[j][word] = langs[j][word] || EMPTY; + } + } + } + } + } + } + const keys = Object.keys(langs.en); + //keys.sort(); + for (const l in langs) { + if (!langs.hasOwnProperty(l)) continue; + const obj = {}; + for (let k = 0; k < keys.length; k++) { + obj[keys[k]] = langs[l][keys[k]]; + } + langs[l] = obj; + } + if (!fs.existsSync(src + 'i18n/')) { + fs.mkdirSync(src + 'i18n/'); + } + for (const ll in langs) { + if (!langs.hasOwnProperty(ll)) continue; + if (!fs.existsSync(src + 'i18n/' + ll)) { + fs.mkdirSync(src + 'i18n/' + ll); + } + + fs.writeFileSync(src + 'i18n/' + ll + '/flat.txt', lang2data(langs[ll], langs.en)); + } + fs.writeFileSync(src + 'i18n/flat.txt', keys.join('\n')); + } else { + console.error('Cannot read or parse ' + fileName); + } +} +function languagesFlat2words(src, isModule) { + const dirs = fs.readdirSync(src + 'i18n/'); + const langs = {}; + const bigOne = {}; + const order = Object.keys(languages); + dirs.sort(function (a, b) { + const posA = order.indexOf(a); + const posB = order.indexOf(b); + if (posA === -1 && posB === -1) { + if (a > b) return 1; + if (a < b) return -1; + return 0; + } else if (posA === -1) { + return -1; + } else if (posB === -1) { + return 1; + } else { + if (posA > posB) return 1; + if (posA < posB) return -1; + return 0; + } + }); + const keys = fs.readFileSync(src + 'i18n/flat.txt').toString().split('\n'); + + for (let l = 0; l < dirs.length; l++) { + if (dirs[l] === 'flat.txt') continue; + const lang = dirs[l]; + const values = fs.readFileSync(src + 'i18n/' + lang + '/flat.txt').toString().split('\n'); + langs[lang] = {}; + keys.forEach(function (word, i) { + langs[lang][word] = values[i].replace(/<\/ i>/g, '').replace(/<\/ b>/g, '').replace(/<\/ span>/g, '').replace(/% s/g, ' %s'); + }); + + const words = langs[lang]; + for (const word in words) { + if (words.hasOwnProperty(word)) { + bigOne[word] = bigOne[word] || {}; + if (words[word] !== EMPTY) { + bigOne[word][lang] = words[word]; + } + } + } + } + // read actual words.js + const aWords = readWordJs(); + + const temporaryIgnore = ['pt', 'fr', 'nl', 'flat.txt']; + if (aWords) { + // Merge words together + for (const w in aWords) { + if (aWords.hasOwnProperty(w)) { + if (!bigOne[w]) { + console.warn('Take from actual words.js: ' + w); + bigOne[w] = aWords[w] + } + dirs.forEach(function (lang) { + if (temporaryIgnore.indexOf(lang) !== -1) return; + if (!bigOne[w][lang]) { + console.warn('Missing "' + lang + '": ' + w); + } + }); + } + } + + } + + writeWordJs(bigOne, src, isModule); +} +function languages2words(src, isModule) { + const dirs = fs.readdirSync(src + 'i18n/'); + const langs = {}; + const bigOne = {}; + const order = Object.keys(languages); + dirs.sort(function (a, b) { + const posA = order.indexOf(a); + const posB = order.indexOf(b); + if (posA === -1 && posB === -1) { + if (a > b) return 1; + if (a < b) return -1; + return 0; + } else if (posA === -1) { + return -1; + } else if (posB === -1) { + return 1; + } else { + if (posA > posB) return 1; + if (posA < posB) return -1; + return 0; + } + }); + for (let l = 0; l < dirs.length; l++) { + if (dirs[l] === 'flat.txt') continue; + const lang = dirs[l]; + langs[lang] = fs.readFileSync(src + 'i18n/' + lang + '/translations.json').toString(); + langs[lang] = JSON.parse(langs[lang]); + const words = langs[lang]; + for (const word in words) { + if (words.hasOwnProperty(word)) { + bigOne[word] = bigOne[word] || {}; + if (words[word] !== EMPTY) { + bigOne[word][lang] = words[word]; + } + } + } + } + // read actual words.js + const aWords = readWordJs(); + + const temporaryIgnore = ['pt', 'fr', 'nl', 'it']; + if (aWords) { + // Merge words together + for (const w in aWords) { + if (aWords.hasOwnProperty(w)) { + if (!bigOne[w]) { + console.warn('Take from actual words.js: ' + w); + bigOne[w] = aWords[w] + } + dirs.forEach(function (lang) { + if (temporaryIgnore.indexOf(lang) !== -1) return; + if (!bigOne[w][lang]) { + console.warn('Missing "' + lang + '": ' + w); + } + }); + } + } + + } + + writeWordJs(bigOne, src, isModule); +} + +gulp.task('[Admin] words.js => json', function (done) { + words2languages('./admin/'); + done(); +}); + +gulp.task('[Admin] words.js => flat', function (done) { + words2languagesFlat('./admin/'); + done(); +}); + +gulp.task('[Admin] flat => words.js', function (done) { + languagesFlat2words('./admin/'); + done(); +}); + +gulp.task('[Admin] json => words.js', function (done) { + languages2words('./admin/'); + done(); +}); + +gulp.task('[lib] words.js => json', function (done) { + words2languages('./lib/'); + done(); +}); + +gulp.task('[lib] words.js => flat', function (done) { + words2languagesFlat('./lib/'); + done(); +}); + +gulp.task('[lib] flat => words.js', function (done) { + languagesFlat2words('./lib/', true); + done(); +}); + +gulp.task('[lib] json => words.js', function (done) { + languages2words('./lib/', true); + done(); +}); + + +gulp.task('updatePackages', function (done) { + iopackage.common.version = pkg.version; + iopackage.common.news = iopackage.common.news || {}; + if (!iopackage.common.news[pkg.version]) { + const news = iopackage.common.news; + const newNews = {}; + + newNews[pkg.version] = { + en: 'news', + de: 'neues', + ru: 'новое' + }; + iopackage.common.news = Object.assign(newNews, news); + } + fs.writeFileSync('io-package.json', JSON.stringify(iopackage, null, 4)); + done(); +}); + +gulp.task('updateReadme', function (done) { + const readme = fs.readFileSync('README.md').toString(); + const pos = readme.indexOf('## Changelog\n'); + if (pos !== -1) { + const readmeStart = readme.substring(0, pos + '## Changelog\n'.length); + const readmeEnd = readme.substring(pos + '## Changelog\n'.length); + + if (readme.indexOf(version) === -1) { + const timestamp = new Date(); + const date = timestamp.getFullYear() + '-' + + ('0' + (timestamp.getMonth() + 1).toString(10)).slice(-2) + '-' + + ('0' + (timestamp.getDate()).toString(10)).slice(-2); + + let news = ''; + if (iopackage.common.news && iopackage.common.news[pkg.version]) { + news += '* ' + iopackage.common.news[pkg.version].en; + } + + fs.writeFileSync('README.md', readmeStart + '### ' + version + ' (' + date + ')\n' + (news ? news + '\n\n' : '\n') + readmeEnd); + } + } + done(); +}); + +gulp.task('default', ['updatePackages', 'updateReadme']); \ No newline at end of file diff --git a/io-package.json b/io-package.json new file mode 100644 index 0000000..a961a05 --- /dev/null +++ b/io-package.json @@ -0,0 +1,544 @@ +{ + "common": { + "name": "yr", + "version": "2.0.2", + "news": { + "2.0.2": { + "en": "Warning! Breaking changes!\nRefactoring of states and roles", + "de": "Warnung! Breaking Änderungen!\nRefactoring von Zuständen und Rollen", + "ru": "Предупреждение! Нарушение изменений!\nРефакторинг состояний и ролей", + "pt": "Atenção! Quebrando mudanças!\nRefatoração de estados e papéis", + "nl": "Waarschuwing! Veranderingen doorbreken!\nRefactoring van staten en rollen", + "fr": "Attention! Briser les changements!\nRefactorisation des états et des rôles", + "it": "Avvertimento! Ultime modifiche!\nRefactoring di stati e ruoli", + "es": "¡Advertencia! ¡Rompiendo cambios!\nRefactorización de estados y roles", + "pl": "Ostrzeżenie! Łamanie zmian!\nRefaktoryzacja stanów i ról" + }, + "1.0.6": { + "en": "Update iconset", + "de": "Iconset geändert", + "ru": "Update iconset" + }, + "1.0.5": { + "en": "move weather widgets to this adapter", + "de": "Verschiebe Wetter-Widgets ins Adapter", + "ru": "Добавлены виджеты" + }, + "1.0.4": { + "en": "fix link to readme", + "de": "fix link to readme", + "ru": "fix link to readme" + } + }, + "title": "yr.no", + "titleLang": { + "en": "yr.no weather", + "de": "yr.no Wetter", + "ru": "yr.no погода", + "pt": "yr.no tempo", + "nl": "yr.nr weer", + "fr": "yr.no météo", + "it": "anno vecchio", + "es": "yr.no tiempo", + "pl": "yr.no pogody" + }, + "desc": { + "en": "Fetches 48h weather forecast from yr.no", + "de": "Holt 48h Wettervorhersage von yr.no", + "ru": "Получает прогноз погоды на 48h с yr.no", + "pt": "Fetches 48h previsão do tempo a partir de yr.no", + "nl": "Haal 48h weersvoorspelling op van yr.no", + "fr": "Fetchs 48h météo de yr.no", + "it": "Calcola le previsioni del tempo a 48 ore dall'anno n", + "es": "Obtiene 48h pronóstico del tiempo de yr.no", + "pl": "Pobiera 48-godzinną prognozę pogody z yr.no" + }, + "authors": [ + "hobbyquaker " + ], + "license": "MIT", + "platform": "Javascript/Node.js", + "mode": "schedule", + "schedule": "6 * * * *", + "enabled": true, + "loglevel": "info", + "materialize": true, + "keywords": ["weather", "forecast", "temperature", "rain", "yr", "meteorology"], + "main": "main.js", + "icon": "yr.png", + "extIcon": "https://git.spacen.net/yunkong2/yunkong2.yr/raw/master/admin/yr.png", + "readme": "https://github.com/yunkong2/yunkong2.yr/blob/master/README.md", + "allowInit": true, + "type": "weather" + }, + "native": { + "location": "", + "language": "", + "sendTranslations": true, + "nonMetric": false + }, + "objects": [ + + ], + "instanceObjects": [ + { + "_id": "forecast", + "type": "device", + "common": { + "name": "actual weather or forecast", + "role": "weather" + }, + "native": {} + }, + { + "_id": "forecast.day0", + "type": "channel", + "common": { + "name": "actual weather or forecast for today", + "role": "weather.forecast" + }, + "native": {} + }, + { + "_id": "forecast.day0.date", + "type": "state", + "common": { + "name": "Actual date", + "type": "string", + "role": "date", + "read": true, + "write": false, + "desc": "Actual date as string" + }, + "native": {} + }, + { + "_id": "forecast.day0.icon", + "type": "state", + "common": { + "name": "Actual icon", + "type": "string", + "role": "weather.icon", + "read": true, + "write": false, + "desc": "Path to icon" + }, + "native": {} + }, + { + "_id": "forecast.day0.state", + "type": "state", + "common": { + "name": "Actual state", + "type": "string", + "role": "weather.state", + "read": true, + "write": false, + "desc": "Description of weather" + }, + "native": {} + }, + { + "_id": "forecast.day0.temperatureActual", + "type": "state", + "common": { + "name": "Actual temperature", + "type": "number", + "role": "value.temperature", + "read": true, + "unit": "°C", + "write": false + }, + "native": {} + }, + { + "_id": "forecast.day0.temperatureMin", + "type": "state", + "common": { + "name": "Forecast of temperature for today", + "type": "number", + "role": "value.temperature.min.forecast.0", + "read": true, + "write": false, + "unit": "°C", + "desc": "Forecast of temperature for today. Minimum value." + }, + "native": {} + }, + { + "_id": "forecast.day0.temperatureMax", + "type": "state", + "common": { + "name": "Forecast of temperature for today", + "type": "number", + "role": "value.temperature.max.forecast.0", + "read": true, + "write": false, + "unit": "°C", + "desc": "Forecast of temperature for today. Maximum value." + }, + "native": {} + }, + { + "_id": "forecast.day0.precipitation", + "type": "state", + "common": { + "name": "Forecast of precipitation level for today (rain/snow)", + "type": "number", + "role": "weather.precipitation", + "read": true, + "write": false, + "unit": "mm", + "desc": "Forecast for rain or snow level in mm" + }, + "native": {} + }, + { + "_id": "forecast.day0.windDirection", + "type": "state", + "common": { + "name": "Forecast for wind direction for today", + "type": "string", + "role": "weather.direction.wind.forecast.0", + "read": true, + "write": false, + "desc": "Forecast for wind direction for today" + }, + "native": {} + }, + { + "_id": "forecast.day0.windSpeed", + "type": "state", + "common": { + "name": "Forecast for wind speed for today", + "type": "number", + "role": "value.speed.wind.forecast.0", + "read": true, + "write": false, + "unit": "km/h", + "desc": "Forecast for wind speed in km/h" + }, + "native": {} + }, + { + "_id": "forecast.day0.pressure", + "type": "state", + "common": { + "name": "Forecast for pressure for today", + "type": "number", + "role": "value.pressure.forecast.0", + "read": true, + "write": false, + "unit": "hPa", + "desc": "Forecast for pressure in hPa" + }, + "native": {} + }, + { + "_id": "forecast.day1", + "type": "channel", + "common": { + "name": "Forecast for tomorrow", + "role": "weather.forecast" + }, + "native": {} + }, + { + "_id": "forecast.day1.date", + "type": "state", + "common": { + "name": "Tomorrow date", + "type": "string", + "role": "date.forecast.1", + "read": true, + "write": false, + "desc": "Tomorrow date as string" + }, + "native": {} + }, + { + "_id": "forecast.day1.icon", + "type": "state", + "common": { + "name": "Icon for tomorrow", + "type": "string", + "role": "weather.icon.forecast.1", + "read": true, + "write": false, + "desc": "Path to icon for tomorrow" + }, + "native": {} + }, + { + "_id": "forecast.day1.state", + "type": "state", + "common": { + "name": "Text for tomorrow", + "type": "string", + "role": "weather.state.forecast.1", + "read": true, + "write": false, + "desc": "Description of weather for tomorrow" + }, + "native": {} + }, + { + "_id": "forecast.day1.temperatureMin", + "type": "state", + "common": { + "name": "Forecast of temperature for tomorrow", + "type": "number", + "role": "value.temperature.min.forecast.1", + "read": true, + "write": false, + "unit": "°C", + "desc": "Forecast of temperature for tomorrow. Minimum value." + }, + "native": {} + }, + { + "_id": "forecast.day1.temperatureMax", + "type": "state", + "common": { + "name": "Forecast of temperature for tomorrow", + "type": "number", + "role": "value.temperature.max.forecast.1", + "read": true, + "write": false, + "unit": "°C", + "desc": "Forecast of temperature for tomorrow. Maximum value." + }, + "native": {} + }, + { + "_id": "forecast.day1.precipitation", + "type": "state", + "common": { + "name": "Forecast of precipitation level for tomorrow (rain/snow)", + "type": "number", + "role": "value.precipitation.forecast.1", + "read": true, + "write": false, + "unit": "mm", + "desc": "Forecast for rain or snow level in mm" + }, + "native": {} + }, + { + "_id": "forecast.day1.windDirection", + "type": "state", + "common": { + "name": "Forecast for wind direction for tomorrow", + "type": "string", + "role": "weather.direction.wind.forecast.1", + "read": true, + "write": false, + "desc": "Forecast wind direction as text" + }, + "native": {} + }, + { + "_id": "forecast.day1.windSpeed", + "type": "state", + "common": { + "name": "Forecast for wind speed for tomorrow", + "type": "number", + "role": "value.speed.wind.forecast.1", + "read": true, + "write": false, + "unit": "km/h", + "desc": "Forecast for wind speed in km/h" + }, + "native": {} + }, + { + "_id": "forecast.day1.pressure", + "type": "state", + "common": { + "name": "Forecast for pressure for tomorrow", + "type": "number", + "role": "value.pressure.forecast.1", + "read": true, + "write": false, + "unit": "hPa", + "desc": "Forecast for pressure in hPa for tomorrow" + }, + "native": {} + }, + { + "_id": "forecast.day2", + "type": "channel", + "common": { + "name": "actual weather or forecast for the day after tomorrow", + "role": "weather.forecast" + }, + "native": {} + }, + { + "_id": "forecast.day2.date", + "type": "state", + "common": { + "name": "The day after tomorrow date", + "type": "string", + "role": "date.forecast.2", + "read": true, + "write": false, + "desc": "The day after tomorrow date as string" + }, + "native": {} + }, + { + "_id": "forecast.day2.icon", + "type": "state", + "common": { + "name": "Icon for the day after tomorrow", + "type": "string", + "role": "weather.icon.forecast.2", + "read": true, + "write": false, + "desc": "Path to icon for the day after tomorrow" + }, + "native": {} + }, + { + "_id": "forecast.day2.state", + "type": "state", + "common": { + "name": "Day after tomorrow state", + "type": "string", + "role": "weather.state.forecast.2", + "read": true, + "write": false, + "desc": "Description of weather for the day after tomorrow" + }, + "native": {} + }, + { + "_id": "forecast.day2.temperatureMin", + "type": "state", + "common": { + "name": "Forecast of temperature for the day after tomorrow", + "type": "number", + "role": "value.temperature.min.forecast.2", + "read": true, + "write": false, + "unit": "°C", + "desc": "Forecast of temperature for the day after tomorrow. Minimum value." + }, + "native": {} + }, + { + "_id": "forecast.day2.temperatureMax", + "type": "state", + "common": { + "name": "Forecast of temperature for the day after tomorrow", + "type": "number", + "role": "value.temperature.max.forecast.2", + "read": true, + "write": false, + "unit": "°C", + "desc": "Forecast of temperature for the day after tomorrow. Maximum value." + }, + "native": {} + }, + { + "_id": "forecast.day2.precipitation", + "type": "state", + "common": { + "name": "Forecast of precipitation level for the day after tomorrow (rain/snow)", + "type": "number", + "role": "value.precipitation.forecast.2", + "read": true, + "write": false, + "unit": "mm", + "desc": "Forecast for rain or snow level in mm" + }, + "native": {} + }, + { + "_id": "forecast.day2.windDirection", + "type": "state", + "common": { + "name": "Forecast for wind direction for the day after tomorrow", + "type": "string", + "role": "weather.direction.wind.forecast.2", + "read": true, + "write": false, + "desc": "Forecast for wind direction as text" + }, + "native": {} + }, + { + "_id": "forecast.day2.windSpeed", + "type": "state", + "common": { + "name": "Forecast for wind speed for the day after tomorrow", + "type": "number", + "role": "value.speed.wind.forecast.2", + "read": true, + "write": false, + "unit": "km/h", + "desc": "Forecast for wind speed in km/h" + }, + "native": {} + }, + { + "_id": "forecast.day2.pressure", + "type": "state", + "common": { + "name": "Forecast for pressure for the day after tomorrow", + "type": "number", + "role": "value.pressure.forecast.2", + "read": true, + "write": false, + "unit": "hPa", + "desc": "Forecast for pressure in hPa for the day after tomorrow" + }, + "native": {} + }, + { + "_id": "forecast.info", + "type": "channel", + "common": { + "name": "forecast links and objects" + }, + "native": {} + }, + { + "_id": "forecast.info.object", + "type": "state", + "common": { + "type": "object", + "name": "forecast table", + "role": "weather.table", + "read": true, + "write": false + }, + "native": {} + }, + { + "_id": "forecast.info.html", + "type": "state", + "common": { + "type": "string", + "name": "forecast html", + "role": "weather.html", + "read": true, + "write": false + }, + "native": {} + }, + { + "_id": "forecast.info.diagram", + "type": "state", + "common": { + "type": "string", + "name": "forecast diagram url", + "role": "weather.chart.url.forecast", + "read": true, + "write": false + }, + "native": {} + } + ] +} diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 0000000..a40fe76 --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,83 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +let controllerDir; +let appName; + +/** + * returns application name + * + * The name of the application can be different and this function finds it out. + * + * @returns {string} + */ + function getAppName() { + const parts = __dirname.replace(/\\/g, '/').split('/'); + return parts[parts.length - 2].split('.')[0]; +} + +/** + * looks for js-controller home folder + * + * @param {boolean} isInstall + * @returns {string} + */ +function getControllerDir(isInstall) { + // Find the js-controller location + const possibilities = [ + 'yunkong2.js-controller', + 'yunkong2.js-controller', + ]; + /** @type {string} */ + let controllerPath; + for (const pkg of possibilities) { + try { + const possiblePath = require.resolve(pkg); + if (fs.existsSync(possiblePath)) { + controllerPath = possiblePath; + break; + } + } catch (e) { /* not found */ } + } + if (!controllerPath) { + if (!isInstall) { + console.log('Cannot find js-controller'); + process.exit(10); + } else { + process.exit(); + } + } + // we found the controller + return path.dirname(controllerPath); +} + +/** + * reads controller base settings + * + * @alias getConfig + * @returns {object} + */ + function getConfig() { + let configPath; + if (fs.existsSync( + configPath = path.join(controllerDir, 'conf', appName + '.json') + )) { + return JSON.parse(fs.readFileSync(configPath, 'utf8')); + } else if (fs.existsSync( + configPath = path.join(controllerDir, 'conf', + appName.toLowerCase() + '.json') + )) { + return JSON.parse(fs.readFileSync(configPath, 'utf8')); + } else { + throw new Error('Cannot find ' + controllerDir + '/conf/' + appName + '.json'); + } +} +appName = getAppName(); +controllerDir = getControllerDir(typeof process !== 'undefined' && process.argv && process.argv.indexOf('--install') !== -1); +const adapter = require(path.join(controllerDir, 'lib/adapter.js')); + +exports.controllerDir = controllerDir; +exports.getConfig = getConfig; +exports.Adapter = adapter; +exports.appName = appName; diff --git a/lib/words.js b/lib/words.js new file mode 100644 index 0000000..5b42094 --- /dev/null +++ b/lib/words.js @@ -0,0 +1,109 @@ +// DO NOT EDIT THIS FILE!!! IT WILL BE AUTOMATICALLY GENERATED FROM /lib/i18n +/* global systemDictionary:true */ +'use strict'; + +module.exports = { + "Now": { "en": "Now", "de": "Jetzt", "ru": "Cейчас", "pt": "Now", "nl": "Now", "fr": "Now", "it": "Now", "es": "Now", "pl": "Now"}, + "Today": { "en": "Today", "de": "Heute", "ru": "Cегодня", "pt": "Today", "nl": "Today", "fr": "Today", "it": "Today", "es": "Today", "pl": "Today"}, + "Tomorrow": { "en": "Tomorrow", "de": "Morgen", "ru": "Завтра", "pt": "Tomorrow", "nl": "Tomorrow", "fr": "Tomorrow", "it": "Tomorrow", "es": "Tomorrow", "pl": "Tomorrow"}, + "After tomorrow": { "en": "After tomorrow", "de": "Übermorgen", "ru": "Послезавтра", "pt": "After tomorrow", "nl": "After tomorrow", "fr": "After tomorrow", "it": "After tomorrow", "es": "After tomorrow", "pl": "After tomorrow"}, + "Cloudy": { "en": "Cloudy", "de": "Wolkig", "ru": "Облачно", "pt": "Cloudy", "nl": "Cloudy", "fr": "Cloudy", "it": "Cloudy", "es": "Cloudy", "pl": "Cloudy"}, + "Snow": { "en": "Snow", "de": "Schnee", "ru": "Снег", "pt": "Snow", "nl": "Snow", "fr": "Snow", "it": "Snow", "es": "Snow", "pl": "Snow"}, + "Partly cloudy": { "en": "Partly cloudy", "de": "Teils wolkig", "ru": "Переменная облачность", "pt": "Partly cloudy", "nl": "Partly cloudy", "fr": "Partly cloudy", "it": "Partly cloudy", "es": "Partly cloudy", "pl": "Partly cloudy"}, + "Sleet": { "en": "Sleet", "de": "Schneeregen", "ru": "Снег с дождём", "pt": "Sleet", "nl": "Sleet", "fr": "Sleet", "it": "Sleet", "es": "Sleet", "pl": "Sleet"}, + "Tornado": { "en": "Tornado", "de": "Tornado", "ru": "Торнадо - сиди дома!", "pt": "Tornado", "nl": "Tornado", "fr": "Tornado", "it": "Tornado", "es": "Tornado", "pl": "Tornado"}, + "Tropical storm": { "en": "Tropical storm", "de": "Tropischer Sturm", "ru": "Тропический шторм", "pt": "Tropical storm", "nl": "Tropical storm", "fr": "Tropical storm", "it": "Tropical storm", "es": "Tropical storm", "pl": "Tropical storm"}, + "Hurricane": { "en": "Hurricane", "de": "Hurrikan", "ru": "Ураган", "pt": "Hurricane", "nl": "Hurricane", "fr": "Hurricane", "it": "Hurricane", "es": "Hurricane", "pl": "Hurricane"}, + "Severe thunderstorms": { "en": "Severe thunderstorms", "de": "Heftiges Gewitter", "ru": "Сильная непогода", "pt": "Severe thunderstorms", "nl": "Severe thunderstorms", "fr": "Severe thunderstorms", "it": "Severe thunderstorms", "es": "Severe thunderstorms", "pl": "Severe thunderstorms"}, + "Thunderstorms": { "en": "Thunderstorms", "de": "Gewitter", "ru": "Грозы", "pt": "Thunderstorms", "nl": "Thunderstorms", "fr": "Thunderstorms", "it": "Thunderstorms", "es": "Thunderstorms", "pl": "Thunderstorms"}, + "Mixed rain and snow": { "en": "Mixed rain and snow", "de": "Regen mit Schnee", "ru": "Дождь со снегом", "pt": "Mixed rain and snow", "nl": "Mixed rain and snow", "fr": "Mixed rain and snow", "it": "Mixed rain and snow", "es": "Mixed rain and snow", "pl": "Mixed rain and snow"}, + "Mixed rain and sleet": { "en": "Mixed rain and sleet", "de": "Regen mit Graupel", "ru": "Дождь с градом", "pt": "Mixed rain and sleet", "nl": "Mixed rain and sleet", "fr": "Mixed rain and sleet", "it": "Mixed rain and sleet", "es": "Mixed rain and sleet", "pl": "Mixed rain and sleet"}, + "Mixed snow and sleet": { "en": "Mixed snow and sleet", "de": "Schnee mit Graupel", "ru": "Снег с градом", "pt": "Mixed snow and sleet", "nl": "Mixed snow and sleet", "fr": "Mixed snow and sleet", "it": "Mixed snow and sleet", "es": "Mixed snow and sleet", "pl": "Mixed snow and sleet"}, + "Freezing drizzle": { "en": "Freezing drizzle", "de": "Eisnieselregen", "ru": "Изморозь", "pt": "Freezing drizzle", "nl": "Freezing drizzle", "fr": "Freezing drizzle", "it": "Freezing drizzle", "es": "Freezing drizzle", "pl": "Freezing drizzle"}, + "Drizzle": { "en": "Drizzle", "de": "Nieselregen", "ru": "Моросящий дождь", "pt": "Drizzle", "nl": "Drizzle", "fr": "Drizzle", "it": "Drizzle", "es": "Drizzle", "pl": "Drizzle"}, + "Freezing rain": { "en": "Freezing rain", "de": "Eisregen", "ru": "Ледяной дождь", "pt": "Freezing rain", "nl": "Freezing rain", "fr": "Freezing rain", "it": "Freezing rain", "es": "Freezing rain", "pl": "Freezing rain"}, + "Showers": { "en": "Showers", "de": "Regenschauer", "ru": "Ливень", "pt": "Showers", "nl": "Showers", "fr": "Showers", "it": "Showers", "es": "Showers", "pl": "Showers"}, + "Snow flurries": { "en": "Snow flurries", "de": "Schneetreiben", "ru": "Снегопад", "pt": "Snow flurries", "nl": "Snow flurries", "fr": "Snow flurries", "it": "Snow flurries", "es": "Snow flurries", "pl": "Snow flurries"}, + "Light snow showers": { "en": "Light snow showers", "de": "Leichter Regen mit Schnee", "ru": "Небольшой дождь со снегом", "pt": "Light snow showers", "nl": "Light snow showers", "fr": "Light snow showers", "it": "Light snow showers", "es": "Light snow showers", "pl": "Light snow showers"}, + "Bowing snow": { "en": "Bowing snow", "de": "Schnee", "ru": "Снег", "pt": "Bowing snow", "nl": "Bowing snow", "fr": "Bowing snow", "it": "Bowing snow", "es": "Bowing snow", "pl": "Bowing snow"}, + "Hail": { "en": "Hail", "de": "Hagel", "ru": "Град", "pt": "Hail", "nl": "Hail", "fr": "Hail", "it": "Hail", "es": "Hail", "pl": "Hail"}, + "Dust": { "en": "Dust", "de": "Staubig", "ru": "Пыльно", "pt": "Dust", "nl": "Dust", "fr": "Dust", "it": "Dust", "es": "Dust", "pl": "Dust"}, + "Foggy": { "en": "Foggy", "de": "Neblig", "ru": "Туманно", "pt": "Foggy", "nl": "Foggy", "fr": "Foggy", "it": "Foggy", "es": "Foggy", "pl": "Foggy"}, + "Haze": { "en": "Haze", "de": "Dunst", "ru": "Лёгкий туман", "pt": "Haze", "nl": "Haze", "fr": "Haze", "it": "Haze", "es": "Haze", "pl": "Haze"}, + "Smoky": { "en": "Smoky", "de": "Qualmig", "ru": "Задымление", "pt": "Smoky", "nl": "Smoky", "fr": "Smoky", "it": "Smoky", "es": "Smoky", "pl": "Smoky"}, + "Blustery": { "en": "Blustery", "de": "Stürmisch", "ru": "Порывистый ветер", "pt": "Blustery", "nl": "Blustery", "fr": "Blustery", "it": "Blustery", "es": "Blustery", "pl": "Blustery"}, + "Windy": { "en": "Windy", "de": "Windig", "ru": "Ветрянно", "pt": "Windy", "nl": "Windy", "fr": "Windy", "it": "Windy", "es": "Windy", "pl": "Windy"}, + "Cold": { "en": "Cold", "de": "Kalt", "ru": "Холодно", "pt": "Cold", "nl": "Cold", "fr": "Cold", "it": "Cold", "es": "Cold", "pl": "Cold"}, + "Mostly cloudy (night)": { "en": "Mostly cloudy (night)", "de": "Ü,berwiegend wölkig", "ru": "В основном облачно", "pt": "Mostly cloudy (night)", "nl": "Mostly cloudy (night)", "fr": "Mostly cloudy (night)", "it": "Mostly cloudy (night)", "es": "Mostly cloudy (night)", "pl": "Mostly cloudy (night)"}, + "Mostly cloudy (day)": { "en": "Mostly cloudy (day)", "de": "Ü,berwiegend wölkig", "ru": "В основном облачно", "pt": "Mostly cloudy (day)", "nl": "Mostly cloudy (day)", "fr": "Mostly cloudy (day)", "it": "Mostly cloudy (day)", "es": "Mostly cloudy (day)", "pl": "Mostly cloudy (day)"}, + "partly cloudy (night)": { "en": "partly cloudy (night)", "de": "Teilweise wolkig", "ru": "Местами облачно", "pt": "partly cloudy (night)", "nl": "partly cloudy (night)", "fr": "partly cloudy (night)", "it": "partly cloudy (night)", "es": "partly cloudy (night)", "pl": "partly cloudy (night)"}, + "partly cloudy (day)": { "en": "partly cloudy (day)", "de": "Meistens sonnig", "ru": "Приемущественно солнечно", "pt": "partly cloudy (day)", "nl": "partly cloudy (day)", "fr": "partly cloudy (day)", "it": "partly cloudy (day)", "es": "partly cloudy (day)", "pl": "partly cloudy (day)"}, + "Clear (night)": { "en": "Clear (night)", "de": "Klar", "ru": "Ясно", "pt": "Clear (night)", "nl": "Clear (night)", "fr": "Clear (night)", "it": "Clear (night)", "es": "Clear (night)", "pl": "Clear (night)"}, + "Sunny": { "en": "Sunny", "de": "Sonnig", "ru": "Солнечно", "pt": "Sunny", "nl": "Sunny", "fr": "Sunny", "it": "Sunny", "es": "Sunny", "pl": "Sunny"}, + "Fair (night)": { "en": "Fair (night)", "de": "Schönwetter", "ru": "Прекрасная погода", "pt": "Fair (night)", "nl": "Fair (night)", "fr": "Fair (night)", "it": "Fair (night)", "es": "Fair (night)", "pl": "Fair (night)"}, + "Fair (day)": { "en": "Fair (day)", "de": "Schöunwetter", "ru": "Прекрасная погода", "pt": "Fair (day)", "nl": "Fair (day)", "fr": "Fair (day)", "it": "Fair (day)", "es": "Fair (day)", "pl": "Fair (day)"}, + "Mixed rain and hail": { "en": "Mixed rain and hail", "de": "Regen mit Hagel", "ru": "Снег с градом", "pt": "Mixed rain and hail", "nl": "Mixed rain and hail", "fr": "Mixed rain and hail", "it": "Mixed rain and hail", "es": "Mixed rain and hail", "pl": "Mixed rain and hail"}, + "Hot": { "en": "Hot", "de": "Warm", "ru": "Жарко", "pt": "Hot", "nl": "Hot", "fr": "Hot", "it": "Hot", "es": "Hot", "pl": "Hot"}, + "Isolated thunderstorms": { "en": "Isolated thunderstorms", "de": "Vereinzeltes Gewitter", "ru": "Одиночные грозы", "pt": "Isolated thunderstorms", "nl": "Isolated thunderstorms", "fr": "Isolated thunderstorms", "it": "Isolated thunderstorms", "es": "Isolated thunderstorms", "pl": "Isolated thunderstorms"}, + "scattered thunderstorms": { "en": "scattered thunderstorms", "de": "Verstreutes Gewitter", "ru": "Грозы", "pt": "scattered thunderstorms", "nl": "scattered thunderstorms", "fr": "scattered thunderstorms", "it": "scattered thunderstorms", "es": "scattered thunderstorms", "pl": "scattered thunderstorms"}, + "scattered showers": { "en": "scattered showers", "de": "Verstreuter Regen", "ru": "Дождь", "pt": "scattered showers", "nl": "scattered showers", "fr": "scattered showers", "it": "scattered showers", "es": "scattered showers", "pl": "scattered showers"}, + "Heavy snow": { "en": "Heavy snow", "de": "Starker Schneefall", "ru": "Сильный снегопад", "pt": "Heavy snow", "nl": "Heavy snow", "fr": "Heavy snow", "it": "Heavy snow", "es": "Heavy snow", "pl": "Heavy snow"}, + "Scattered snow showers": { "en": "Scattered snow showers", "de": "Verstreuter Schneeregen", "ru": "Ливень с дождем", "pt": "Scattered snow showers", "nl": "Scattered snow showers", "fr": "Scattered snow showers", "it": "Scattered snow showers", "es": "Scattered snow showers", "pl": "Scattered snow showers"}, + "Thundershowers": { "en": "Thundershowers", "de": "Gewitterschauer", "ru": "Штормовой дождь", "pt": "Thundershowers", "nl": "Thundershowers", "fr": "Thundershowers", "it": "Thundershowers", "es": "Thundershowers", "pl": "Thundershowers"}, + "Snow showers": { "en": "Snow showers", "de": "Schneeregen", "ru": "Снег с дождем", "pt": "Snow showers", "nl": "Snow showers", "fr": "Snow showers", "it": "Snow showers", "es": "Snow showers", "pl": "Snow showers"}, + "Isolated thundershowers": { "en": "Isolated thundershowers", "de": "Vereinzelter Gewitterschauer", "ru": "Местами грозы", "pt": "Isolated thundershowers", "nl": "Isolated thundershowers", "fr": "Isolated thundershowers", "it": "Isolated thundershowers", "es": "Isolated thundershowers", "pl": "Isolated thundershowers"}, + "Fair": { "en": "Fair", "de": "Schönwetter", "ru": "Ясно", "pt": "Fair", "nl": "Fair", "fr": "Fair", "it": "Fair", "es": "Fair", "pl": "Fair"}, + "Clear sky": { "en": "Clear sky", "de": "Klarer Himmel", "ru": "Чистое небо", "pt": "Clear sky", "nl": "Clear sky", "fr": "Clear sky", "it": "Clear sky", "es": "Clear sky", "pl": "Clear sky"}, + "Rain": { "en": "Rain", "de": "Regen", "ru": "Дождь", "pt": "Rain", "nl": "Rain", "fr": "Rain", "it": "Rain", "es": "Rain", "pl": "Rain"}, + "Light rain": { "en": "Light rain", "de": "Leichter Regen", "ru": "Слабый дождик", "pt": "Light rain", "nl": "Light rain", "fr": "Light rain", "it": "Light rain", "es": "Light rain", "pl": "Light rain"}, + "Heavy rain": { "en": "Heavy rain", "de": "Heftiger Regen", "ru": "Сильный дождь", "pt": "Heavy rain", "nl": "Heavy rain", "fr": "Heavy rain", "it": "Heavy rain", "es": "Heavy rain", "pl": "Heavy rain"}, + "Rain showers": { "en": "Rain showers", "de": "Regenschauer", "ru": "Ливневый дождь", "pt": "Rain showers", "nl": "Rain showers", "fr": "Rain showers", "it": "Rain showers", "es": "Rain showers", "pl": "Rain showers"}, + "Light rain showers": { "en": "Light rain showers", "de": "Leichter Regenschauer", "ru": "Слабый ливневый дождь", "pt": "Light rain showers", "nl": "Light rain showers", "fr": "Light rain showers", "it": "Light rain showers", "es": "Light rain showers", "pl": "Light rain showers"}, + "Fog": { "en": "Fog", "de": "Nebel", "ru": "Туман", "pt": "Fog", "nl": "Fog", "fr": "Fog", "it": "Fog", "es": "Fog", "pl": "Fog"}, + "Heavy rain showers": { "en": "Heavy rain showers", "de": "Starke Regenschauer", "ru": "Сильный ливневый дождь", "pt": "Heavy rain showers", "nl": "Heavy rain showers", "fr": "Heavy rain showers", "it": "Heavy rain showers", "es": "Heavy rain showers", "pl": "Heavy rain showers"}, + "Light sleet": { "en": "Light sleet", "de": "Schneeregen", "ru": "Мокрый снег", "pt": "Light sleet", "nl": "Light sleet", "fr": "Light sleet", "it": "Light sleet", "es": "Light sleet", "pl": "Light sleet"}, + "Light snow": { "en": "Light snow", "de": "Leichter Schnee", "ru": "Слабый снег", "pt": "Light snow", "nl": "Light snow", "fr": "Light snow", "it": "Light snow", "es": "Light snow", "pl": "Light snow"}, + "Light sleet showers": { "en": "Light sleet showers", "de": "Leichter Graupelschauer", "ru": "Слабый дождь со снегом", "pt": "Light sleet showers", "nl": "Light sleet showers", "fr": "Light sleet showers", "it": "Light sleet showers", "es": "Light sleet showers", "pl": "Light sleet showers"}, + "Heavy rain and thunder": { "en": "Heavy rain and thunder", "de": "Heftiger Regen und Blitze", "ru": "Сильный дождь и молнии", "pt": "Heavy rain and thunder", "nl": "Heavy rain and thunder", "fr": "Heavy rain and thunder", "it": "Heavy rain and thunder", "es": "Heavy rain and thunder", "pl": "Heavy rain and thunder"}, + "Heavy rain showers and thunder": { "en": "Heavy rain showers and thunder", "de": "Heftiger Regenschauer und Blitze", "ru": "Сильный ливневый дождь и молнии", "pt": "Heavy rain showers and thunder", "nl": "Heavy rain showers and thunder", "fr": "Heavy rain showers and thunder", "it": "Heavy rain showers and thunder", "es": "Heavy rain showers and thunder", "pl": "Heavy rain showers and thunder"}, + "Heavy sleet": { "en": "Heavy sleet", "de": "Heftiger Schneeregen", "ru": "Сильный снегопад с дождем", "pt": "Heavy sleet", "nl": "Heavy sleet", "fr": "Heavy sleet", "it": "Heavy sleet", "es": "Heavy sleet", "pl": "Heavy sleet"}, + "Heavy sleet showers": { "en": "Heavy sleet showers", "de": "Heftiger Schneeregen", "ru": "Сильный снегопад с дождем", "pt": "Heavy sleet showers", "nl": "Heavy sleet showers", "fr": "Heavy sleet showers", "it": "Heavy sleet showers", "es": "Heavy sleet showers", "pl": "Heavy sleet showers"}, + "Heavy snow showers": { "en": "Heavy snow showers", "de": "Heftiger Schnee", "ru": "Сильный снегопад", "pt": "Heavy snow showers", "nl": "Heavy snow showers", "fr": "Heavy snow showers", "it": "Heavy snow showers", "es": "Heavy snow showers", "pl": "Heavy snow showers"}, + "Light rain and thunder": { "en": "Light rain and thunder", "de": "Leichter Regen und Blitze", "ru": "Слабый дождик и молнии", "pt": "Light rain and thunder", "nl": "Light rain and thunder", "fr": "Light rain and thunder", "it": "Light rain and thunder", "es": "Light rain and thunder", "pl": "Light rain and thunder"}, + "Light rain showers and thunder": { "en": "Light rain showers and thunder", "de": "Leichter Regenschauer und Blitze", "ru": "Слабый ливневый дождь и молнии", "pt": "Light rain showers and thunder", "nl": "Light rain showers and thunder", "fr": "Light rain showers and thunder", "it": "Light rain showers and thunder", "es": "Light rain showers and thunder", "pl": "Light rain showers and thunder"}, + "Rain and thunder": { "en": "Rain and thunder", "de": "Regen und Blitze", "ru": "Дождь с молниями", "pt": "Rain and thunder", "nl": "Rain and thunder", "fr": "Rain and thunder", "it": "Rain and thunder", "es": "Rain and thunder", "pl": "Rain and thunder"}, + "Rain showers and thunder": { "en": "Rain showers and thunder", "de": "Regenschauer und Blitze", "ru": "Ливневый дождь и молнии", "pt": "Rain showers and thunder", "nl": "Rain showers and thunder", "fr": "Rain showers and thunder", "it": "Rain showers and thunder", "es": "Rain showers and thunder", "pl": "Rain showers and thunder"}, + "Sleet showers": { "en": "Sleet showers", "de": "Schneeregen", "ru": "Дождь со снегом", "pt": "Sleet showers", "nl": "Sleet showers", "fr": "Sleet showers", "it": "Sleet showers", "es": "Sleet showers", "pl": "Sleet showers"}, + "N": { "en": "N", "de": "N", "ru": "C", "pt": "N", "nl": "N", "fr": "N", "it": "N", "es": "N", "pl": "N"}, + "S": { "en": "S", "de": "S", "ru": "Ю", "pt": "S", "nl": "S", "fr": "S", "it": "S", "es": "S", "pl": "S"}, + "W": { "en": "W", "de": "W", "ru": "З", "pt": "W", "nl": "W", "fr": "W", "it": "W", "es": "W", "pl": "W"}, + "E": { "en": "E", "de": "O", "ru": "В", "pt": "E", "nl": "E", "fr": "E", "it": "E", "es": "E", "pl": "E"}, + "NW": { "en": "NW", "de": "NW", "ru": "CЗ", "pt": "NW", "nl": "NW", "fr": "NW", "it": "NW", "es": "NW", "pl": "NW"}, + "NNW": { "en": "NNW", "de": "NNW", "ru": "CСЗ", "pt": "NNW", "nl": "NNW", "fr": "NNW", "it": "NNW", "es": "NNW", "pl": "NNW"}, + "WNW": { "en": "WNW", "de": "WNW", "ru": "ЗСЗ", "pt": "WNW", "nl": "WNW", "fr": "WNW", "it": "WNW", "es": "WNW", "pl": "WNW"}, + "NE": { "en": "NE", "de": "NO", "ru": "CВ", "pt": "NE", "nl": "NE", "fr": "NE", "it": "NE", "es": "NE", "pl": "NE"}, + "NNE": { "en": "NNE", "de": "NNO", "ru": "CСВ", "pt": "NNE", "nl": "NNE", "fr": "NNE", "it": "NNE", "es": "NNE", "pl": "NNE"}, + "ENE": { "en": "ENE", "de": "ONO", "ru": "ВCВ", "pt": "ENE", "nl": "ENE", "fr": "ENE", "it": "ENE", "es": "ENE", "pl": "ENE"}, + "SW": { "en": "SW", "de": "SW", "ru": "ЮЗ", "pt": "SW", "nl": "SW", "fr": "SW", "it": "SW", "es": "SW", "pl": "SW"}, + "SSW": { "en": "SSW", "de": "SSW", "ru": "ЮЮЗ", "pt": "SSW", "nl": "SSW", "fr": "SSW", "it": "SSW", "es": "SSW", "pl": "SSW"}, + "WSW": { "en": "WSW", "de": "WSW", "ru": "ЗЮЗ", "pt": "WSW", "nl": "WSW", "fr": "WSW", "it": "WSW", "es": "WSW", "pl": "WSW"}, + "SE": { "en": "SE", "de": "SO", "ru": "ЮВ", "pt": "SE", "nl": "SE", "fr": "SE", "it": "SE", "es": "SE", "pl": "SE"}, + "SSE": { "en": "SSE", "de": "SSO", "ru": "ЮЮВ", "pt": "SSE", "nl": "SSE", "fr": "SSE", "it": "SSE", "es": "SSE", "pl": "SSE"}, + "ESE": { "en": "ESE", "de": "OSO", "ru": "ВЮВ", "pt": "ESE", "nl": "ESE", "fr": "ESE", "it": "ESE", "es": "ESE", "pl": "ESE"}, + "North": { "en": "North", "de": "Norden", "ru": "Север", "pt": "Norte", "nl": "noorden", "fr": "Nord", "it": "Nord", "es": "Norte", "pl": "Północ"}, + "North-northeast": { "en": "North-northeast", "de": "Nord-Nordost", "ru": "Северо-северо-восток", "pt": "Norte-nordeste", "nl": "Noord-noordoosten", "fr": "Nord-nord-est", "it": "Nord-nordest", "es": "Norte-noreste", "pl": "Północno-północny wschód"}, + "Northeast": { "en": "Northeast", "de": "Nordost", "ru": "Северо-восток", "pt": "Nordeste", "nl": "Noordoosten", "fr": "Nord-Est", "it": "nord-est", "es": "Noreste", "pl": "Północny wschód"}, + "East-Northeast": { "en": "East-Northeast", "de": "Ost-Nordost", "ru": "Ост-норд-ост", "pt": "Leste-Nordeste", "nl": "Oost-Noordoost", "fr": "Est-Nord-Est", "it": "Est-Nord-Est", "es": "Este-Noreste", "pl": "Wschód-północny wschód"}, + "East": { "en": "East", "de": "Osten", "ru": "Восток", "pt": "Leste", "nl": "Oosten", "fr": "est", "it": "est", "es": "Este", "pl": "Wschód"}, + "East-Southeast": { "en": "East-Southeast", "de": "Ost-Südost", "ru": "Восток-Юго", "pt": "Leste-Sudeste", "nl": "Oost-Zuidoost", "fr": "Est-Sud-Est", "it": "Est-Sud-Est", "es": "Este-Sureste", "pl": "Wschód-południowy-wschód"}, + "Southeast": { "en": "Southeast", "de": "Süd-Ost", "ru": "Юго-восток", "pt": "Sudeste", "nl": "zuidoosten", "fr": "Sud-Est", "it": "sud-est", "es": "Sureste", "pl": "Południowy wschód"}, + "South-southeast": { "en": "South-southeast", "de": "Süd-Südost", "ru": "Юго-юго-восток", "pt": "Sul-sudeste", "nl": "Zuid-zuidoost", "fr": "Sud-sud-est", "it": "Sud-sud-est", "es": "Sur-sureste", "pl": "Południowo-południowy wschód"}, + "South": { "en": "South", "de": "Süd", "ru": "Юг", "pt": "Sul", "nl": "zuiden", "fr": "Sud", "it": "Sud", "es": "Sur", "pl": "południe"}, + "South-southwest": { "en": "South-southwest", "de": "Süd-Südwesten", "ru": "Юго-юго-запад", "pt": "Sul-sudoeste", "nl": "Zuid-zuidwest", "fr": "Sud-sud-ouest", "it": "Sud-sud-ovest", "es": "Sur-suroeste", "pl": "Południowo-południowy zachód"}, + "Southwest": { "en": "Southwest", "de": "Südwesten", "ru": "Юго-запад", "pt": "Sudoeste", "nl": "Southwest", "fr": "Sud-ouest", "it": "sud-ovest", "es": "Sur oeste", "pl": "Południowy zachód"}, + "West-southwest": { "en": "West-southwest", "de": "West-Südwesten", "ru": "Западно-юго-запад", "pt": "Oeste-sudoeste", "nl": "West-zuidwesten", "fr": "Ouest-sud-ouest", "it": "Ovest-sud-ovest", "es": "Oeste-suroeste", "pl": "Zachód-południowy zachód"}, + "West": { "en": "West", "de": "Westen", "ru": "Запад", "pt": "Oeste", "nl": "westen", "fr": "Ouest", "it": "ovest", "es": "Oeste", "pl": "Zachód"}, + "West-northwest": { "en": "West-northwest", "de": "West-Nordwesten", "ru": "Вест-норд-вест", "pt": "Oeste-noroeste", "nl": "West-noordwesten", "fr": "Ouest-nord-ouest", "it": "Ovest-Nord-Ovest", "es": "Oeste-noroeste", "pl": "Zachód-północny-zachód"}, + "Northwest": { "en": "Northwest", "de": "Nordwest", "ru": "Северо-Запад", "pt": "Noroeste", "nl": "Noord West", "fr": "Nord Ouest", "it": "Nord Ovest", "es": "Noroeste", "pl": "Północny zachód"}, + "North-northwest": { "en": "North-northwest", "de": "Nord Nord West", "ru": "Северо-северо-запад", "pt": "Norte-noroeste", "nl": "Noordnoordwest", "fr": "Nord-nord-ouest", "it": "Nord-Nord-Ovest", "es": "Norte-noroeste", "pl": "Północno-północny zachód"} +}; \ No newline at end of file diff --git a/main.js b/main.js new file mode 100644 index 0000000..a440ab9 --- /dev/null +++ b/main.js @@ -0,0 +1,348 @@ +/* jshint -W097 */// jshint strict:false +/*jslint node: true */ +'use strict'; + +const xml2js = require('xml2js'); +const http = require('http'); +const utils = require(__dirname + '/lib/utils'); // Get common adapter utils +const dictionary = require(__dirname + '/lib/words'); + +const adapter = utils.Adapter({ + name: 'yr', // adapter name + useFormatDate: true // read date format from config +}); + +adapter.on('ready', main); + +function main() { + const tmp = adapter.config.location.split('/'); + const city = decodeURI(tmp.pop()); + + adapter.config.language = adapter.config.language || 'en'; + if (adapter.config.sendTranslations === undefined) adapter.config.sendTranslations = true; + if (adapter.config.sendTranslations === 'true') adapter.config.sendTranslations = true; + if (adapter.config.sendTranslations === 'false') adapter.config.sendTranslations = false; + + adapter.getObject('forecast.day0.temperatureActual', (err, obj) => { + if (obj && obj.common && obj.common.unit) { + if (obj.common.unit === '°C' && adapter.config.nonMetric) { + obj.common.unit = '°F'; + adapter.setObject(obj._id, obj, () => { + adapter.log.info(`Metrics changed for ${obj._id} to ${obj.common.unit}`); + }); + } else if (obj.common.unit !== '°C' && !adapter.config.nonMetric) { + obj.common.unit = '°C'; + adapter.setObject(obj._id, obj, () => { + adapter.log.info(`Metrics changed for ${obj._id} to ${obj.common.unit}`); + }); + } + } + }); + + for (let d = 0; d < 3; d++) { + adapter.getObject('forecast.day' + d + '.windSpeed', (err, obj) => { + if (obj && obj.common && obj.common.unit) { + if (obj.common.unit === 'km/h' && adapter.config.nonMetric) { + obj.common.unit = 'm/h'; + adapter.setObject(obj._id, obj, () => { + adapter.log.info(`Metrics changed for ${obj._id} to ${obj.common.unit}`); + }); + } else if (obj.common.unit !== 'km/h' && !adapter.config.nonMetric) { + obj.common.unit = 'km/h'; + adapter.setObject(obj._id, obj, () => { + adapter.log.info(`Metrics changed for ${obj._id} to ${obj.common.unit}`); + }); + } + } + }); + adapter.getObject('forecast.day' + d + '.temperatureMin', (err, obj) => { + if (obj && obj.common && obj.common.unit) { + if (obj.common.unit === '°C' && adapter.config.nonMetric) { + obj.common.unit = '°F'; + adapter.setObject(obj._id, obj, () => { + adapter.log.info(`Metrics changed for ${obj._id} to ${obj.common.unit}`); + }); + } else if (obj.common.unit !== '°C' && !adapter.config.nonMetric) { + obj.common.unit = '°C'; + adapter.setObject(obj._id, obj, () => { + adapter.log.info(`Metrics changed for ${obj._id} to ${obj.common.unit}`); + }); + } + } + }); + adapter.getObject('forecast.day' + d + '.temperatureMax', (err, obj) => { + if (obj && obj.common && obj.common.unit) { + if (obj.common.unit === '°C' && adapter.config.nonMetric) { + obj.common.unit = '°F'; + adapter.setObject(obj._id, obj, () => { + adapter.log.info(`Metrics changed for ${obj._id} to ${obj.common.unit}`); + }); + } else if (obj.common.unit !== '°C' && !adapter.config.nonMetric) { + obj.common.unit = '°C'; + adapter.setObject(obj._id, obj, () => { + adapter.log.info(`Metrics changed for ${obj._id} to ${obj.common.unit}`); + }); + } + } + }); + adapter.getObject('forecast.day' + d + '.precipitation', (err, obj) => { + if (obj && obj.common && obj.common.unit) { + if (obj.common.unit === 'mm' && adapter.config.nonMetric) { + obj.common.unit = 'in'; + adapter.setObject(obj._id, obj, () => { + adapter.log.info(`Metrics changed for ${obj._id} to ${obj.common.unit}`); + }); + } else if (obj.common.unit !== 'mm' && !adapter.config.nonMetric) { + obj.common.unit = 'mm'; + adapter.setObject(obj._id, obj, () => { + adapter.log.info(`Metrics changed for ${obj._id} to ${obj.common.unit}`); + }); + } + } + }); + } + + adapter.getObject('forecast', (err, obj) => { + if (!obj || !obj.common || obj.common.name !== 'yr.no forecast ' + city) { + adapter.setObject('forecast', { + type: 'device', + role: 'forecast', + common: { + name: 'yr.no forecast ' + city + }, + native: { + url: adapter.config.location, + country: decodeURI(tmp[0]), + state: decodeURI(tmp[1]), + city: city + } + }); + } + }); + + if (adapter.config.location.indexOf('forecast.xml') === -1) { + if (adapter.config.location.indexOf('%') === -1) adapter.config.location = encodeURI(adapter.config.location); + + adapter.setState('forecast.info.diagram', 'http://www.yr.no/place/' + adapter.config.location + '/avansert_meteogram.png', true); + + const reqOptions = { + hostname: 'www.yr.no', + port: 80, + path: '/place/' + adapter.config.location + '/forecast.xml', + method: 'GET' + }; + + adapter.log.debug('get http://' + reqOptions.hostname + reqOptions.path); + + const req = http.request(reqOptions, res => { + let data = ''; + + res.on('data', chunk => data += chunk); + + res.on('end', () => { + adapter.log.debug('received data from yr.no'); + parseData(data.toString()); + }); + }); + + req.on('error', e => { + adapter.log.error(e.message); + parseData(null); + }); + + req.end(); + } else { + parseData(require('fs').readFileSync(adapter.config.location).toString()); + } + + // Force terminate after 5min + setTimeout(() => { + adapter.log.error('force terminate'); + process.exit(1); + }, 300000); +} + +function _(text) { + if (!text) return ''; + + if (dictionary[text]) { + let newText = dictionary[text][adapter.config.language]; + if (newText) { + return newText; + } else if (adapter.config.language !== 'en') { + newText = dictionary[text].en; + if (newText) { + return newText; + } + } + } else { + if (adapter.config.sendTranslations) { + const options = { + hostname: 'download.yunkong2.net', + port: 80, + path: '/yr.php?word=' + encodeURIComponent(text) + }; + const req = http.request(options, res => { + console.log('STATUS: ' + res.statusCode); + adapter.log.info('Missing translation sent to yunkong2.net: "' + text + '"'); + }); + req.on('error', e => { + adapter.log.error('Cannot send to server missing translation for "' + text + '": ' + e.message); + }); + req.end(); + } else { + adapter.log.warn('Translate: "' + text + '": {"en": "' + text + '", "de": "' + text + '", "ru": "' + text + '"}, please send to developer'); + } + } + return text; +} + +function celsius2fahrenheit(degree, isConvert) { + if (isConvert) { + return degree * 9 / 5 + 32; + } else { + return degree; + } +} + +function parseData(xml) { + if (!xml) { + setTimeout(() => process.exit(0), 5000); + return; + } + const options = { + explicitArray: false, + mergeAttrs: true + }; + const parser = new xml2js.Parser(options); + parser.parseString(xml, (err, result) => { + if (err) { + adapter.log.error(err); + } else { + adapter.log.info('got weather data from yr.no'); + const forecastArr = result.weatherdata.forecast.tabular.time; + + let tableDay = ''; + let tableHead = ''; + let tableMiddle = ''; + let tableBottom = ''; + const dateObj = new Date(); + const dayEnd = dateObj.getFullYear() + '-' + ('0' + (dateObj.getMonth() + 1)).slice(-2) + '-' + ('0' + dateObj.getDate()).slice(-2) + 'T24:00:00'; + let daySwitch = false; + + let day = -1; // Start from today + const days = []; + for (let i = 0; i < 12 && i < forecastArr.length; i++) { + const period = forecastArr[i]; + + if (!period.period || period.period === '0') day++; + + // We want to process only today, tomorrow and the day after tomorrow + if (day === 3) break; + period.symbol.url = '/adapter/yr/icons/' + period.symbol.var + '.svg'; + period.symbol.name = _(period.symbol.name); + period.windDirection.code = _(period.windDirection.code); + period.windDirection.name = _(period.windDirection.name); + + if (i < 8) { + switch (i) { + case 0: + tableHead += ''; + break; + default: + if (period.from > dayEnd) { + if (!daySwitch) { + daySwitch = true; + tableDay += ''; + if (i < 3) tableDay += ''; + tableHead += ''; + } else { + tableHead += ''; + } + + } else { + tableHead += ''; + } + } + + tableMiddle += ''; + } + + if (day === -1 && !i) day = 0; + if (!days[day]) { + days[day] = { + date: new Date(period.from), + icon: period.symbol.url, + state: period.symbol.name, + temperatureMin: celsius2fahrenheit(parseFloat(period.temperature.value), adapter.config.nonMetric), + temperatureMax: celsius2fahrenheit(parseFloat(period.temperature.value), adapter.config.nonMetric), + precipitation: adapter.config.nonMetric ? parseFloat(period.precipitation.value) / 25.4 : parseFloat(period.precipitation.value), + windDirection: period.windDirection.code, + windSpeed: adapter.config.nonMetric ? parseFloat(period.windSpeed.mps) : parseFloat(period.windSpeed.mps) * 3.6, + pressure: parseFloat(period.pressure.value), + count: 1 + }; + } else { + // Summarize + let t; + // Take icon for day always from 12:00 to 18:00 if possible + if (i === 2) { + days[day].icon = period.symbol.url; + days[day].state = period.symbol.name; + days[day].windDirection = period.windDirection.code; + } + t = celsius2fahrenheit(parseFloat(period.temperature.value), adapter.config.nonMetric); + if (t < days[day].temperatureMin) { + days[day].temperatureMin = t; + } else + if (t > days[day].temperatureMax) { + days[day].temperatureMax = t; + } + + days[day].precipitation += adapter.config.nonMetric ? parseFloat(period.precipitation.value) / 25.4 : parseFloat(period.precipitation.value); + days[day].windSpeed += adapter.config.nonMetric ? parseFloat(period.windSpeed.mps) : parseFloat(period.windSpeed.mps) * 3.6; + days[day].pressure += parseFloat(period.pressure.value); + days[day].count++; + } + // Set actual temperature + if (!day && !i) { + days[day].temperatureActual = celsius2fahrenheit(parseInt(period.temperature.value, 10), adapter.config.nonMetric); + } + } + const style = ''; + const table = style + tableDay + tableHead + tableMiddle + tableBottom + '
' + _('Now') + '' + _('Today') + '' + _('Tomorrow') + '' + _('After tomorrow') + '' + parseInt(period.from.substring(11, 13), 10).toString() + '-' + parseInt(period.to.substring(11, 13), 10).toString() + '' + parseInt(period.from.substring(11, 13), 10).toString() + '-' + parseInt(period.to.substring(11, 13), 10).toString() + '' + parseInt(period.from.substring(11, 13), 10).toString() + '-' + parseInt(period.to.substring(11, 13), 10).toString() + '' + period.symbol.name + '
'; + tableBottom += '
' + period.temperature.value + '°C
'; + //console.log(JSON.stringify(result, null, " ")); + + for (day = 0; day < days.length; day++) { + // Take the average + if (days[day].count > 1) { + days[day].precipitation /= days[day].count; + days[day].windSpeed /= days[day].count; + days[day].pressure /= days[day].count; + } + days[day].temperatureMin = Math.round(days[day].temperatureMin); + days[day].temperatureMax = Math.round(days[day].temperatureMax); + days[day].precipitation = Math.round(days[day].precipitation); + days[day].windSpeed = Math.round(days[day].windSpeed * 10) / 10; + days[day].pressure = Math.round(days[day].pressure); + + days[day].date = adapter.formatDate(days[day].date); + + delete days[day].count; + for (const name in days[day]) { + if (days[day].hasOwnProperty(name)) { + adapter.setState('forecast.day' + day + '.' + name, {val: days[day][name], ack: true}); + } + } + } + + adapter.log.debug('data successfully parsed. setting states'); + + adapter.setState('forecast.info.html', {val: table, ack: true}); + adapter.setState('forecast.info.object', {val: JSON.stringify(days), ack: true}, () => { + setTimeout(() => process.exit(0), 5000); + }); + } + }); +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..90dd0da --- /dev/null +++ b/package.json @@ -0,0 +1,43 @@ +{ + "name": "yunkong2.yr", + "description": "Fetches 48h weather forecast from yr.no", + "version": "2.0.2", + "contributors": [ + "bluefox ", + "hobbyquaker " + ], + "homepage": "https://github.com/yunkong2/yunkong2.yr", + "repository": { + "type": "git", + "url": "https://github.com/yunkong2/yunkong2.yr" + }, + "licenses": [ + { + "type": "MIT", + "url": "https://github.com/yunkong2/yunkong2.yr/blob/master/LICENSE" + } + ], + "keywords": [ + "yunkong2", + "weather", + "Home automation", + "yr.no" + ], + "dependencies": { + "xml2js": "^0.4.19" + }, + "devDependencies": { + "gulp": "^3.9.1", + "mocha": "^5.2.0", + "chai": "^4.1.2" + }, + "bugs": { + "url": "https://github.com/yunkong2/yunkong2.yr/issues" + }, + "main": "main.js", + "scripts": { + "test": "node node_modules/mocha/bin/mocha --exit" + }, + "author": "hobbyquaker ", + "license": "MIT" +} diff --git a/server/translations.sql b/server/translations.sql new file mode 100644 index 0000000..e0c681d --- /dev/null +++ b/server/translations.sql @@ -0,0 +1,4 @@ +CREATE TABLE IF NOT EXISTS `translations` ( + `sentence` varchar(100) COLLATE utf8_bin NOT NULL, + KEY `sentence` (`sentence`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; diff --git a/server/yr.php b/server/yr.php new file mode 100644 index 0000000..14e2b55 --- /dev/null +++ b/server/yr.php @@ -0,0 +1,39 @@ +connect_error) { + die("Connection failed: " . $conn->connect_error); +} + + +$sql = "SELECT COUNT(*) FROM translations WHERE sentence='".$word."'"; +$result = $conn->query($sql); +//var_dump($result); +if ($result->num_rows > 0) { + $row = $result->fetch_assoc(); + $total = $row["COUNT(*)"]; + //var_dump($total); + if ($total == 0) { + $sql = "INSERT INTO translations (sentence) VALUES ('".$word."')"; + $conn->query($sql); + } +} else { + //var_dump("p*".$total); + $sql = "INSERT INTO translations (sentence) VALUES ('".$word."')"; + $conn->query($sql); +} + +$conn->close(); + +?> diff --git a/tasks/jscs.js b/tasks/jscs.js new file mode 100644 index 0000000..588b6f2 --- /dev/null +++ b/tasks/jscs.js @@ -0,0 +1,17 @@ +var srcDir = __dirname + "/../"; + +module.exports = { + all: { + src: [ + srcDir + "*.js", + srcDir + "lib/*.js", + srcDir + "adapter/example/*.js", + srcDir + "tasks/**/*.js", + srcDir + "www/**/*.js", + '!' + srcDir + "www/lib/**/*.js", + '!' + srcDir + 'node_modules/**/*.js', + '!' + srcDir + 'adapter/*/node_modules/**/*.js' + ], + options: require('./jscsRules.js') + } +}; \ No newline at end of file diff --git a/tasks/jscsRules.js b/tasks/jscsRules.js new file mode 100644 index 0000000..ded301d --- /dev/null +++ b/tasks/jscsRules.js @@ -0,0 +1,36 @@ +module.exports = { + force: true, + "requireCurlyBraces": ["else", "for", "while", "do", "try", "catch"], /*"if",*/ + "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"], + "requireSpaceBeforeBlockStatements": true, + "requireParenthesesAroundIIFE": true, + "disallowSpacesInFunctionDeclaration": {"beforeOpeningRoundBrace": true}, + "disallowSpacesInNamedFunctionExpression": {"beforeOpeningRoundBrace": true}, + "requireSpacesInFunctionExpression": {"beforeOpeningCurlyBrace": true}, + "requireSpacesInAnonymousFunctionExpression": {"beforeOpeningRoundBrace": true, "beforeOpeningCurlyBrace": true}, + "requireSpacesInNamedFunctionExpression": {"beforeOpeningCurlyBrace": true}, + "requireSpacesInFunctionDeclaration": {"beforeOpeningCurlyBrace": true}, + "disallowMultipleVarDecl": true, + "requireBlocksOnNewline": true, + "disallowEmptyBlocks": true, + "disallowSpacesInsideObjectBrackets": true, + "disallowSpacesInsideArrayBrackets": true, + "disallowSpaceAfterObjectKeys": true, + "disallowSpacesInsideParentheses": true, + "requireCommaBeforeLineBreak": true, + //"requireAlignedObjectValues": "all", + "requireOperatorBeforeLineBreak": ["?", "+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="], +// "disallowLeftStickedOperators": ["?", "+", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="], +// "requireRightStickedOperators": ["!"], +// "requireSpaceAfterBinaryOperators": ["?", "+", "/", "*", ":", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="], + //"disallowSpaceAfterBinaryOperators": [","], + "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"], + "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"], + "requireSpaceBeforeBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!=="], + "requireSpaceAfterBinaryOperators": ["?", ">", ",", ">=", "<=", "<", "+", "-", "/", "*", "=", "==", "===", "!=", "!=="], + //"validateIndentation": 4, + //"validateQuoteMarks": { "mark": "\"", "escape": true }, + "disallowMixedSpacesAndTabs": true, + "disallowKeywordsOnNewLine": ["else", "catch"] + +}; diff --git a/tasks/jshint.js b/tasks/jshint.js new file mode 100644 index 0000000..f823ebc --- /dev/null +++ b/tasks/jshint.js @@ -0,0 +1,17 @@ +var srcDir = __dirname + "/../"; + +module.exports = { + options: { + force: true + }, + all: [ + srcDir + "*.js", + srcDir + "lib/*.js", + srcDir + "adapter/example/*.js", + srcDir + "tasks/**/*.js", + srcDir + "www/**/*.js", + '!' + srcDir + "www/lib/**/*.js", + '!' + srcDir + 'node_modules/**/*.js', + '!' + srcDir + 'adapter/*/node_modules/**/*.js' + ] +}; \ No newline at end of file diff --git a/test/lib/example.xml b/test/lib/example.xml new file mode 100644 index 0000000..676e1c4 --- /dev/null +++ b/test/lib/example.xml @@ -0,0 +1,413 @@ + + + + Karlsruhe + Administration centre + Germany + + + + + + + + + + + + + + + + + 2018-07-28T09:38:00 + 2018-07-28T22:00:00 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/lib/forecast.xml b/test/lib/forecast.xml new file mode 100644 index 0000000..773e916 --- /dev/null +++ b/test/lib/forecast.xml @@ -0,0 +1,413 @@ + + + + Stuttgart + Regional capital + Germany + + + + + + + + + + + + + + + + + 2016-03-15T04:39:00 + 2016-03-15T17:00:00 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/lib/setup.js b/test/lib/setup.js new file mode 100644 index 0000000..09cc5f3 --- /dev/null +++ b/test/lib/setup.js @@ -0,0 +1,728 @@ +/* jshint -W097 */// jshint strict:false +/*jslint node: true */ +// check if tmp directory exists +var fs = require('fs'); +var path = require('path'); +var child_process = require('child_process'); +var rootDir = path.normalize(__dirname + '/../../'); +var pkg = require(rootDir + 'package.json'); +var debug = typeof v8debug === 'object'; +pkg.main = pkg.main || 'main.js'; + +var adapterName = path.normalize(rootDir).replace(/\\/g, '/').split('/'); +adapterName = adapterName[adapterName.length - 2]; +var adapterStarted = false; + +function getAppName() { + var parts = __dirname.replace(/\\/g, '/').split('/'); + return parts[parts.length - 3].split('.')[0]; +} + +var appName = getAppName().toLowerCase(); + +var objects; +var states; + +var pid = null; + +function copyFileSync(source, target) { + + var targetFile = target; + + //if target is a directory a new file with the same name will be created + if (fs.existsSync(target)) { + if ( fs.lstatSync( target ).isDirectory() ) { + targetFile = path.join(target, path.basename(source)); + } + } + + try { + fs.writeFileSync(targetFile, fs.readFileSync(source)); + } + catch (err) { + console.log("file copy error: " +source +" -> " + targetFile + " (error ignored)"); + } +} + +function copyFolderRecursiveSync(source, target, ignore) { + var files = []; + + var base = path.basename(source); + if (base === adapterName) { + base = pkg.name; + } + //check if folder needs to be created or integrated + var targetFolder = path.join(target, base); + if (!fs.existsSync(targetFolder)) { + fs.mkdirSync(targetFolder); + } + + //copy + if (fs.lstatSync(source).isDirectory()) { + files = fs.readdirSync(source); + files.forEach(function (file) { + if (ignore && ignore.indexOf(file) !== -1) { + return; + } + + var curSource = path.join(source, file); + var curTarget = path.join(targetFolder, file); + if (fs.lstatSync(curSource).isDirectory()) { + // ignore grunt files + if (file.indexOf('grunt') !== -1) return; + if (file === 'chai') return; + if (file === 'mocha') return; + copyFolderRecursiveSync(curSource, targetFolder, ignore); + } else { + copyFileSync(curSource, curTarget); + } + }); + } +} + +if (!fs.existsSync(rootDir + 'tmp')) { + fs.mkdirSync(rootDir + 'tmp'); +} + +function storeOriginalFiles() { + console.log('Store original files...'); + var dataDir = rootDir + 'tmp/' + appName + '-data/'; + + var f = fs.readFileSync(dataDir + 'objects.json'); + var objects = JSON.parse(f.toString()); + if (objects['system.adapter.admin.0'] && objects['system.adapter.admin.0'].common) { + objects['system.adapter.admin.0'].common.enabled = false; + } + if (objects['system.adapter.admin.1'] && objects['system.adapter.admin.1'].common) { + objects['system.adapter.admin.1'].common.enabled = false; + } + + fs.writeFileSync(dataDir + 'objects.json.original', JSON.stringify(objects)); + try { + f = fs.readFileSync(dataDir + 'states.json'); + fs.writeFileSync(dataDir + 'states.json.original', f); + } + catch (err) { + console.log('no states.json found - ignore'); + } +} + +function restoreOriginalFiles() { + console.log('restoreOriginalFiles...'); + var dataDir = rootDir + 'tmp/' + appName + '-data/'; + + var f = fs.readFileSync(dataDir + 'objects.json.original'); + fs.writeFileSync(dataDir + 'objects.json', f); + try { + f = fs.readFileSync(dataDir + 'states.json.original'); + fs.writeFileSync(dataDir + 'states.json', f); + } + catch (err) { + console.log('no states.json.original found - ignore'); + } + +} + +function checkIsAdapterInstalled(cb, counter, customName) { + customName = customName || pkg.name.split('.').pop(); + counter = counter || 0; + var dataDir = rootDir + 'tmp/' + appName + '-data/'; + console.log('checkIsAdapterInstalled...'); + + try { + var f = fs.readFileSync(dataDir + 'objects.json'); + var objects = JSON.parse(f.toString()); + if (objects['system.adapter.' + customName + '.0']) { + console.log('checkIsAdapterInstalled: ready!'); + setTimeout(function () { + if (cb) cb(); + }, 100); + return; + } else { + console.warn('checkIsAdapterInstalled: still not ready'); + } + } catch (err) { + + } + + if (counter > 20) { + console.error('checkIsAdapterInstalled: Cannot install!'); + if (cb) cb('Cannot install'); + } else { + console.log('checkIsAdapterInstalled: wait...'); + setTimeout(function() { + checkIsAdapterInstalled(cb, counter + 1); + }, 1000); + } +} + +function checkIsControllerInstalled(cb, counter) { + counter = counter || 0; + var dataDir = rootDir + 'tmp/' + appName + '-data/'; + + console.log('checkIsControllerInstalled...'); + try { + var f = fs.readFileSync(dataDir + 'objects.json'); + var objects = JSON.parse(f.toString()); + if (objects['system.adapter.admin.0']) { + console.log('checkIsControllerInstalled: installed!'); + setTimeout(function () { + if (cb) cb(); + }, 100); + return; + } + } catch (err) { + + } + + if (counter > 20) { + console.log('checkIsControllerInstalled: Cannot install!'); + if (cb) cb('Cannot install'); + } else { + console.log('checkIsControllerInstalled: wait...'); + setTimeout(function() { + checkIsControllerInstalled(cb, counter + 1); + }, 1000); + } +} + +function installAdapter(customName, cb) { + if (typeof customName === 'function') { + cb = customName; + customName = null; + } + customName = customName || pkg.name.split('.').pop(); + console.log('Install adapter...'); + var startFile = 'node_modules/' + appName + '.js-controller/' + appName + '.js'; + // make first install + if (debug) { + child_process.execSync('node ' + startFile + ' add ' + customName + ' --enabled false', { + cwd: rootDir + 'tmp', + stdio: [0, 1, 2] + }); + checkIsAdapterInstalled(function (error) { + if (error) console.error(error); + console.log('Adapter installed.'); + if (cb) cb(); + }); + } else { + // add controller + var _pid = child_process.fork(startFile, ['add', customName, '--enabled', 'false'], { + cwd: rootDir + 'tmp', + stdio: [0, 1, 2, 'ipc'] + }); + + waitForEnd(_pid, function () { + checkIsAdapterInstalled(function (error) { + if (error) console.error(error); + console.log('Adapter installed.'); + if (cb) cb(); + }); + }); + } +} + +function waitForEnd(_pid, cb) { + if (!_pid) { + cb(-1, -1); + return; + } + _pid.on('exit', function (code, signal) { + if (_pid) { + _pid = null; + cb(code, signal); + } + }); + _pid.on('close', function (code, signal) { + if (_pid) { + _pid = null; + cb(code, signal); + } + }); +} + +function installJsController(cb) { + console.log('installJsController...'); + if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller') || + !fs.existsSync(rootDir + 'tmp/' + appName + '-data')) { + // try to detect appName.js-controller in node_modules/appName.js-controller + // travis CI installs js-controller into node_modules + if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller')) { + console.log('installJsController: no js-controller => copy it from "' + rootDir + 'node_modules/' + appName + '.js-controller"'); + // copy all + // stop controller + console.log('Stop controller if running...'); + var _pid; + if (debug) { + // start controller + _pid = child_process.exec('node ' + appName + '.js stop', { + cwd: rootDir + 'node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2] + }); + } else { + _pid = child_process.fork(appName + '.js', ['stop'], { + cwd: rootDir + 'node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2, 'ipc'] + }); + } + + waitForEnd(_pid, function () { + // copy all files into + if (!fs.existsSync(rootDir + 'tmp')) fs.mkdirSync(rootDir + 'tmp'); + if (!fs.existsSync(rootDir + 'tmp/node_modules')) fs.mkdirSync(rootDir + 'tmp/node_modules'); + + if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')){ + console.log('Copy js-controller...'); + copyFolderRecursiveSync(rootDir + 'node_modules/' + appName + '.js-controller', rootDir + 'tmp/node_modules/'); + } + + console.log('Setup js-controller...'); + var __pid; + if (debug) { + // start controller + _pid = child_process.exec('node ' + appName + '.js setup first --console', { + cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2] + }); + } else { + __pid = child_process.fork(appName + '.js', ['setup', 'first', '--console'], { + cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2, 'ipc'] + }); + } + waitForEnd(__pid, function () { + checkIsControllerInstalled(function () { + // change ports for object and state DBs + var config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json'); + config.objects.port = 19001; + config.states.port = 19000; + fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2)); + console.log('Setup finished.'); + + copyAdapterToController(); + + installAdapter(function () { + storeOriginalFiles(); + if (cb) cb(true); + }); + }); + }); + }); + } else { + // check if port 9000 is free, else admin adapter will be added to running instance + var client = new require('net').Socket(); + client.connect(9000, '127.0.0.1', function() { + console.error('Cannot initiate fisrt run of test, because one instance of application is running on this PC. Stop it and repeat.'); + process.exit(0); + }); + + setTimeout(function () { + client.destroy(); + if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')) { + console.log('installJsController: no js-controller => install from git'); + + child_process.execSync('npm install https://github.com/' + appName + '/' + appName + '.js-controller/tarball/master --prefix ./ --production', { + cwd: rootDir + 'tmp/', + stdio: [0, 1, 2] + }); + } else { + console.log('Setup js-controller...'); + var __pid; + if (debug) { + // start controller + child_process.exec('node ' + appName + '.js setup first', { + cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2] + }); + } else { + child_process.fork(appName + '.js', ['setup', 'first'], { + cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2, 'ipc'] + }); + } + } + + // let npm install admin and run setup + checkIsControllerInstalled(function () { + var _pid; + + if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller/' + appName + '.js')) { + _pid = child_process.fork(appName + '.js', ['stop'], { + cwd: rootDir + 'node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2, 'ipc'] + }); + } + + waitForEnd(_pid, function () { + // change ports for object and state DBs + var config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json'); + config.objects.port = 19001; + config.states.port = 19000; + fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2)); + + copyAdapterToController(); + + installAdapter(function () { + storeOriginalFiles(); + if (cb) cb(true); + }); + }); + }); + }, 1000); + } + } else { + setTimeout(function () { + console.log('installJsController: js-controller installed'); + if (cb) cb(false); + }, 0); + } +} + +function copyAdapterToController() { + console.log('Copy adapter...'); + // Copy adapter to tmp/node_modules/appName.adapter + copyFolderRecursiveSync(rootDir, rootDir + 'tmp/node_modules/', ['.idea', 'test', 'tmp', '.git', appName + '.js-controller']); + console.log('Adapter copied.'); +} + +function clearControllerLog() { + var dirPath = rootDir + 'tmp/log'; + var files; + try { + if (fs.existsSync(dirPath)) { + console.log('Clear controller log...'); + files = fs.readdirSync(dirPath); + } else { + console.log('Create controller log directory...'); + files = []; + fs.mkdirSync(dirPath); + } + } catch(e) { + console.error('Cannot read "' + dirPath + '"'); + return; + } + if (files.length > 0) { + try { + for (var i = 0; i < files.length; i++) { + var filePath = dirPath + '/' + files[i]; + fs.unlinkSync(filePath); + } + console.log('Controller log cleared'); + } catch (err) { + console.error('cannot clear log: ' + err); + } + } +} + +function clearDB() { + var dirPath = rootDir + 'tmp/yunkong2-data/sqlite'; + var files; + try { + if (fs.existsSync(dirPath)) { + console.log('Clear sqlite DB...'); + files = fs.readdirSync(dirPath); + } else { + console.log('Create controller log directory...'); + files = []; + fs.mkdirSync(dirPath); + } + } catch(e) { + console.error('Cannot read "' + dirPath + '"'); + return; + } + if (files.length > 0) { + try { + for (var i = 0; i < files.length; i++) { + var filePath = dirPath + '/' + files[i]; + fs.unlinkSync(filePath); + } + console.log('Clear sqlite DB'); + } catch (err) { + console.error('cannot clear DB: ' + err); + } + } +} + +function setupController(cb) { + installJsController(function (isInited) { + clearControllerLog(); + clearDB(); + + if (!isInited) { + restoreOriginalFiles(); + copyAdapterToController(); + } + // read system.config object + var dataDir = rootDir + 'tmp/' + appName + '-data/'; + + var objs; + try { + objs = fs.readFileSync(dataDir + 'objects.json'); + objs = JSON.parse(objs); + } + catch (e) { + console.log('ERROR reading/parsing system configuration. Ignore'); + objs = {'system.config': {}}; + } + if (!objs || !objs['system.config']) { + objs = {'system.config': {}}; + } + + if (cb) cb(objs['system.config']); + }); +} + +function startAdapter(objects, states, callback) { + if (adapterStarted) { + console.log('Adapter already started ...'); + if (callback) callback(objects, states); + return; + } + adapterStarted = true; + console.log('startAdapter...'); + if (fs.existsSync(rootDir + 'tmp/node_modules/' + pkg.name + '/' + pkg.main)) { + try { + if (debug) { + // start controller + pid = child_process.exec('node node_modules/' + pkg.name + '/' + pkg.main + ' --console silly', { + cwd: rootDir + 'tmp', + stdio: [0, 1, 2] + }); + } else { + // start controller + pid = child_process.fork('node_modules/' + pkg.name + '/' + pkg.main, ['--console', 'silly'], { + cwd: rootDir + 'tmp', + stdio: [0, 1, 2, 'ipc'] + }); + } + } catch (error) { + console.error(JSON.stringify(error)); + } + } else { + console.error('Cannot find: ' + rootDir + 'tmp/node_modules/' + pkg.name + '/' + pkg.main); + } + if (callback) callback(objects, states); +} + +function startController(isStartAdapter, onObjectChange, onStateChange, callback) { + if (typeof isStartAdapter === 'function') { + callback = onStateChange; + onStateChange = onObjectChange; + onObjectChange = isStartAdapter; + isStartAdapter = true; + } + + if (onStateChange === undefined) { + callback = onObjectChange; + onObjectChange = undefined; + } + + if (pid) { + console.error('Controller is already started!'); + } else { + console.log('startController...'); + adapterStarted = false; + var isObjectConnected; + var isStatesConnected; + + var Objects = require(rootDir + 'tmp/node_modules/' + appName + '.js-controller/lib/objects/objectsInMemServer'); + objects = new Objects({ + connection: { + "type" : "file", + "host" : "127.0.0.1", + "port" : 19001, + "user" : "", + "pass" : "", + "noFileCache": false, + "connectTimeout": 2000 + }, + logger: { + silly: function (msg) { + console.log(msg); + }, + debug: function (msg) { + console.log(msg); + }, + info: function (msg) { + console.log(msg); + }, + warn: function (msg) { + console.warn(msg); + }, + error: function (msg) { + console.error(msg); + } + }, + connected: function () { + isObjectConnected = true; + if (isStatesConnected) { + console.log('startController: started!'); + if (isStartAdapter) { + startAdapter(objects, states, callback); + } else { + if (callback) { + callback(objects, states); + callback = null; + } + } + } + }, + change: onObjectChange + }); + + // Just open in memory DB itself + var States = require(rootDir + 'tmp/node_modules/' + appName + '.js-controller/lib/states/statesInMemServer'); + states = new States({ + connection: { + type: 'file', + host: '127.0.0.1', + port: 19000, + options: { + auth_pass: null, + retry_max_delay: 15000 + } + }, + logger: { + silly: function (msg) { + console.log(msg); + }, + debug: function (msg) { + console.log(msg); + }, + info: function (msg) { + console.log(msg); + }, + warn: function (msg) { + console.log(msg); + }, + error: function (msg) { + console.log(msg); + } + }, + connected: function () { + isStatesConnected = true; + if (isObjectConnected) { + console.log('startController: started!!'); + if (isStartAdapter) { + startAdapter(objects, states, callback); + } else { + if (callback) { + callback(objects, states); + callback = null; + } + } + } + }, + change: onStateChange + }); + } +} + +function stopAdapter(cb) { + if (!pid) { + console.error('Controller is not running!'); + if (cb) { + setTimeout(function () { + cb(false); + }, 0); + } + } else { + adapterStarted = false; + pid.on('exit', function (code, signal) { + if (pid) { + console.log('child process terminated due to receipt of signal ' + signal); + if (cb) cb(); + pid = null; + } + }); + + pid.on('close', function (code, signal) { + if (pid) { + if (cb) cb(); + pid = null; + } + }); + + pid.kill('SIGTERM'); + } +} + +function _stopController() { + if (objects) { + objects.destroy(); + objects = null; + } + if (states) { + states.destroy(); + states = null; + } +} + +function stopController(cb) { + var timeout; + if (objects) { + console.log('Set system.adapter.' + pkg.name + '.0'); + objects.setObject('system.adapter.' + pkg.name + '.0', { + common:{ + enabled: false + } + }); + } + + stopAdapter(function () { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + + _stopController(); + + if (cb) { + cb(true); + cb = null; + } + }); + + timeout = setTimeout(function () { + timeout = null; + console.log('child process NOT terminated'); + + _stopController(); + + if (cb) { + cb(false); + cb = null; + } + pid = null; + }, 5000); +} + +// Setup the adapter +function setAdapterConfig(common, native, instance) { + var objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString()); + var id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0); + if (common) objects[id].common = common; + if (native) objects[id].native = native; + fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/objects.json', JSON.stringify(objects)); +} + +// Read config of the adapter +function getAdapterConfig(instance) { + var objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString()); + var id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0); + return objects[id]; +} + +if (typeof module !== undefined && module.parent) { + module.exports.getAdapterConfig = getAdapterConfig; + module.exports.setAdapterConfig = setAdapterConfig; + module.exports.startController = startController; + module.exports.stopController = stopController; + module.exports.setupController = setupController; + module.exports.stopAdapter = stopAdapter; + module.exports.startAdapter = startAdapter; + module.exports.installAdapter = installAdapter; + module.exports.appName = appName; + module.exports.adapterName = adapterName; + module.exports.adapterStarted = adapterStarted; +} diff --git a/test/lib/warnings.json b/test/lib/warnings.json new file mode 100644 index 0000000..eae8f1a --- /dev/null +++ b/test/lib/warnings.json @@ -0,0 +1,1297 @@ +warnWetter.loadWarnings({ + "time" : 1456998601000, + "warnings" : { + "108237000" : [{ + "stateShort" : "BW", + "regionName" : "Kreis Freudenstadt", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1457038800000, + "start" : 1456986060000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Baden-Württemberg" + } + ], + "107335000" : [{ + "stateShort" : "RP", + "regionName" : "Kreis und Stadt Kaiserslautern", + "description" : "Es tritt im Warnzeitraum oberhalb 400 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457010000000, + "start" : 1456993020000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 400, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + }, { + "stateShort" : "RP", + "regionName" : "Kreis und Stadt Kaiserslautern", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457002800000, + "start" : 1456986960000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + } + ], + "108235000" : [{ + "stateShort" : "BW", + "regionName" : "Kreis Calw", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1457038800000, + "start" : 1456986060000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Baden-Württemberg" + } + ], + "108135000" : [{ + "stateShort" : "BW", + "regionName" : "Kreis Heidenheim", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen bis 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457038800000, + "start" : 1456986060000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Baden-Württemberg" + } + ], + "107316000" : [{ + "stateShort" : "RP", + "regionName" : "Stadt Neustadt an der Weinstraße", + "description" : "Es tritt im Warnzeitraum oberhalb 400 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457010000000, + "start" : 1456993020000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 400, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + } + ], + "108436000" : [{ + "stateShort" : "BW", + "regionName" : "Kreis Ravensburg", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen bis 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457038800000, + "start" : 1456986060000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Baden-Württemberg" + }, { + "stateShort" : "BW", + "regionName" : "Kreis Ravensburg", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen um 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1457046000000, + "start" : 1457002800000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Baden-Württemberg" + } + ], + "109763000" : [{ + "stateShort" : "BY", + "regionName" : "Stadt Kempten (Allgäu)", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen um 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1457046000000, + "start" : 1457002800000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Bayern" + } + ], + "107231000" : [{ + "stateShort" : "RP", + "regionName" : "Kreis Bernkastel-Wittlich", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1456999200000, + "start" : 1456975800000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + } + ], + "105354000" : [{ + "stateShort" : "NRW", + "regionName" : "StädteRegion Aachen", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1456999200000, + "start" : 1456975800000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Nordrhein-Westfalen" + } + ], + "105382000" : [{ + "stateShort" : "NRW", + "regionName" : "Rhein-Sieg-Kreis", + "description" : "Es tritt im Warnzeitraum oberhalb 400 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457006400000, + "start" : 1456991760000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 400, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Nordrhein-Westfalen" + } + ], + "108336000" : [{ + "stateShort" : "BW", + "regionName" : "Kreis Lörrach", + "description" : "Es treten oberhalb 1000 m Sturmböen mit Geschwindigkeiten bis 70 km/h (20m/s, 38kn, Bft 8) anfangs aus südwestlicher, später aus westlicher Richtung auf. In exponierten Lagen muss mit Sturmböen bis 80 km/h (22m/s, 44kn, Bft 9) gerechnet werden.", + "end" : 1457017200000, + "start" : 1456995600000, + "headline" : "Amtliche WARNUNG vor STURMBÖEN", + "event" : "STURMBÖEN", + "instruction" : "ACHTUNG! Hinweis auf mögliche Gefahren: Es können zum Beispiel einzelne Äste herabstürzen. Achten Sie besonders auf herabfallende Gegenstände.", + "altitudeStart" : 1000, + "altitudeEnd" : null, + "type" : 1, + "level" : 3, + "state" : "Baden-Württemberg" + }, { + "stateShort" : "BW", + "regionName" : "Kreis Lörrach", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1457038800000, + "start" : 1456986060000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Baden-Württemberg" + } + ], + "110046000" : [{ + "stateShort" : "SL", + "regionName" : "Kreis St. Wendel", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1456999200000, + "start" : 1456975800000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Saarland" + } + ], + "108116000" : [{ + "stateShort" : "BW", + "regionName" : "Kreis Esslingen", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen bis 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457038800000, + "start" : 1456986060000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Baden-Württemberg" + } + ], + "107138000" : [{ + "stateShort" : "RP", + "regionName" : "Kreis Neuwied", + "description" : "Es tritt im Warnzeitraum oberhalb 400 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457006400000, + "start" : 1456991760000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 400, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + } + ], + "108327000" : [{ + "stateShort" : "BW", + "regionName" : "Kreis Tuttlingen", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen bis 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457038800000, + "start" : 1456986060000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Baden-Württemberg" + } + ], + "108316000" : [{ + "stateShort" : "BW", + "regionName" : "Kreis Emmendingen", + "description" : "Es treten oberhalb 1000 m Sturmböen mit Geschwindigkeiten bis 70 km/h (20m/s, 38kn, Bft 8) anfangs aus südwestlicher, später aus westlicher Richtung auf. In exponierten Lagen muss mit Sturmböen bis 80 km/h (22m/s, 44kn, Bft 9) gerechnet werden.", + "end" : 1457017200000, + "start" : 1456995600000, + "headline" : "Amtliche WARNUNG vor STURMBÖEN", + "event" : "STURMBÖEN", + "instruction" : "ACHTUNG! Hinweis auf mögliche Gefahren: Es können zum Beispiel einzelne Äste herabstürzen. Achten Sie besonders auf herabfallende Gegenstände.", + "altitudeStart" : 1000, + "altitudeEnd" : null, + "type" : 1, + "level" : 3, + "state" : "Baden-Württemberg" + }, { + "stateShort" : "BW", + "regionName" : "Kreis Emmendingen", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1457038800000, + "start" : 1456986060000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Baden-Württemberg" + } + ], + "107332000" : [{ + "stateShort" : "RP", + "regionName" : "Kreis Bad Dürkheim", + "description" : "Es tritt im Warnzeitraum oberhalb 400 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457010000000, + "start" : 1456993020000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 400, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + } + ], + "110045000" : [{ + "stateShort" : "SL", + "regionName" : "Saarpfalz-Kreis", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1456999200000, + "start" : 1456975800000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Saarland" + } + ], + "108211000" : [{ + "stateShort" : "BW", + "regionName" : "Stadt Baden-Baden", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1457038800000, + "start" : 1456986060000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Baden-Württemberg" + } + ], + "108417000" : [{ + "stateShort" : "BW", + "regionName" : "Zollernalbkreis", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen bis 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457038800000, + "start" : 1456986060000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Baden-Württemberg" + } + ], + "108415000" : [{ + "stateShort" : "BW", + "regionName" : "Kreis Reutlingen", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen bis 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457038800000, + "start" : 1456986060000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Baden-Württemberg" + } + ], + "107340000" : [{ + "stateShort" : "RP", + "regionName" : "Kreis Südwestpfalz und Stadt Pirmasens", + "description" : "Es tritt im Warnzeitraum oberhalb 400 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457010000000, + "start" : 1456993020000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 400, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + }, { + "stateShort" : "RP", + "regionName" : "Kreis Südwestpfalz und Stadt Pirmasens", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457002800000, + "start" : 1456986960000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + } + ], + "108425000" : [{ + "stateShort" : "BW", + "regionName" : "Alb-Donau-Kreis und Stadt Ulm", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen bis 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457038800000, + "start" : 1456986060000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Baden-Württemberg" + } + ], + "108337000" : [{ + "stateShort" : "BW", + "regionName" : "Kreis Waldshut", + "description" : "Es treten oberhalb 1000 m Sturmböen mit Geschwindigkeiten bis 70 km/h (20m/s, 38kn, Bft 8) anfangs aus südwestlicher, später aus westlicher Richtung auf. In exponierten Lagen muss mit Sturmböen bis 80 km/h (22m/s, 44kn, Bft 9) gerechnet werden.", + "end" : 1457017200000, + "start" : 1456995600000, + "headline" : "Amtliche WARNUNG vor STURMBÖEN", + "event" : "STURMBÖEN", + "instruction" : "ACHTUNG! Hinweis auf mögliche Gefahren: Es können zum Beispiel einzelne Äste herabstürzen. Achten Sie besonders auf herabfallende Gegenstände.", + "altitudeStart" : 1000, + "altitudeEnd" : null, + "type" : 1, + "level" : 3, + "state" : "Baden-Württemberg" + }, { + "stateShort" : "BW", + "regionName" : "Kreis Waldshut", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1457038800000, + "start" : 1456986060000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Baden-Württemberg" + } + ], + "110041000" : [{ + "stateShort" : "SL", + "regionName" : "Regionalverband Saarbrücken", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1456999200000, + "start" : 1456975800000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Saarland" + } + ], + "108216000" : [{ + "stateShort" : "BW", + "regionName" : "Kreis Rastatt", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1457038800000, + "start" : 1456986060000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Baden-Württemberg" + } + ], + "109187000" : [{ + "stateShort" : "BY", + "regionName" : "Kreis und Stadt Rosenheim", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457002800000, + "start" : 1456978200000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Bayern" + } + ], + "109189000" : [{ + "stateShort" : "BY", + "regionName" : "Kreis Traunstein", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457002800000, + "start" : 1456978200000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Bayern" + } + ], + "107337000" : [{ + "stateShort" : "RP", + "regionName" : "Kreis Südliche Weinstraße", + "description" : "Es tritt im Warnzeitraum oberhalb 400 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457010000000, + "start" : 1456993020000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 400, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + }, { + "stateShort" : "RP", + "regionName" : "Kreis Südliche Weinstraße", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457002800000, + "start" : 1456986960000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + } + ], + "108325000" : [{ + "stateShort" : "BW", + "regionName" : "Kreis Rottweil", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1457038800000, + "start" : 1456986060000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Baden-Württemberg" + } + ], + "108435000" : [{ + "stateShort" : "BW", + "regionName" : "Bodenseekreis", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen bis 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457038800000, + "start" : 1456986060000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Baden-Württemberg" + } + ], + "105358000" : [{ + "stateShort" : "NRW", + "regionName" : "Kreis Düren", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1456999200000, + "start" : 1456975800000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Nordrhein-Westfalen" + } + ], + "109777000" : [{ + "stateShort" : "BY", + "regionName" : "Kreis Ostallgäu", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen um 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1457046000000, + "start" : 1457002800000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Bayern" + } + ], + "107232000" : [{ + "stateShort" : "RP", + "regionName" : "Eifelkreis Bitburg-Prüm", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1456999200000, + "start" : 1456975800000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + } + ], + "107333000" : [{ + "stateShort" : "RP", + "regionName" : "Donnersbergkreis", + "description" : "Es tritt im Warnzeitraum oberhalb 400 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457010000000, + "start" : 1456993020000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 400, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + } + ], + "110044000" : [{ + "stateShort" : "SL", + "regionName" : "Kreis Saarlouis", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1456999200000, + "start" : 1456975800000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Saarland" + } + ], + "107235000" : [{ + "stateShort" : "RP", + "regionName" : "Kreis Trier-Saarburg und Stadt Trier", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1456999200000, + "start" : 1456975800000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + } + ], + "108236000" : [{ + "stateShort" : "BW", + "regionName" : "Enzkreis und Stadt Pforzheim", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1457038800000, + "start" : 1456986060000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Baden-Württemberg" + } + ], + "109776000" : [{ + "stateShort" : "BY", + "regionName" : "Kreis Lindau", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen um 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1457046000000, + "start" : 1457002800000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Bayern" + } + ], + "108136000" : [{ + "stateShort" : "BW", + "regionName" : "Ostalbkreis", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen bis 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457038800000, + "start" : 1456986060000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Baden-Württemberg" + } + ], + "107320000" : [{ + "stateShort" : "RP", + "regionName" : "Stadt Zweibrücken", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457002800000, + "start" : 1456986960000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + } + ], + "107133000" : [{ + "stateShort" : "RP", + "regionName" : "Kreis Bad Kreuznach", + "description" : "Es tritt im Warnzeitraum oberhalb 400 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457010000000, + "start" : 1456993020000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 400, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + } + ], + "108317000" : [{ + "stateShort" : "BW", + "regionName" : "Ortenaukreis", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1457038800000, + "start" : 1456986060000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Baden-Württemberg" + } + ], + "107140000" : [{ + "stateShort" : "RP", + "regionName" : "Rhein-Hunsrück-Kreis", + "description" : "Es tritt im Warnzeitraum oberhalb 400 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457006400000, + "start" : 1456991760000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 400, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + }, { + "stateShort" : "RP", + "regionName" : "Rhein-Hunsrück-Kreis", + "description" : "Es tritt im Warnzeitraum oberhalb 400 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457010000000, + "start" : 1456993020000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 400, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + }, { + "stateShort" : "RP", + "regionName" : "Rhein-Hunsrück-Kreis", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1456999200000, + "start" : 1456975800000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + } + ], + "109780000" : [{ + "stateShort" : "BY", + "regionName" : "Kreis Oberallgäu", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen um 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1457046000000, + "start" : 1457002800000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Bayern" + } + ], + "110042000" : [{ + "stateShort" : "SL", + "regionName" : "Kreis Merzig-Wadern", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1456999200000, + "start" : 1456975800000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Saarland" + } + ], + "107336000" : [{ + "stateShort" : "RP", + "regionName" : "Kreis Kusel", + "description" : "Es tritt im Warnzeitraum oberhalb 400 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457010000000, + "start" : 1456993020000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 400, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + }, { + "stateShort" : "RP", + "regionName" : "Kreis Kusel", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457002800000, + "start" : 1456986960000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + }, { + "stateShort" : "RP", + "regionName" : "Kreis Kusel", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1456999200000, + "start" : 1456975800000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + } + ], + "108437000" : [{ + "stateShort" : "BW", + "regionName" : "Kreis Sigmaringen", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen bis 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457038800000, + "start" : 1456986060000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Baden-Württemberg" + } + ], + "108416000" : [{ + "stateShort" : "BW", + "regionName" : "Kreis Tübingen", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen bis 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457038800000, + "start" : 1456986060000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Baden-Württemberg" + } + ], + "109180000" : [{ + "stateShort" : "BY", + "regionName" : "Kreis Garmisch-Partenkirchen", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen um 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1457046000000, + "start" : 1457002800000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Bayern" + } + ], + "107134000" : [{ + "stateShort" : "RP", + "regionName" : "Kreis Birkenfeld", + "description" : "Es tritt im Warnzeitraum oberhalb 400 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457006400000, + "start" : 1456991760000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 400, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + }, { + "stateShort" : "RP", + "regionName" : "Kreis Birkenfeld", + "description" : "Es tritt im Warnzeitraum oberhalb 400 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457010000000, + "start" : 1456993020000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 400, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + }, { + "stateShort" : "RP", + "regionName" : "Kreis Birkenfeld", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457002800000, + "start" : 1456986960000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + }, { + "stateShort" : "RP", + "regionName" : "Kreis Birkenfeld", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1456999200000, + "start" : 1456975800000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + } + ], + "107313000" : [{ + "stateShort" : "RP", + "regionName" : "Stadt Landau in der Pfalz", + "description" : "Es tritt im Warnzeitraum oberhalb 400 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457010000000, + "start" : 1456993020000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 400, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + } + ], + "107233000" : [{ + "stateShort" : "RP", + "regionName" : "Kreis Vulkaneifel", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1456999200000, + "start" : 1456975800000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + } + ], + "110043000" : [{ + "stateShort" : "SL", + "regionName" : "Kreis Neunkirchen", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1456999200000, + "start" : 1456975800000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Saarland" + } + ], + "108335000" : [{ + "stateShort" : "BW", + "regionName" : "Kreis Konstanz", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen bis 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457038800000, + "start" : 1456986060000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Baden-Württemberg" + } + ], + "108117000" : [{ + "stateShort" : "BW", + "regionName" : "Kreis Göppingen", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen bis 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457038800000, + "start" : 1456986060000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Baden-Württemberg" + } + ], + "108426000" : [{ + "stateShort" : "BW", + "regionName" : "Kreis Biberach", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen bis 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457038800000, + "start" : 1456986060000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Baden-Württemberg" + } + ], + "109172000" : [{ + "stateShort" : "BY", + "regionName" : "Kreis Berchtesgadener Land", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457002800000, + "start" : 1456978200000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Bayern" + } + ], + "107131000" : [{ + "stateShort" : "RP", + "regionName" : "Kreis Ahrweiler", + "description" : "Es tritt im Warnzeitraum oberhalb 400 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457006400000, + "start" : 1456991760000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 400, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + }, { + "stateShort" : "RP", + "regionName" : "Kreis Ahrweiler", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1456999200000, + "start" : 1456975800000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + } + ], + "108315000" : [{ + "stateShort" : "BW", + "regionName" : "Kreis Breisgau-Hochschwarzwald und Stadt Freiburg", + "description" : "Es treten oberhalb 1000 m Sturmböen mit Geschwindigkeiten bis 70 km/h (20m/s, 38kn, Bft 8) anfangs aus südwestlicher, später aus westlicher Richtung auf. In exponierten Lagen muss mit Sturmböen bis 80 km/h (22m/s, 44kn, Bft 9) gerechnet werden.", + "end" : 1457017200000, + "start" : 1456995600000, + "headline" : "Amtliche WARNUNG vor STURMBÖEN", + "event" : "STURMBÖEN", + "instruction" : "ACHTUNG! Hinweis auf mögliche Gefahren: Es können zum Beispiel einzelne Äste herabstürzen. Achten Sie besonders auf herabfallende Gegenstände.", + "altitudeStart" : 1000, + "altitudeEnd" : null, + "type" : 1, + "level" : 3, + "state" : "Baden-Württemberg" + }, { + "stateShort" : "BW", + "regionName" : "Kreis Breisgau-Hochschwarzwald und Stadt Freiburg", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1457038800000, + "start" : 1456986060000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Baden-Württemberg" + } + ], + "108326000" : [{ + "stateShort" : "BW", + "regionName" : "Schwarzwald-Baar-Kreis", + "description" : "Es treten oberhalb 1000 m Sturmböen mit Geschwindigkeiten bis 70 km/h (20m/s, 38kn, Bft 8) anfangs aus südwestlicher, später aus westlicher Richtung auf. In exponierten Lagen muss mit Sturmböen bis 80 km/h (22m/s, 44kn, Bft 9) gerechnet werden.", + "end" : 1457017200000, + "start" : 1456995600000, + "headline" : "Amtliche WARNUNG vor STURMBÖEN", + "event" : "STURMBÖEN", + "instruction" : "ACHTUNG! Hinweis auf mögliche Gefahren: Es können zum Beispiel einzelne Äste herabstürzen. Achten Sie besonders auf herabfallende Gegenstände.", + "altitudeStart" : 1000, + "altitudeEnd" : null, + "type" : 1, + "level" : 3, + "state" : "Baden-Württemberg" + }, { + "stateShort" : "BW", + "regionName" : "Schwarzwald-Baar-Kreis", + "description" : "Es tritt im Warnzeitraum oberhalb 600 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1457038800000, + "start" : 1456986060000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 600, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Baden-Württemberg" + } + ], + "107135000" : [{ + "stateShort" : "RP", + "regionName" : "Kreis Cochem-Zell", + "description" : "Es tritt im Warnzeitraum oberhalb 400 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457006400000, + "start" : 1456991760000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 400, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + }, { + "stateShort" : "RP", + "regionName" : "Kreis Cochem-Zell", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1456999200000, + "start" : 1456975800000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + } + ], + "107137000" : [{ + "stateShort" : "RP", + "regionName" : "Kreis Mayen-Koblenz", + "description" : "Es tritt im Warnzeitraum oberhalb 400 m leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. Verbreitet wird es glatt.", + "end" : 1457006400000, + "start" : 1456991760000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : 400, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + }, { + "stateShort" : "RP", + "regionName" : "Kreis Mayen-Koblenz", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1456999200000, + "start" : 1456975800000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Rheinland-Pfalz" + } + ], + "105366000" : [{ + "stateShort" : "NRW", + "regionName" : "Kreis Euskirchen", + "description" : "Es tritt im Warnzeitraum leichter Schneefall mit Mengen zwischen 1 cm und 5 cm auf. In Staulagen werden Mengen bis 10 cm erreicht. Verbreitet wird es glatt.", + "end" : 1456999200000, + "start" : 1456975800000, + "headline" : "Amtliche WARNUNG vor LEICHTEM SCHNEEFALL", + "event" : "LEICHTER SCHNEEFALL", + "instruction" : "", + "altitudeStart" : null, + "altitudeEnd" : null, + "type" : 3, + "level" : 2, + "state" : "Nordrhein-Westfalen" + } + ] + }, + "vorabInformation" : {}, + "copyright" : "Copyright Deutscher Wetterdienst" +}); diff --git a/test/testFunctions.js b/test/testFunctions.js new file mode 100644 index 0000000..ba36641 --- /dev/null +++ b/test/testFunctions.js @@ -0,0 +1,135 @@ +const expect = require('chai').expect; +const setup = require(__dirname + '/lib/setup'); +let name = __dirname.replace(/\\/g, '/').split('/'); +name = name[name.length - 2].split('.')[1]; + +let objects = null; +let states = null; +let onStateChanged = null; +let onObjectChanged = null; + +function checkConnectionOfAdapter(cb, counter) { + counter = counter || 0; + if (counter > 20) { + cb && cb('Cannot check connection'); + return; + } + + states.getState('system.adapter.' + name + '.0.alive', function (err, state) { + if (err) console.error(err); + if (state && state.val) { + cb && cb(); + } else { + setTimeout(function () { + checkConnectionOfAdapter(cb, counter + 1); + }, 1000); + } + }); +} + +function checkValueOfState(id, value, cb, counter) { + counter = counter || 0; + if (counter > 20) { + cb && cb('Cannot check value Of State ' + id); + return; + } + + states.getState(id, function (err, state) { + if (err) console.error(err); + if (value === null && !state) { + cb && cb(); + } else + if (state && (value === undefined || state.val === value)) { + cb && cb(); + } else { + setTimeout(function () { + checkValueOfState(id, value, cb, counter + 1); + }, 500); + } + }); +} + +describe('Test ' + name, function () { + before('Test ' + name + ': Start js-controller', function (_done) { + this.timeout(600000); // because of first install from npm + setup.setupController(function () { + const config = setup.getAdapterConfig(); + // enable adapter + config.common.enabled = true; + config.common.loglevel = 'debug'; + + config.native.location = __dirname + '/lib/forecast.xml'; + config.native.language = ''; + config.native.sendTranslations = false; + + setup.setAdapterConfig(config.common, config.native); + + setup.startController(true, function (id, obj) { + if (onObjectChanged) onObjectChanged(id, obj); + }, function (id, state) { + console.log(id + ': ' + state.val); + if (onStateChanged) onStateChanged(id, state); + }, + function (_objects, _states) { + objects = _objects; + states = _states; + states.subscribe('*'); + objects.subscribe('*'); + _done(); + }); + }); + }); + + it('Test ' + name + ': Check if adapter started', function (done) { + this.timeout(5000); + checkConnectionOfAdapter(done); + }); + + it('Test ' + name + ': channel must have name of city', function (done) { + setTimeout(function () { + objects.getObject('yr.0.forecast', function (err, obj) { + expect(err).to.be.not.ok; + expect(obj).to.be.ok; + expect(obj.common.name).to.be.equal('yr.no forecast forecast.xml'); + done(); + }); + }, 500); + }); + + it('Test ' + name + ': check states', function (done) { + this.timeout(5000); + + setTimeout(function () { + states.getState('yr.0.forecast.day0.temperatureMin', function (err, state) { + expect(err).to.be.not.ok; + expect(state).to.be.ok; + expect(state.val).to.be.not.undefined; + expect(state.val).to.be.a('number'); + + states.getState('yr.0.forecast.day0.temperatureMax', function (err, state) { + expect(err).to.be.not.ok; + expect(state).to.be.ok; + expect(state.val).to.be.not.undefined; + expect(state.val).to.be.a('number'); + + states.getState('yr.0.forecast.day0.temperatureActual', function (err, state) { + expect(err).to.be.not.ok; + expect(state).to.be.ok; + expect(state.val).to.be.not.undefined; + expect(state.val).to.be.a('number'); + done(); + }); + }); + }); + }, 100); + }); + + after('Test ' + name + ': Stop js-controller', function (done) { + this.timeout(7000); + + setup.stopController(function (normalTerminated) { + console.log('Adapter normal terminated: ' + normalTerminated); + done(); + }); + }); +}); \ No newline at end of file diff --git a/test/testPackageFiles.js b/test/testPackageFiles.js new file mode 100644 index 0000000..d0759c0 --- /dev/null +++ b/test/testPackageFiles.js @@ -0,0 +1,91 @@ +/* jshint -W097 */ +/* jshint strict:false */ +/* jslint node: true */ +/* jshint expr: true */ +var expect = require('chai').expect; +var fs = require('fs'); + +describe('Test package.json and io-package.json', function() { + it('Test package files', function (done) { + console.log(); + + var fileContentIOPackage = fs.readFileSync(__dirname + '/../io-package.json', 'utf8'); + var ioPackage = JSON.parse(fileContentIOPackage); + + var fileContentNPMPackage = fs.readFileSync(__dirname + '/../package.json', 'utf8'); + var npmPackage = JSON.parse(fileContentNPMPackage); + + expect(ioPackage).to.be.an('object'); + expect(npmPackage).to.be.an('object'); + + expect(ioPackage.common.version, 'ERROR: Version number in io-package.json needs to exist').to.exist; + expect(npmPackage.version, 'ERROR: Version number in package.json needs to exist').to.exist; + + expect(ioPackage.common.version, 'ERROR: Version numbers in package.json and io-package.json needs to match').to.be.equal(npmPackage.version); + + if (!ioPackage.common.news || !ioPackage.common.news[ioPackage.common.version]) { + console.log('WARNING: No news entry for current version exists in io-package.json, no rollback in Admin possible!'); + console.log(); + } + + expect(npmPackage.author, 'ERROR: Author in package.json needs to exist').to.exist; + expect(ioPackage.common.authors, 'ERROR: Authors in io-package.json needs to exist').to.exist; + + if (ioPackage.common.name.indexOf('template') !== 0) { + if (Array.isArray(ioPackage.common.authors)) { + expect(ioPackage.common.authors.length, 'ERROR: Author in io-package.json needs to be set').to.not.be.equal(0); + if (ioPackage.common.authors.length === 1) { + expect(ioPackage.common.authors[0], 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name '); + } + } + else { + expect(ioPackage.common.authors, 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name '); + } + } + else { + console.log('WARNING: Testing for set authors field in io-package skipped because template adapter'); + console.log(); + } + expect(fs.existsSync(__dirname + '/../README.md'), 'ERROR: README.md needs to exist! Please create one with description, detail information and changelog. English is mandatory.').to.be.true; + if (!ioPackage.common.titleLang || typeof ioPackage.common.titleLang !== 'object') { + console.log('WARNING: titleLang is not existing in io-package.json. Please add'); + console.log(); + } + if ( + ioPackage.common.title.indexOf('yunkong2') !== -1 || + ioPackage.common.title.indexOf('yunkong2') !== -1 || + ioPackage.common.title.indexOf('adapter') !== -1 || + ioPackage.common.title.indexOf('Adapter') !== -1 + ) { + console.log('WARNING: title contains Adapter or yunkong2. It is clear anyway, that it is adapter for yunkong2.'); + console.log(); + } + + if (ioPackage.common.name.indexOf('vis-') !== 0) { + if (!ioPackage.common.materialize || !fs.existsSync(__dirname + '/../admin/index_m.html') || !fs.existsSync(__dirname + '/../gulpfile.js')) { + console.log('WARNING: Admin3 support is missing! Please add it'); + console.log(); + } + if (ioPackage.common.materialize) { + expect(fs.existsSync(__dirname + '/../admin/index_m.html'), 'Admin3 support is enabled in io-package.json, but index_m.html is missing!').to.be.true; + } + } + + var licenseFileExists = fs.existsSync(__dirname + '/../LICENSE'); + var fileContentReadme = fs.readFileSync(__dirname + '/../README.md', 'utf8'); + if (fileContentReadme.indexOf('## Changelog') === -1) { + console.log('Warning: The README.md should have a section ## Changelog'); + console.log(); + } + expect((licenseFileExists || fileContentReadme.indexOf('## License') !== -1), 'A LICENSE must exist as LICENSE file or as part of the README.md').to.be.true; + if (!licenseFileExists) { + console.log('Warning: The License should also exist as LICENSE file'); + console.log(); + } + if (fileContentReadme.indexOf('## License') === -1) { + console.log('Warning: The README.md should also have a section ## License to be shown in Admin3'); + console.log(); + } + done(); + }); +}); diff --git a/widgets/yr.html b/widgets/yr.html new file mode 100644 index 0000000..1d33991 --- /dev/null +++ b/widgets/yr.html @@ -0,0 +1,33 @@ + + + + \ No newline at end of file diff --git a/widgets/yr/img/Prev_YrAdapter.png b/widgets/yr/img/Prev_YrAdapter.png new file mode 100644 index 0000000..881b37b Binary files /dev/null and b/widgets/yr/img/Prev_YrAdapter.png differ