Aby wykorzystać w WordPressie dane z serwera pogodowego, pobierane dla tokena, należy napisać widget.

Posłużę się tutaj prostym przykładem, który oczywiście można dowolnie przerobić.
Przy pisaniu widgetu korzystałem z opisów na stronie: https://wpadmin.pl, tak więc po szczegóły dot budowy w części WordPressowej sugeruję zerknąć na WPadmin.

Budowany widget będzie miał dwie klasy (pobierania danych i senso-stricte widgetowy) i prosty panel ustawień, informacja prezentowana będzie w formie tekstowej (ale będą zbudowane podstawy do prezentowania jej także w formie graficznej, albo wręcz wskazań poszczególnych parametrów aktualnej temperatury), będzie także prosty panel ustawień sposobów wyświetlania.

Klasa pobierająca dane

 

W zależności od miejsca dla którego czytamy dane, zdefiniowanego posiadanym tokenem, uzyskamy dane pogodowe aktualne i prognozę (ok. 60 miejsc – głównie w Polsce) lub tylko prognozę (ok. 300 miejsc w Polsce).
Klasa pobierająca dane ma za zadanie pobrać, sprawdzić i ujednolicić je w ten sposób, aby w łatwy sposób przedstawić w widgecie.

przykładowe dane z serwera- pogoda i prognoza:

"{"w":[{"name":"\u015awinouj\u015bcie","dt":"2018-07-18 09:00:00",
         "descr_forecast":"w godz.20-... bezchmurne niebo, temp. +20...+21\u00b0C, \u0142agodny...umiarkowany wiatr (3-4B) z kierunku NW, ci\u015bnienie 1027 hPa, rozchmurzy si\u0119 do 0\/8 , wilgotno\u015b\u0107 84...91 %",
         "descr_now":"pochmurnie, temperatura +19\u00b0C, wiatr z kierunku NW 3B (ok.5m\/s), ci\u015bnienie 1011hPa, wilgotno\u015b\u0107 93% ",
         "urln":"http:\/\/pogoda.wiks.eu\/a\/18092323_GWIBC9F5.wav",
         "urlf":"http:\/\/pogoda.wiks.eu\/a\/18092323_NMIPGAX9.wav",
         "img_forecast":"http:\/\/pbs.twimg.com\/media\/DiXuBp7XcAUVk2-.png",
         "lat":"53.923","lng":"14.278",
         "windspeed":"5",
         "winddir":"320",
         "humidity":"93",
         "pressure":"1011","clouds":"75","temp":"19"}],
          
         "f":[{"name":"\u015awinouj\u015bcie","dt":"2018-07-18 09:38:58",
         "for_date":"2018-07-18",
         "forec_descr":"w godz....-16 i 20-... przelotny deszcz, temp. +18...+19\u00b0C, \u0142agodny...umiarkowany wiatr (3-4B) z kierunku N, ci\u015bnienie 1028 hPa, zachmurzenie 7\/8, wilgotno\u015b\u0107 95...100 %",
         "url":"http:\/\/pogoda.wiks.eu\/a\/18093858_BM5GPWT2.wav"},
         {"name":"\u015awinouj\u015bcie","dt":"2018-07-18 09:38:59",
         "for_date":"2018-07-19",
         "forec_descr":"w godz....-0 i 8-13 przelotny deszcz, w godz.2-7 umiarkowany deszcz, w godz.20-... bezchmurne niebo, temp. +19...+21\u00b0C, umiarkowany...do\u015b\u0107 silny wiatr (4-5B), z kierunk\u00f3w NW, N, ci\u015bnienie 1027...1029 hPa, zachmurzenie 0-8\/8, wilgotno\u015b\u0107 87...100 %",
         "url":"http:\/\/pogoda.wiks.eu\/a\/18093858_VR7SIGAO.wav"},
         {"name":"\u015awinouj\u015bcie","dt":"2018-07-18 09:38:59",
         "for_date":"2018-07-20",
         "forec_descr":"w godz....-13 bezchmurne niebo, temp. +18...+20\u00b0C, powiew...umiarkowany wiatr (1-4B), z kierunku NW, ci\u015bnienie 1027...1029 hPa, zachmurzenie 0-3\/8, wilgotno\u015b\u0107 86...96 %",
         "url":"http:\/\/pogoda.wiks.eu\/a\/18093859_ZWRU2J24.wav"},
         {"name":"\u015awinouj\u015bcie","dt":"2018-07-18 09:38:59",
         "for_date":"2018-07-21",
         "forec_descr":"w godz.2-16 bezchmurne niebo, temp. +18...+24\u00b0C, powiew...umiarkowany wiatr (1-4B), z kierunk\u00f3w E, SW, ci\u015bnienie 1021...1027 hPa, zachmurzenie 0-7\/8, wilgotno\u015b\u0107 74...99 %",
         "url":"http:\/\/pogoda.wiks.eu\/a\/18093859_L2J2U4W5.wav"}]}"

przykładowe dane z serwera- tylko prognoza:

          
"{"p":{
         "0":{"name":"Krosno Odrza\u0144skie","dt":"2018-07-17 19:30:51","for_date":"2018-07-18",
         "forec_descr":"w godz....-0 i 20-... bezchmurne niebo, temp. +17...+26\u00b0C, \u0142agodny...umiarkowany wiatr (3-4B), z kierunk\u00f3w NW, N, ci\u015bnienie 1016...1019 hPa, zachmurzenie 0-7\/8, wilgotno\u015b\u0107 50...80 %",
         "url":"http:\/\/pogoda.wiks.eu\/a\/17193051_LRQRZM4I.wav"},
         "1":{"name":"Krosno Odrza\u0144skie","dt":"2018-07-17 19:30:52","for_date":"2018-07-19",
         "forec_descr":"w godz....-0, 5-10, 14-... bezchmurne niebo, temp. +16...+26\u00b0C, \u0142agodny wiatr ( 3B ), z kierunku NW, ci\u015bnienie 1018...1020 hPa, zachmurzenie 0-6\/8, wilgotno\u015b\u0107 38...70 %",
         "url":"http:\/\/pogoda.wiks.eu\/a\/17193052_6K0U092W.wav"},
         "name":"Lulla"}}"         

Wynik działania klasy pobierająco przetwarzającej jest podobny do:

Array
(
    [type] => w
    [name] => Świnoujście
    [w] => Array
        (
            [dt] => 2018-07-18 11:30:00
            [descr] => przelotny deszcz, temperatura +19°C, wiatr z kierunku NW 3B (ok.4m/s), ciśnienie 1013hPa, wilgotność 88% 
            [descr_12h] => w godz....-16 i 20-... przelotny deszcz, temp. +18...+19°C, łagodny...umiarkowany wiatr (3-4B) z kierunku N, ciśnienie 1028 hPa, zachmurzenie 7/8, wilgotność 95...100 %
            [img] => 
            [url] => http://pogoda.wiks.eu/a/18115504_I4SCADDS.wav
            [url_12h] => http://pogoda.wiks.eu/a/18115504_ZLGTPN2S.wav
        )
    [f] => Array
        (
            [2018-07-18] => Array
                (
                    [descr] => w godz....-16 i 20-... przelotny deszcz, temp. +18...+19°C, łagodny...umiarkowany wiatr (3-4B) z kierunku N, ciśnienie 1028 hPa, zachmurzenie 7/8, wilgotność 95...100 %
                    [url] => http://pogoda.wiks.eu/a/18093858_BM5GPWT2.wav
                )
            [2018-07-19] => Array
                (
                    [descr] => w godz....-0 i 8-13 przelotny deszcz, w godz.2-7 umiarkowany deszcz, w godz.20-... bezchmurne niebo, temp. +19...+21°C, umiarkowany...dość silny wiatr (4-5B), z kierunków NW, N, ciśnienie 1027...1029 hPa, zachmurzenie 0-8/8, wilgotność 87...100 %
                    [url] => http://pogoda.wiks.eu/a/18093858_VR7SIGAO.wav
                )
            [2018-07-20] => Array
                (
                    [descr] => w godz....-13 bezchmurne niebo, temp. +18...+20°C, powiew...umiarkowany wiatr (1-4B), z kierunku NW, ciśnienie 1027...1029 hPa, zachmurzenie 0-3/8, wilgotność 86...96 %
                    [url] => http://pogoda.wiks.eu/a/18093859_ZWRU2J24.wav
                )
            [2018-07-21] => Array
                (
                    [descr] => w godz.2-16 bezchmurne niebo, temp. +18...+24°C, powiew...umiarkowany wiatr (1-4B), z kierunków E, SW, ciśnienie 1021...1027 hPa, zachmurzenie 0-7/8, wilgotność 74...99 %
                    [url] => http://pogoda.wiks.eu/a/18093859_L2J2U4W5.wav
                )
        )
    [dt] => 2018-Jul-18 09:38:59
)
-------------- lub ----------------
Array
(
    [type] => p
    [name] => Lulla
    [f] => Array
        (
            [2018-07-18] => Array
                (
                    [descr] => w godz....-0 i 20-... bezchmurne niebo, temp. +17...+26°C, łagodny...umiarkowany wiatr (3-4B), z kierunków NW, N, ciśnienie 1016...1019 hPa, zachmurzenie 0-7/8, wilgotność 50...80 %
                    [url] => http://pogoda.wiks.eu/a/17193051_LRQRZM4I.wav
                )
            [2018-07-19] => Array
                (
                    [descr] => w godz....-0, 5-10, 14-... bezchmurne niebo, temp. +16...+26°C, łagodny wiatr ( 3B ), z kierunku NW, ciśnienie 1018...1020 hPa, zachmurzenie 0-6/8, wilgotność 38...70 %
                    [url] => http://pogoda.wiks.eu/a/17193052_6K0U092W.wav
                )
        )

    [dt] => 2018-Jul-17 19:30:52
)

Klasa widgetu – zobrazowanie

Przetworzenie powyższego dla zobrazowania:

/** Wyświetlanie widgetu uzytkownikowi
 * 
 * @param type $args
 * @param type $instance
 */
public function widget($args, $instance) {

   $my_html_content = '';
   $g = new GETpogodaWiksEu();
   $vcontent = $g->get_and_validate_for_token($instance['token']);
   if($vcontent) {
      if(!empty($vcontent['name']) && !empty($vcontent['f'])) {
         $my_html_content = '<h3>'.$vcontent['name'].'</h3>';
         $lp_days = '0';
         foreach($vcontent['f'] as $key=>$value) {
            if($lp_days++ < $instance['max_days']) {
               $my_html_content .= '<h4>';
               if(!$instance['days_as_date_only'] === true) {
                  $inside_key = $key;
               }else{
                  $inside_key = $this->prittyfy_dt($key);
               }
               if($instance['allow_audio'] === true && !empty($value['url'])){
                  $img = '<img src="'.$this->url_img.'speaker50.gif'.'" 
                  style="width:20px;height:20px;">';
                  $my_html_content .= '<a href="'.$value['url'].'">'.$img.' '.$inside_key.'</a>';
               }else{
                  $my_html_content .= $inside_key;
               }
               $my_html_content .= ':</h4>';
               $my_html_content .= $value['descr'].'<br>';
            }
         }
      }
   }
   echo '<div class="widget" id="wiks_weather_content">'.$my_html_content.'</div>';
}

Zaś opcje:

/**
 * Outputs the options form on admin
 *
 * @param array $instance The widget options
 */
public function form( $instance ) {

   //ustawiamy opcje domyslne
   $my_defaults = array(
      'token' => 'for_test_cntcXBWYJDQ0H6VwanN3Zrh', // test token
      'max_days' => 3,
      'days_as_date_only' => false,
      'allow_audio' => true,
   );
   $instance = wp_parse_args( (array) $instance, $my_defaults );
   ?>
   <p>
   <!-- token -->
   <label for="<?php echo $this->get_field_id('token'); ?>"><?php _e('Token:'); ?></label>
   <input class="widefat" id="<?php echo $this->get_field_id('token'); ?>" name="<?php echo $this->get_field_name('token'); ?>" type="text" value="<?php echo esc_attr($instance['token']); ?>" />
   </p>
   <p>
   <!-- max days show -->
   <label for="<?php echo $this->get_field_id( 'max_days' ); ?>"><?php _e( 'Maksymalna liczba dni prognozy:' ); ?></label>
   <input id="<?php echo $this->get_field_id( 'max_days' ); ?>" name="<?php echo $this->get_field_name( 'max_days' ); ?>" type="text" value="<?php echo $instance['max_days']; ?>" size="3" />
   </p>
   <p>
   <!-- czy zmieniać daty na przyjazne opisy dziś jutro -->
   <input class="checkbox" type="checkbox" name="<?php echo $this->get_field_name('days_as_date_only'); ?>" id="<?php echo $this->get_field_id('days_as_date_only'); ?>" value="true" <?php checked(true, $instance['days_as_date_only']);?> />
   <label for="<?php echo $this->get_field_id('days_as_date_only'); ?>"> <?php _e( 'pokaż przyjazne opisy dni np. "dziś" zamiast "'.date('Y-m-d', time()).'"' ); ?></label><br />
   </p>
   <p>
   <!-- czy tworzyć linki do audio -->
   <input class="checkbox" type="checkbox" name="<?php echo $this->get_field_name('allow_audio'); ?>" id="<?php echo $this-
   >get_field_id('allow_audio'); ?>" value="true" <?php checked(true, $instance['allow_audio']);?> />
   <label for="<?php echo $this->get_field_id('allow_audio'); ?>"> <?php _e( 'utwórz odnośniki do audio' ); ?></label><br />
   </p>
   <?php
}

opcje prezentują się tak:

Widget zaś jest tutaj:


 

Radości!
WikS

Aby sprawdzić co można uzyskać dla tokena w pełniejszej nieco formie, np do wykorzystania w widgecie WordPressa albo na stronie, możemy użyć prostego Pythonowego skruptu:

# coding=utf-8

import requests
import json


def tes_json_resp(url):
    """

    :param url:
    :return:
    """
    print '==========================================================================='
    print 'pytanie POST - odpowiedź w formacie JSON -kompleksowa, na podstawie tokena'
    print
    data = {'t': 'for_test_cntcXBWYJDQ0H6VwanN3Zrh',
            }
    r = requests.post(url, data=data)
    print r.content


if __name__ == "__main__":

    url = 'http://pogoda.wiks.eu/api.php'
    tes_json_resp(url)

dla miejsc, dla których posiadamy pogodę i prognozę:

{"w":[
{"name":"\u015awinouj\u015bcie",
"dt":"2018-07-16 20:30:00",
"descr_forecast":"w godz.2-3 przelotny deszcz, w godz.5-... umiarkowany deszcz, temp. +18...+21\u00b0C, \u0142agodny...do\u015b\u0107 silny wiatr (3-5B) z kierunk\u00f3w zmiennych: NE, NW, ci\u015bnienie 1026 hPa, zachmurzenie 6-7\/8 , wilgotno\u015b\u0107 96...100 %",
"descr_now":"bezchmurne niebo, temperatura +23\u00b0C, wiatr z kierunku N 2B (ok.3m\/s), ci\u015bnienie 1012hPa, wilgotno\u015b\u0107 49% ",
"urln":"http:\/\/pogoda.wiks.eu\/a\/16210432_7AKPGSDO.wav",
"urlf":"http:\/\/pogoda.wiks.eu\/a\/16210432_R3YV8CUO.wav",
"img_forecast":"http:\/\/pbs.twimg.com\/tweet_video_thumb\/DiP7XOHXcAEQXR8.jpg",
"lat":"53.923","lng":"14.278",
"windspeed":"3","winddir":"360",
"humidity":"49","pressure":"1012",
"clouds":"0","temp":"23"}
],
"f":[
{"name":"\u015awinouj\u015bcie",
"dt":"2018-07-16 18:18:49","for_date":"2018-07-16",
"forec_descr":"temp. +22\u00b0C, \u0142agodny wiatr ( 3B ) z kierunku NE, ci\u015bnienie 1026 hPa, zachmurzenie 6\/8, wilgotno\u015b\u0107 92 %",
"url":"http:\/\/pogoda.wiks.eu\/a\/16181849_CLFSLKYG.wav"},
{"name":"\u015awinouj\u015bcie",
"dt":"2018-07-16 21:20:00","for_date":"2018-07-17",
"forec_descr":"w godz.2-3 i 11-... przelotny deszcz, w godz.5-10 umiarkowany deszcz, temp. +18...+20\u00b0C, \u0142agodny...do\u015b\u0107 silny wiatr (3-5B), z kierunku N, ci\u015bnienie 1024...1026 hPa, zachmurzenie 6-7\/8, wilgotno\u015b\u0107 91...100 %",
"url":"http:\/\/pogoda.wiks.eu\/a\/16212000_76M4KUED.wav"},
{"name":"\u015awinouj\u015bcie",
"dt":"2018-07-16 21:20:01","for_date":"2018-07-18",
"forec_descr":"w godz....-4 umiarkowany deszcz, w godz.5-10 przelotny deszcz, temp. +18...+22\u00b0C, \u0142agodny...umiarkowany wiatr (3-4B), z kierunku N, ci\u015bnienie 1024...1028 hPa, zachmurzenie 1-7\/8, wilgotno\u015b\u0107 84...100 %","url":"http:\/\/pogoda.wiks.eu\/a\/16212000_C4JIZ0HF.wav"},{"name":"\u015awinouj\u015bcie",
"dt":"2018-07-16 21:20:02","for_date":"2018-07-19",
"forec_descr":"bezchmurne niebo, temp. +19...+21\u00b0C, umiarkowany...do\u015b\u0107 silny wiatr (4-5B), z kierunk\u00f3w NW, N, ci\u015bnienie 1028...1030 hPa, wilgotno\u015b\u0107 84...98 %",
"url":"http:\/\/pogoda.wiks.eu\/a\/16212001_LYK9PYBZ.wav"}
]}

– gdzie:
w = pogoda, f = prognoza,
dt = moment opracowania,
for_date = dla prognozy dzień na który jest opracowana,
„urln” i „urlf” = adresy url plików audio odczytów pogody i prognozy,
„img_forecast” = url obrazka (jeśli występuje) lub None/NULL
„lat”:”53.923″,”lng”:”14.278″, = współrzędne geograficzne,
„windspeed”:”3″,”winddir”:”360″, = prędkość i kierunek wiatru,
„humidity”:”49″,”pressure”:”1012″, wilgotność i ciśnienie,
„clouds”:”0″,”temp”:”23″} zachmurzenie i temperatura

oraz dla miejsc, dla których mamy tylko prognozę:

{"p":{
"0":{"name":"Krosno Odrza\u0144skie",
"dt":"2018-07-16 11:03:41","for_date":"2018-07-16",
"forec_descr":"przelotny deszcz, temp. +19...+30\u00b0C och\u0142odzi si\u0119, powiew...s\u0142aby wiatr (1-2B), ci\u015bnienie 1016 hPa, zachmurzenie 3-7\/8 , wilgotno\u015b\u0107 40...87 %",
"url":"http:\/\/pogoda.wiks.eu\/a\/16110340_TGBUNSLC.wav"},
"1":{"name":"Krosno Odrza\u0144skie",
"dt":"2018-07-16 11:03:42","for_date":"2018-07-17",
"forec_descr":"w godz.11-16 przelotny deszcz, w godz.17-... umiarkowany deszcz, temp. +16...+23\u00b0C, s\u0142aby...do\u015b\u0107 silny wiatr (2-5B), z kierunk\u00f3w NW, N, ci\u015bnienie 1015...1016 hPa, zachmurzenie 1-7\/8, wilgotno\u015b\u0107 64...98 %","url":"http:\/\/pogoda.wiks.eu\/a\/16110341_HM7AWIYM.wav"},
"2":{"name":"Krosno Odrza\u0144skie",
"dt":"2018-07-16 11:03:42","for_date":"2018-07-18",
"forec_descr":"w godz....-4 umiarkowany deszcz, w godz.5-13 przelotny deszcz, temp. +17...+24\u00b0C, \u0142agodny...umiarkowany wiatr (3-4B), z kierunk\u00f3w NW, N, ci\u015bnienie 1015...1020 hPa, zachmurzenie 2-7\/8, wilgotno\u015b\u0107 70...99 %",
"url":"http:\/\/pogoda.wiks.eu\/a\/16110342_X4AU9PJJ.wav"},
"3":{"name":"Krosno Odrza\u0144skie",
"dt":"2018-07-16 11:03:43","for_date":"2018-07-19",
"forec_descr":"w godz.14-... bezchmurne niebo, temp. +16...+26\u00b0C, s\u0142aby...umiarkowany wiatr (2-4B), z kierunk\u00f3w NW, N, ci\u015bnienie 1021...1022 hPa, zachmurzenie 0-2\/8, wilgotno\u015b\u0107 48...83 %",
"url":"http:\/\/pogoda.wiks.eu\/a\/16110342_B4PA7PEW.wav"},
"name":"Lulla"}}

Prognoza pogody „p” występuje na kolejne dni „for_date”, zazwyczaj są to 2 do 4 dni.
Prognoza jest przygotowywana raz na około 24h.
„name” występująca wewnątrz to nazwa miejsca dla którego dokładnie jest przygotowana prognoza (np. nazwa powiatu), zaś „name” występująca na końcu – jeden raz to nazwa miejsca leżącego bardzo blisko powyższego powiatu (w jego administracyjnym obrębie), dla której pogodę/prognozę potrzebujemy.

Jak użyć tego np dla WordPress?

Radości!
WikS

Jednym ze sposobów wykorzystania danych pogodowych jest ich odczyt przez urządzenie za pomocą requestu GET z określonego IP lub wraz z unikatowym tokenem.

Do tego celu zostało przygotowane małe PHPowe API pod adresem:

http://pogoda.wiks.eu/api.php

API wykonuje dwa rodzaje działań na w odpowiedzi na trzy rodzaje zapytań GET:

  • zapytanie ze znanego adresu IP – nie wymaga żadnych danych – zwraca określone warunki pogodowe,
  • zapytanie z podaniem unikatowego tokena – również zwraca określone warunki pogodowe,
  • zapytanie o token z określonego adresu IP, z podaniem adresu MAC karty sieciowej urządzenia – w momencie gdy oczekujemy takiego zapytania – zwraca token, którym następnie urządzenie może posługiwać się z dowolnego adresu IP

W trzecim spośród opisanych przykładów należy odblokować na kilka minut możliwość udzielenia odpowiedzi na pytanie urządzenia, podać jego MAC oraz adres IP z którego pytanie zostanie zadane. Wszystkie czynności po zalogowaniu uprawnionego użytkownika.
Urządzenie, które startuje po raz pierwszy i nie ma jeszcze ważnego tokena (albo nie uzyskało danych przy pomocy posiadanego tokena), ukaże na swoim wyświetlaczu adres MAC karty WiFi oraz informacje, że wysyła pytanie o token.
Token jest przechowywany w nieulotnej pamięci EEPROM urządzenia.
Token, który wpadnie w niepowołane ręce może zostać łątwo zmieniony i w ten sposób pytania z nim pozostaną bez odpowiedzi.

Zatem jeśli wpiszemy w pasek adresu przeglądarki:

http://pogoda.wiks.eu/api.php

– nie uzyskamy nic, chyba że pytanie zostało wysłąne z uprawnionego adresu.
Jeśli wpiszemy pytanie z tokenem, np:

http://pogoda.wiks.eu/api.php?t=for_test_cntcXBWYJDQ0H6VwanN3Zrh

uzyskamy coś jak:

;POGODA.WIKS.EU;Swinoujscie;2018-07-16 16:30;27;1012;0;5;340;47;bezchmurne niebo, temperatura +27C, wiatr z kierunku N 3B (ok.5m/s), cisnienie 1012hPa, wilgotnosc 47% ;w godz.2-... przelotny deszcz, temp. +19...+25C ochlodzi sie, lagodny...umiarkowany wiatr (3-4B) z kierunku NE, cisnienie 1026 hPa, zachmurzenie 6-7/8 , wilgotnosc 85...100 %;;EU.WIKS.POGODA;;

Jeśli z treści wyciągniemy to co jest pomiędzy ;POGODA.WIKS.EU; a ;EU.WIKS.POGODA; , oraz podzielimy to wzdłuż średników, uzyskamy:

Swinoujscie;
2018-07-16 16:30;
27;
1012;
0;
5;
340;
47;
bezchmurne niebo, temperatura +27C, wiatr z kierunku N 3B (ok.5m/s), cisnienie 1012hPa, wilgotnosc 47% ;
w godz.2-... przelotny deszcz, temp. +19...+25C ochlodzi sie, lagodny...umiarkowany wiatr (3-4B) z kierunku NE, cisnienie 1026 hPa, zachmurzenie 6-7/8 , wilgotnosc 85...100 %;

czyli 10 pól, które oznaczają kolejno:

0 –> Swinoujscie – nazwa miejsca
1 –> 2018-07-16 17:00 – moment, dla którego wysyłamy pogodę, lub moment wysłania prognozy
2 –> 27 – temperatura (stC),
3 –> 1012 – ciśnienie (hPa),
4 –> 0 – siła wiatru w stopniach Bouforta,
5 –> 5 – prędkość wiatry w m/s,
6 –> 350 – kierunek wiatru w stopniach względem północy,
7 –> 44 – wilgotność (w %),
8 –> bezchmurne niebo, temperatura +27C, wiatr z kierunku N 3B (ok.5m/s), cisnienie 1012hPa, wilgotnosc 44% – tekstowy opis obecnej sytuacji
9 –> w godz.2-… przelotny deszcz, temp. +19…+25C ochlodzi sie, lagodny…umiarkowany wiatr (3-4B) z kierunku NE, cisnienie 1026 hPa, zachmurzenie 6-7/8 , wilgotnosc 85…100 % – tekstowy opis najbliższej prognozy pogody, najbliższe około 12 godzin

Warto zwrócić uwagę, że tekst jest pozbawiony innych znaków niż ASCII – aby łatwiej analizować go i wyświetlać w prostym 8bitowym urządzeniu.

W przypadku miejsc, dla których gromadzone są jedynie prognozy (czyli ok 300 miejsc w PL):

0 –> Krosno Odrzanskie
1 –> 2018-07-16 17:40:55
9 –> przelotny deszcz, temp. +19…+30C ochlodzi sie, powiew…slaby wiatr (1-2B), cisnienie 1016 hPa, zachmurzenie 3-7/8 , wilgotnosc 40…87 %

GitHUB:

https://github.com/wiks/arduino_weather

Programik wewnątrz ArduinoMEGA:

// http://www.arduino.cc/en/Tutorial/LiquidCrystalDisplay
// https://www.elektroda.pl/rtvforum/topic3091069.html
#include 
#include 

// eeprom addresses:
int eeprom_addr_token = 0;
int eeprom_addr_ssid = 32;
int eeprom_addr_passwd = 64;

// place for wifi credentials
boolean got_new_wifi_creds = false;
String SSID_ = ""; 
String PASSW = ""; 

// time in sec change screen LCD
const int one_screen_time = 15;

// eeprom egdes:
const int EEPROM_MIN_ADDR = 0;
const int EEPROM_MAX_ADDR = 511; // TODO
// for eeprom excachge:
const int BUFSIZE = 32; // TODO
char buft[BUFSIZE + 1];

// html content of weather starts and stops with:
const String opening_string = "POGODA.WIKS.EU";
const String closing_string = "EU.WIKS.POGODA";

// url of weather api server:
const String api_url = "http://pogoda.wiks.eu/api.php";
// host-url
const String host_url = "pogoda.wiks.eu";

// ---
// for own MAC and IP:
String myip = "";
String mac = "";
// for token into and from EEPROM:
String token = ""; // will be read from eeprom or -if empty - ask with MAC
// global place for read html content
String picked = ""; 
// array for fields position in html content:
int colon_divided_fields[15] = {}; 

// here result to show on LCD:
String ss_now_city2 = "";
String ss_forecast = "";
int failed_attempts = 0;

// buffer for read whole html content
long buf_max_size = 2000;
byte buf[2000];

/*
  LiquidCrystal440 Library

 The circuit:
 * LCD Enable2 pin to digital pin 13
 * LCD RS pin to digital pin 12
 * LCD Enable pin to digital pin 11
 * LCD D4 pin to digital pin 5
 * LCD D5 pin to digital pin 4
 * LCD D6 pin to digital pin 3
 * LCD D7 pin to digital pin 2
 * LCD R/W pin to ground
 * 10K resistor:
 * ends to +5V and ground
 * wiper to LCD VO pin (pin 3)

 http://www.arduino.cc/en/Tutorial/LiquidCrystalDisplay
 https://www.elektroda.pl/rtvforum/topic3091069.html
*/

// initialize the LCD440 library by associating any needed LCD interface pin
// with the arduino pin number it is connected to
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
const int en2 = 13; // for LCD440
int rw = 255; // not connected
LiquidCrystal lcd(rs, rw, en, en2, d4, d5, d6, d7);

//  EEPROM ---
// https://gist.github.com/smching/05261f11da11e0a5dc834f944afd5961

/* checks range of eeprom address
 */
boolean eeprom_is_addr_ok(int addr) {
  
  return ((addr >= EEPROM_MIN_ADDR) && (addr <= EEPROM_MAX_ADDR));
}

/* Writes bytes into address
 */
boolean eeprom_write_bytes(int startAddr, const byte* array, int numBytes) {
  
  int i;
  if (!eeprom_is_addr_ok(startAddr) || !eeprom_is_addr_ok(startAddr + numBytes)) {
    return false;
  }
  for (i = 0; i < numBytes; i++) {
    EEPROM.write(startAddr + i, array[i]);
  }
  return true;
}

/* Writes a string starting at the specified address.
 * max 32chars
 * returns true if succes
 */
boolean eeprom_write_32string(int addr, const char* string) {

  //int numBytes; // actual number of bytes to be written
  //write the string contents plus the string terminator byte (0x00)
  int numBytes = strlen(string) + 1;
  if (numBytes > BUFSIZE) {
    numBytes = BUFSIZE;
  }
  //Serial.println("eeprom --> " + String(string) );
  return eeprom_write_bytes(addr, (const byte*)string, numBytes);
}

/* Reads a string starting from the specified address.
 * max 32 chars
 * returns true if succes
 */
boolean eeprom_read_32string(int addr, char* buffer) {
  
  byte ch; // byte read from eeprom
  int bytesRead = 0; // number of bytes read so far
  if (!eeprom_is_addr_ok(addr)) { // check start address
    return false;
  }
  int bufSize = BUFSIZE;
  ch = 1; // no mather but other than 0x00
  while ( (ch != 0x00) && (bytesRead < bufSize) && ( (addr + bytesRead) <= EEPROM_MAX_ADDR) ) {
    ch = EEPROM.read(addr + bytesRead);
    buffer[bytesRead] = ch; 
    bytesRead++; 
  }
  // make sure the user buffer has a string terminator, (0x00) as its last byte
  if ((ch != 0x00) && (bytesRead >= 1)) {
    buffer[bytesRead] = 0;
  }
  return true;
}

/** wait for OK ack
 */
boolean rx_ok(String descr="") {
  
  if(Serial1.find("OK")) {
    //Serial.println(descr + "--> RX ok");
    return true;
  }
  Serial.println(descr + "... ok not found ");
  return false;
}

/** send AT command, wait for ack OK
 */
boolean at_and_wait_ok(String at) {

  Serial1.println(at);
  //Serial.println(at);
  String descr = "TX: " + at + " ";
  return rx_ok(descr);
}

/* like in name
 *  
 */
boolean reset_wifi_modul() {

  boolean ret = false; // not connected
  Serial.println("RESETTING ESP8266 WIFI CHIP");
  lcd.home();  
  lcd.print("http://pogoda.wiks.eu  Dzien dobry :-)");
  while(true){
    at_and_wait_ok("AT+RST");
    if(Serial1.find("eady")) { // after reset should return 'Ready'
      Serial.println("Resetted and ready");
      return;
    }
    delay(2000);
  }
}

/** get several bytes to specified char, f.e. "
 *  specified nomber of chars is 'some_bytes'
 */
String rx_some_bytes_end_with(char divider='"', int some_bytes=16, int max_wait=3000){

  String rx_bytes = "";
  int wait = 0;
  int count_bytes = 0;
  while((wait < max_wait) && (count_bytes < some_bytes) ){
    if( Serial1.available() ) {
      char c = Serial1.read();
      if ( c != divider ) { 
        rx_bytes += String(c);
        count_bytes += 1;
      }
      if( count_bytes >= some_bytes || c == divider ){
        return rx_bytes;    
      }
    }
    wait += 5;
    delay(5);
  }
  return "";
}

/** ask about IP oraz MAC from connected router
 *  
 */
boolean ask_about_ip() {
  
  //global mac;
  
  boolean res = false;
  int max_wait = 500;
  int wait = 0;
  delay(1000);
  Serial1.flush();
  Serial1.println("AT+CIFSR");
  Serial.println("AT+CIFSR --> Get local IP address...");
  while(!Serial1.find("+CIFSR:STAIP,\"")) {
    delay(100);
    Serial.print(".");
  }
  Serial.println("");
  myip = rx_some_bytes_end_with('"');
  Serial.println("mam IP " + myip);
  while(!Serial1.find("+CIFSR:STAMAC,\"")) {
    delay(100);
    Serial.print(".");
  }
  Serial.println("");
  mac = rx_some_bytes_end_with('"', 20);
  Serial.println("MAC " + mac);
  rx_ok("(from +CIFSR)");
  Serial1.flush();
  return ( myip && mac );
}

/** waiting for info about IP and ask IP & MAC
 *  return true if both are found
 */
boolean find_ip() {
  
  Serial.print("waiting for IP i MAC [ \"WIFI GOT IP\" ]...");
  boolean have_ip_mac = false;
  while(!Serial1.find("WIFI GOT IP")){
    Serial.print(".");
    delay(1000);    
  }
  Serial.println("");
  Serial.println("pytam o IP i MAC");
  have_ip_mac = ask_about_ip();
  if(have_ip_mac == false) {
    Serial.println("not IP, MAC found..." );
  }else{
    // TODO show MAC only if not recived weather...
    //lcd.setCursor(0, 2);
    //lcd.print("MAC " + mac + " ");
    // IP local is here - totally not 
    //lcd.setCursor(22, 2);
    //lcd.print("ip " + myip);
    if(got_new_wifi_creds) {
      Serial.println("got_new_wifi_creds --> write it to EEPROM");
    }
  }
  return have_ip_mac;
}

/* read bytes into buff
 *  return number bytes read
 */
long rx_and_usbprint_bytes(byte pbuf[], int bytes) { 
  
  int i = 0;
  int index =0;
  while(i= '0' && c <= '9' ) {  
      max_blend_before_count = 0;
      index++;
      str += String(c);
    }else{
      if( max_blend_before_count-- <= 0 ) { // or not digit before number
        if (str.length()>0) {
          rx = str.toInt();
        }
        break;
      }
    }
    delay(5);
  }
  return rx;
}

/** send anounce to request,
 *  after prompt > send request and recive response
 */
long send_content_wifi(String after_cmd) {

  long rx = 0;
  long readed_bytes = 0;
  String cmd2 = "AT+CIPSEND=" + String(after_cmd.length());
  Serial1.println(cmd2);
  Serial.println(cmd2 + " <-- need send request");
  delay(1500); 
  if(!Serial1.find(">")) {
    Serial1.println("AT+CIPCLOSE");
    Serial.println("prompt ( > ) not found");
    return rx;
  }
  Serial1.println(after_cmd);
  /* +IPD,747:HTTP/1.1 200 OK */
  wait_for_somestring_recived("+IPD,", 300);
  rx = wait_valueint_recive(5);
  if(Serial1.find("OK")){
    Serial.println("to RX: " + String(rx) + " bajtów, HTTP ... 200 OK" );
  }
  readed_bytes = rx_and_usbprint_bytes(buf, rx-175);  // 17 is length of "HTTP/1.1 200 OK + \r\n"
  Serial1.flush();
  Serial.println("rx bytes: " + String(readed_bytes) );    
  return readed_bytes;
}

/** pick up from buffor content between opening_string and closing_string strings
 */
String pick_up_mycontent(int readed_bytes) {
  
  String tmp_string = "";
  boolean gotit = false;
  String clue_content = "";
  if(readed_bytes > 0) {
    for(int i=0;i 31 ) {
        if(gotit) {
          tmp_string += char(buf[i]);
          if(tmp_string.endsWith(closing_string + ";")) {
            tmp_string = tmp_string.substring(0, tmp_string.indexOf(";"+closing_string));
            return tmp_string;
          }
        }else{
          if (!gotit) {
            tmp_string += char(buf[i]);
            if(tmp_string.endsWith(opening_string + ";")) {
              gotit = true;
              tmp_string = "";
            }
          }
        }
      }
    }
  }  
  return "";
}

/** connect to WiFi
 */
boolean connectWiFi() {

  Serial1.println("AT+CWMODE=1");
  delay(5000);
  String cmd = "AT+CWJAP=\""+SSID_+"\",\""+PASSW+"\"";
  return at_and_wait_ok(cmd);
}

/** connect to remote server
 */
void connect_weather_server() {

  Serial.print("connecting server...");
  String cmd = "AT+CIPSTART=\"TCP\",\"" + host_url + "\",80";
  //Serial.print( cmd );
  while(!at_and_wait_ok(cmd)) {
    delay(1000);
    Serial.print(".");
  };
  Serial.println(" :-)");
}

/** connect to wifi, 
 *  find MAC, IP set transmision mode
 */
void looking_wifi_ip_mac() {

  lcd.setCursor(0, 1);
  lcd.print(" looking for WiFi: " + SSID_ );
  boolean is_connected = false;
  int i = 5;
  while(!is_connected) {
    Serial.println();
    i = 5;
    Serial.print("looking for WiFi");
    while(!Serial1.find("WIFI CONNECTED") && i > 0){
      Serial.print(".");
      delay(1000);
      i--;
    }
    if(i > 0) {
      Serial.println(" --> ?is auto-connected.");
      is_connected = true;
    }else{
      is_connected = connectWiFi(); 
      if(is_connected) {
        Serial.println(" --> ?is connected, after USER&PASS.");
      }
    }
  }
  // ------- only connected -------  
  Serial.println("looking for IP...");
  find_ip();
  Serial.flush();
  delay(500); 
  //  set the single connection mode
  Serial.print("set the single connection mode...");
  while (!at_and_wait_ok("AT+CIPMUX=0")) {
    delay(500);
    Serial.print(".");
  }; 
  Serial.println("");
}

/** get token with MAC (needed server allow to)
 */
String token_from_mac() {

    token = "";
    if(mac.length() > 10) {
      lcd.setCursor(0, 2);
      lcd.print("MAC " + mac + " ");
      lcd.setCursor(0, 3);
      lcd.print("ask about token (MAC & IP) is allowed?");
      String cmd_get = "GET " + api_url + "?m=" + mac + " HTTP/1.1\r\n\r\nHost: " + host_url + ":80\r\n";
      long readed_bytes = send_content_wifi(cmd_get);
      token = pick_up_mycontent(readed_bytes);
      if( token.length() > 4 ) {
        Serial.println( "rx-token: " + token );
        token.toCharArray(buft, BUFSIZE); 
        eeprom_write_32string(eeprom_addr_token, buft);
        Serial.print("Token read from api.weather, writen to EEPROM: " + token );
        delay(500);
      }else{
        Serial.print("No token from api.weather...");
      }
    }
    return token;
}

/** get weather from server ask with token
 *  find number of fields divided with semicolon
 */
int get_request_weather() {
  
  String cmd_get = "GET " + api_url + "?t=" + token + " HTTP/1.1\r\n\r\nHost: " + host_url + ":80\r\n";
  long readed_bytes = send_content_wifi(cmd_get);
  picked = pick_up_mycontent(readed_bytes);
  int lp_semicolon = 0;
  int max_semicolon = 15; // TODO
  for(int i=0;i 31) {
            tmp_string += String( byt );
          }
        }
      }
      delay(5); 
    }
    tmp_string = tmp_string.substring(0, tmp_string.indexOf(":wifi"));
    SSID_ = tmp_string.substring(0, tmp_string.indexOf(":"));
    PASSW = tmp_string.substring(tmp_string.indexOf(":") + 1);
    got_new_wifi_creds = true;
    SSID_.toCharArray(buft, BUFSIZE); 
    eeprom_write_32string(eeprom_addr_ssid, buft);
    PASSW.toCharArray(buft, BUFSIZE); 
    eeprom_write_32string(eeprom_addr_passwd, buft);
  }else{
    Serial.println("read wifi_creds from EEPROM");
    // get SSID from EEPROM:
    eeprom_read_32string(eeprom_addr_ssid, buft);
    SSID_ = String(buft);
    // get SSID from EEPROM:
    eeprom_read_32string(eeprom_addr_passwd, buft);
    PASSW = String(buft);
  }
  //reset and test if the module is ready
  reset_wifi_modul();
  looking_wifi_ip_mac();
  // get token from EEPROM:
  eeprom_read_32string(eeprom_addr_token, buft);
  token = String(buft); // "for_test_cntcXBWYJDQ0H6VwanN3Zrh"; // 
  Serial.println("Token read from EEPROM: " + token + " (length: " + token.length() + " )" );
  delay(1000);
  connect_weather_server();
  // is token from eeprom?
  if( token.length() < 1) {
    Serial.println("empty EEPROM token");
    // ask server (server should be set to allow to);
    token = token_from_mac();
  }else{
    // check is right token (try *3)
    boolean token_works = false;
    int coundtry = 10;
    while(!token_works && coundtry > 0) {
      int lp_semicolon = get_request_weather();
      if (lp_semicolon > 8) {
        token_works = true;
      }else{
        coundtry--;
        delay(10000);
      }
    }
    if( !token_works ) {
      Serial.println("not weather recided, try * 10");
      token = token_from_mac();
    }else{
      Serial.println("request with token is right!");
    }
  }
}

//------------------------------------------------------------------------------------------

void loop() {

  Serial.println("================== LOOP ===================");
  delay(500);
  String piece = "";
  String eof = "\r\n";
  // Turn on the display:
  lcd.display();
  connect_weather_server();
  int lp_semicolon = get_request_weather();
  if (lp_semicolon > 1) {
    ss_now_city2 = "";
    ss_forecast = "";
    int prev_pos = 0;
    for(int i=0;i<15;i++) {
      if(colon_divided_fields[i] > 0) {
        piece = picked.substring(prev_pos, colon_divided_fields[i]);
        if (i == 0) {
          ss_now_city2 += piece + " ";
        }
        if (i == 1) {
          ss_now_city2 += piece + " ";
        }
        if (i == 8) {
          ss_now_city2 += piece;
          failed_attempts = 0;
        }
        if (i == 9) {
          ss_forecast = "wkrotce: " + piece;
          failed_attempts = 0;
        }
        // all recived fields:
        if ( piece.length() > 0 ) {
          Serial.println( String(i) + " --> " + piece );
        }
        prev_pos = colon_divided_fields[i] + 1;
      }
    }
  }else{
    failed_attempts++;
  }
  if(failed_attempts < 5 && (ss_now_city2.length() > 1 || ss_forecast.length() > 1)){
    int count = 3;
    while(count-- > 0) {
      show_it(ss_now_city2);
      show_it(ss_forecast);
    }
  } else{
    show_it("error connection...");
  }
}
lcd_wifi_get_sote5.inoview rawview file on GitHub

Powyższy program, napisany na Arduino Mega z podłączonym wyświetlaczem LCD 40 * 4:

pobiera z EEPROM dane do połączenia z WiFi użytkownik i hasło, łączy się z routerem,
następnie pobiera z EEPROM token (może też być bez tokena) i łączy się z serweem pogodowym w sposób opisany wcześniej, pobierając pogodę, następnie wyświetla ją na LCD i po około minucie łączy się znów.
Dzięki temu wyświetla aktualną pogodę.
Jeśli nie uda się połączyć i otrzymać pogody – wykonuje kilka prób, wyświetla adres MAC i karty sieciowej WiFi i informuje, że łączy się z serwerem pytając o token.
Jeśli nie uda się połączenie z WiFi to zima…

Ale używając prostego Pythonowego skryptu…

# coding=utf-8

import serial

wifi_ssid = "WikS_mobileH"
wifi_passwd = "***"  # TODO put here your WiFi password

usb_as_serial = '/dev/ttyACM0'

ser = serial.Serial(usb_as_serial, 115200, timeout=2)
ser.flushInput()
ser.flushOutput()

while True:
    data_raw = ser.readline()
    if len(data_raw) > 0:
        print(data_raw)
        if "wifi credientals" in data_raw:
            toprint = "wifi:" + wifi_ssid + ":" + wifi_passwd + ":wifi"
            print toprint
            ser.write(toprint)

# ---
# run from Terminal in this way (as user allowed to use mentioned serial-port):
# $ python run.py

…przekażemy dane do połączenia WiFi – usera i hasło.

Aby sprawdzić co można uzyskać dla tokena w pełniejszej nieco formie…

Radości!
WikS

Zamiana kierunku wiatru wyrażonego w stopniach, na opisanego ‚ładniej’ -w nieco nawigacyjny sposób, mogłaby być w Pythonie wykonana następująco:

def wind_direction2descr_old(wind_dir):
"""
po prostu zmieniam wartość kątową na opisową kierunku wiatru
:param wind_dir: liczbowa wartość w stopniach
:return: unistring 1 lub dwie literki
"""
wind_dir_descr = u''
if wind_dir is not None and isinstance(wind_dir, (int, long, float)):
wind_dir = round(wind_dir % 360, 0)
if wind_dir < 22.5 or wind_dir >= (360 - 22.5):
wind_dir_descr = u'N'
dir_wokolo = 45
if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5):
wind_dir_descr = u'NE'
dir_wokolo = 90
if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5):
wind_dir_descr = u'E'
dir_wokolo = 135
if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5):
wind_dir_descr = u'SE'
dir_wokolo = 180
if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5):
wind_dir_descr = u'S'
dir_wokolo = 225
if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5):
wind_dir_descr = u'SW'
dir_wokolo = 270
if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5):
wind_dir_descr = u'W'
dir_wokolo = 315
if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5):
wind_dir_descr = u'NW'
return wind_dir_descr

Czyli okrąg-tort – 360 stopni umownie podzielony na 8 równych kawałków, ten na górze N – jak północ, na dole S – południe, po bokach E i W oraz pomiędzy nimi: NE SE SW NW. Osiem kawałków razem, licząc od góry – zgodnie ze wskazówkami zegara da nam: ‚N’ ‚NE’ ‚E’ ‚SE’ ‚S’ ‚SW’ ‚W’ ‚NW’.
Gdy 360 stopni na 8 otrzymamy 45, tak więc dla 0 stopni ( +/- połowa 45 = 22.5 stopnia ), czyli od -22.5 do +22.5 daje N , 45 (+/- 22.5) czyli 22.5 do 67.5 daje NE itd.
Funkcja powyżej w ten właśnie sposób zamienia wartość kąta na jego nawigacyjny opis kierunku.

Wcześniej sprawdzamy, czy wejściowa dana istnieje i jest liczbą – aby uniknąć błędów, jeśli jest poza zakresem 0 – 360 stopni to przywracamy ją do tego zakresu, zaokrąglamy i porównujemy aby określić opis słowny.

Można to też zrobić tak:

def wind_direction2descr(wind_dir):
"""
po prostu zmieniam wartość kątową na opisową kierunku wiatru
:param wind_dir: liczbowa wartość w stopniach
:return: unistring 1 lub dwie literki
"""
wind_dir_descr = u''
if wind_dir is not None and isinstance(wind_dir, (int, long, float)):
index = int(((float(wind_dir) + 22.5) % 360) / 45) % 8
wind_dir_descr = [u'N', u'NE', u'E', u'SE', u'S', u'SW', u'W', u'NW', ][index]
return wind_dir_descr

…czyli przeliczyć wind_dir na liczbę 0…7, która będzie indexem tablicy oznaczeń kierunków i da nam oczekiwany wynik.

Zaraz, zaraz, jak sprawdzić, czy wynik będzie oczekiwany?
Może przy pomocy testu?

wind_dir = 0
wind_dir_descr = wind_direction2descr(wind_dir)
print wind_dir, '-->', wind_dir_descr

…można to sprawdzać na piechotę oczywiście, można też napisać ładny Python’owy test.

Na piechotę:

Test:

# -*- coding: utf-8 -*-

# TODO reload utf-8
import sys
reload(sys)
sys.setdefaultencoding("utf-8")

from datetime import datetime

import describe_weather

try:
    import unittest2 as unittest
except ImportError:
    import unittest


class DescribeWeatherTest(unittest.TestCase):

    def test_wind_direction2descr(self):

        wind_dir_test_list = [
            [0, u'N'],
            [360, u'N'],
            [22.4, u'N'],
            [337.6, u'N'],

            [45 - 22.4, u'NE'],
            [45 + 22.4, u'NE'],
            [90 - 22.4, u'E'],
            [90 + 22.4, u'E'],
            [135 - 22.4, u'SE'],
            [135 + 22.4, u'SE'],
            [180 - 22.4, u'S'],
            [180 + 22.4, u'S'],
            [225 - 22.4, u'SW'],
            [225 + 22.4, u'SW'],
            [270 - 22.4, u'W'],
            [270 + 22.4, u'W'],
            [315 - 22.4, u'NW'],
            [315 + 22.4, u'NW'],

            [None, u''],
        ]

        for test_case in wind_dir_test_list:
            wind_dir = test_case[0]
            expected = test_case[1]
            res = describe_weather.wind_direction2descr(wind_dir)
            self.assertIsInstance(res, unicode, u'nie jest UNICODE')
            self.assertEqual(res, expected, u'oczekiwano innej wartości: ' + unicode(expected)
                             + u' otrzymano: ' + unicode(res) + u' dla kierunku: ' + unicode(wind_dir))
        return None

Od początku:

# -*- coding: utf-8 -*-

– sposób kodowania znaków użyty tutaj, oraz

import sys
reload(sys)
sys.setdefaultencoding("utf-8")

– sposób kodowania użyty w wytworzonym pliku wynikowym testu,

import describe_weather

– pobieram plik, w którym zawarta jest opisana wyżej funkcja,

import unittest

– pobieram pliki wykonujące tworzone testy, tzw testy jednostkowe lub ‚unit-testy’

Na bazie pobranych unit-testów tworzę klasę:

class DescribeWeatherTest(unittest.TestCase):

a w niej metodę / funkcję wykonującą test na opisanej powyżej mojej funkcji zamiany stopni na opis kierunku wiatru:

def test_wind_direction2descr(self):

nazwa klasy tworzona jest w sposób CamelCasing, czyli wielbłądzi – ma garby (wielkie litery) jako rozdzielenie wyrazów i Test w nazwie (np. ‚OpiszMojTest’ albo ‚OpiszJakisDziwnyNieModTest’…), nazwa metody – małymi literami podkreślnik jako rozdzielenie i na końcu ‚_test’.
Taka konwencja pozwala łatwiej zrozumieć …niezrozumiałe.
Co wewnątrz testu?
Mam do przetestowania funkcje, chcę jej zadawać wartości kąta i sprawdzać, czy otrzymany wynik jest zgodny z oczekiwaniami. Np jeśli zadam 0 (zero), chciałbym uzyskać ‚N’.
Zapiszmy to jako jeden przypadeczek testowy:
[0, u’N’]
(literka u oznacza kodowanie jako unicode, choć tutaj nie ma większego znaczenia…)

jeśli zebrać przypadeczki razem i ułożyć z nich listę otrzymamy:

wind_dir_test_list = [
    [0, u'N'],
    [360, u'N'],
    [22.4, u'N'],
    [337.6, u'N'],

    [45 - 22.4, u'NE'],
    [45 + 22.4, u'NE'],
    [90 - 22.4, u'E'],
    [90 + 22.4, u'E'],
    [135 - 22.4, u'SE'],
    [135 + 22.4, u'SE'],
    [180 - 22.4, u'S'],
    [180 + 22.4, u'S'],
    [225 - 22.4, u'SW'],
    [225 + 22.4, u'SW'],
    [270 - 22.4, u'W'],
    [270 + 22.4, u'W'],
    [315 - 22.4, u'NW'],
    [315 + 22.4, u'NW'],

    [None, u''],
]

…gdzie pierwszy element to zadawana wartość, drugi – oczekiwana odpowiedź.

Jak testujemy?

for test_case in wind_dir_test_list:
    wind_dir = test_case[0]
    expected = test_case[1]
    res = describe_weather.wind_direction2descr(wind_dir)
    self.assertIsInstance(res, unicode, u'nie jest UNICODE')
    self.assertEqual(res, expected, u'oczekiwano innej wartości: ' + unicode(expected)
                     + u' otrzymano: ' + unicode(res) + u' dla kierunku: ' + unicode(wind_dir))
return None

 

Pętla pobierająca jeden przypadeczek do ‚test_case’, gdzie ‚test_case[0]’ to zadawana wartość, a test_case[1] – oczekiwany wynik,

… wykonanie funkcji (dwa człony nazwy – pierwszy to nazwa zaimportowanego pliku ‚describe_weather’, drugi – ‚wind_direction2descr’ -nazwa funkcji oraz w nawiasach dana wejściowa )
wynik zwracany do zmiennej ‚res’ – jak resource (zasoby) oraz porównywana -to już narzędzia unit-testów:

assertIsInstance – czy jest typu unicode (bo tylko taki akceptujemy zgodnie z założeniami),
oraz:
assertEqual – czy to co uzyskano ‚res’ jest identyczne (equal -> równe) z oczekiwamym ‚expected’.

W obu ‚assert`ach’ sprawdzających, ostatnią wartością jest komunikat tekstowy, który pojawia się gdy wynik jest inny niż oczekiwano.

 

Na końcu, aby wykonać wszystkie testy:

if __name__ == '__main__':

    # --- jeden sposób:
    # unittest.main()
    # --- drugi sposób (chyba ładniejszy no i dla Jenkinsa):
    import xmlrunner
    unittest.main(testRunner=xmlrunner.XMLTestRunner(output='test-reports'))

…i wykonanie (poprzez run dla PyCharm).

Narzędzie Unittest wygeneruje raport:

Running tests...
----------------------------------------------------------------------
..........................
----------------------------------------------------------------------
Ran 26 tests in 0.003s

OK

Generating XML reports...

…także do pliku:

<?xml version="1.0" ?>
<testsuite errors="0" failures="0" name="DescribeWeatherTest-20180423140707" tests="26" time="0.003">
   <testcase classname="DescribeWeatherTest" name="test_describe_weather_teraz" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_for_effect_id_find_hours_ranges" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_integer_to_effect_description" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_cisnienie" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_cisnienie_doby" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_dodatkowo_efekty_doby" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_dodatkowo_efekty_prognozy" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_prognoze_doby" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_temperature_doby" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_temperature_prognozy" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_wiatr_doby" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_wiatr_prognoze" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_wilgotnosc_popoludnia" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_zachmurzenie_doby" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_zachmurzenie_popoludnia" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opracuj_pobrana_prognoze_max" time="0.001"/>
   <testcase classname="DescribeWeatherTest" name="test_opracuj_pobrana_prognoze_najblizsza" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_semi_zachmurzenie_percent2_octascale" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_special" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_temperatura_ze_znakiem" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_wind_boufort2descr_pl" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_wind_direction2descr" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_wind_speed_mps_2boufort" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_zachmurzenie_percent2_octascale" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_zamien_tylko_interesujace_efekty_atmosferyczne_na_opis" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_zapamietaj_okres_od_do" time="0.000"/>
   <system-out>
<![CDATA[]]>   </system-out>
   <system-err>
<![CDATA[]]>   </system-err>
</testsuite>

Opis unittestów można znaleźć tutaj: https://docs.python.org/2/library/unittest.html

Co do wiatru, mamy jeszcze jego prędkość, można ją wyrażać w stopniach Bouforta, opisowo a także zwyczajnie – w metrach na sekundę…

Zamiana prędkości na stopnie Bouforta – w zależności od zakresu – bułka z masłem,

zaś stopnie Bouforta (z pewnością pamiętacie że zapisujemy inaczej niż stopnie Celsjusza – mianowicie bez znaczka stopni, tylko samo B) na opis, to już kiełbaska na tym maśle 🙂 :

def wind_boufort2descr_pl(wind_boufort):
    """
    zamień stopnie bouforta na opis
    :param wind_boufort: liczba całkowita 0...12
    :return: unistring-opis
    """
    wind_descr = u''
    l_w_d = [u'cisza',
             u'powiew',
             u'słaby wiatr',
             u'łagodny wiatr',
             u'umiarkowany wiatr',
             u'dość silny wiatr',
             u'silny wiatr',
             u'bardzo silny wiatr',
             u'wicher',
             u'silny sztorm',
             u'bardzo silny sztorm',
             u'gwałtowny sztorm',
             u'huragan']
    if wind_boufort is not None and isinstance(wind_boufort, (int, long, float)) and 0 <= wind_boufort <= 12:
        wind_descr = l_w_d[wind_boufort]
    return wind_descr

…oczywiście jakiś test do tej bułki.

http://pogoda.wiks.eu

Radości!

WikS