Gmail

Tworzenie konta dla mailingu

1. Google Gmail

Przeglądarka, adres: https://www.google.com przycisk Zaloguj się

wybieramy utwórz konto

imie, nazwisko, nazwa użytkownika, hasło…

potrzebny będzie numer telefonu do weryfikacji konta, otrzymamy SMS podobny do:

kod przepisujemy i zatwierdzamy weryfikuj

 

możemy podać pomocniczy email, podajemy datę urodzenia i płeć (jeśli znamy)

zgody i utworzenie konta:

 

potrzeba jeszcze zatwierdzić zgodę na używanie tzw mniej bezpiecznych aplikacji:

adres myaccount.google.com/lesssecureapps

i w zasadzie mamy już serwer, pozostała konfiguracja w settings.py Django:

# --- dla Google: ---
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'  
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = 'pogodawikseu@gmail.com'  # \ to zmieniam
EMAIL_HOST_PASSWORD = '--pass--'            # / ważne, aby był ustawiony dostęp mniej bezpiecznych apek

2. mail z Linuxpl.com

Gdy próbowałęm wysłać ze swojego konta linuxowego na utworzone właśnie konto wp.pl, przy adresie z app/views.py nadawcy innym niż ten prawdziwy to nie przeszedł email – nie został odebrany, choć Django nie pokazało żadnych błędów.

Wysłanie gdy te adresy są zgodne – WP odbiera poprawnie:

konfiguracja w settings.py:

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST = 'mail.pogoda.wiks.eu'
EMAIL_HOST_USER = 'pogoda@pogoda.wiks.eu'
EMAIL_HOST_PASSWORD = '---pass---'

do celów developerskich zaś w Django w roli ćwiczebnego serwera email możemy użyć:

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

Radości!

WikS

Django pozwala w przyjemny sposób tworzyć treści webowe, które niemal natychmiast możemy podejrzeć w oknie przeglądarki. Gdy tworzymy, mając ustawienia w settings.py jako

DEBUG = True
po wykonaniu:

$ python manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).
August 07, 2018 - 16:40:12
Django version 1.11.14, using settings 'duser.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

wynik jest widoczny pod lokalnym adresem przeglądarki 127.0.0.1 / localhost i na porcie domyślnym 8000.

Jednak gdy zmienimy DEBUG na False, czyli tryb produkcyjny, i uruchomimy ‚runserver’ -… uzyskany wynik ędzie zazwyczaj odbiegał od wcześniejszych oczekiwań… Będzie pozbawiony plików statycznych – JavaScript, CSS oraz obrazów.

Poniżej opiszę swoje doświadczenia z uruchamiania Djangowej apki pod Apache2/Django (WSGI) oraz za pomocą uWSGI (na serwerze Linux). Opiszę to tak jak sam (nie)rozumiem 🙂

Różnice będą dotyczyć plików:

settings.py, urls.py, plików konfiguracji Apache2 / sposobu uruchamiania w uWSGI.

A więc do dzieła.

Apache2/WSGI

na serwerze z Apache2 pod Django (z takim miałem najwięcej doświadczeń), każda zmiana w plikach Django (prócz plików statycznych i templatek) będzie wymagała restartu serwera:

service apache2 restart

wynika stąd, że uruchamianiem zajmuje się server Apache2 i korzysta w nim z pliku konfiguracyjnego przygotowanego w

/etc/apache2/conf-available

Apache musi mieć zainstalowany:

sudo apt-get install libapache2-mod-wsgi

Jak przygotować plik konfiguracyjny dla Apache2?

sudo nano /etc/apache2/sites-available/wsgi_manage.conf

i wpisujemy:

<VirtualHost *:80>
ServerName pogoda.wiks.eu

ServerAdmin webmaster@localhost

ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined

# /manage/static/admin /img/icon-addlink.svg
Alias /manage/static/admin /usr/local/lib/python2.7/dist-packages/django/contrib/admin/static/admin
<Directory /usr/local/lib/python2.7/dist-packages/django/contrib/admin/static/admin>
Require all granted
</Directory>

Alias /manage/static /home/wiks/projects/duser/app/static
<Directory /home/wiks/projects/duser/app/static>
Require all granted
</Directory>

<Directory /home/wiks/projects/duser>
<Files wsgi.py>
Require all granted
</Files>
</Directory>

WSGIDaemonProcess proj_manage python-path=/usr/local/lib/python2.7/dist-packages
WSGIProcessGroup proj_manage
WSGIScriptAlias /manage /home/wiks/projects/duser/duser/wsgi.py

</VirtualHost>

W moim wypadku skorzystałem z edytora nano dla Debiana.

Jak rozumiem zapisy w tym pliku?

<VirtualHost *:80>  i ServerName pogoda.wiks.eu

czyli wykonuj/reaguj dla portu 80 (domyślny http) oraz serwera o nazwie ‚pogoda.wiks.eu’ – może tu być ‚localhost’ gdy uruchamiamy na swojej lokalnej maszynie,

ServerAdmin webmaster@localhost
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined

informujemy Apache gdzie wysłać maila z problemami natury administracyjnej, oraz gdzie umieszczać logi serwera Apache dotyczące działań na tej stronie,

# /manage/static/admin /img/icon-addlink.svg

powyższa linijka jest komentarzem, jak wszystko co w tym pliku zaczyna się od #.

Tego powyższego linku używałem, aby sprawdzić w którym miejscu są dostępne pliki Statyczne Admina Django na serwerze, aby wpisać to w następną linijkę, tj:

Alias /manage/static/admin /usr/local/lib/python2.7/dist-packages/django/contrib/admin/static/admin
<Directory /usr/local/lib/python2.7/dist-packages/django/contrib/admin/static/admin>
Require all granted
</Directory>

…czyli jeśli w ścieżce adresu początek to ‚/manage/static/admin’ należy odwzorować resztę na pliki dostępne pod lokalnym katalogiem ‚/usr/local/lib/python2.7/dist-packages/django/contrib/admin/static/admin’

A sprawdziłem to używając wspomnianej wcześniej – zakomentowanej ścieżki, dla jednego z plików graficznych Django Admina.

Alias /manage/static /home/wiks/Dokumenty/projects/weather/LINUXserver/duser/duser/app/static
<Directory /home/wiks/Dokumenty/projects/weather/LINUXserver/duser/duser/app/static>
Require all granted
</Directory>

Dostęp do plików statycznych naszej apki – tam gdzie ona jest umieszczona.

<Directory /home/wiks/Dokumenty/projects/weather/LINUXserver/duser/duser>
<Files wsgi.py>
Require all granted
</Files>
</Directory>

Ścieżka do pliku wsgi.py Django (o nim za chwilę), oraz:

WSGIDaemonProcess proj_manage python-path=/usr/local/lib/python2.7/dist-packages
WSGIProcessGroup proj_manage
WSGIScriptAlias /manage /home/wiks/Dokumenty/projects/weather/LINUXserver/duser/duser/duser/wsgi.py

czyli ustawienie ścieżki Pythona i użytkownika/grupy mającej do niej dostęp, a także wykonywanie/użycie Pythonowego wsgi.py dla aliasu ścieżki ‚/manage’

Dzięki ustaleniu aliasu ‚/manage’ a także innych, możemy pod jednym serwerem Apache uruchomić tradycyjne strony HTML, PHP oraz wiele aplikacji Pythonowych. Bosko.

O naszym aliasie ‚/manage’ musimy pamiętać, przy ustawieniach w pliku Djangowym settings.py

U mnie znalazł się on w settings.py tutaj:

BASE_URL = '/manage'  # albo '/fikusny/url'  # albo '/bardzo/fikusny/url'
# wszystkie jednak muszą się zgadzać z tym co w pliku konfiguracyjnym Apache.

STATIC_URL = BASE_URL + ‚/static/’
# co przekłada się na ścieżki w templatkach, tam gdzie używamy obrazów i plików statycznych:

<link rel="stylesheet" type="text/css" href="{% static 'css/mystyle.css' %}">

a także tam, gdzie używamy w templatkach

<a href="{% url 'index' %}...

odnośników do ścieżek z urls.py .

chmod x+ /home/wiks/Dokumenty/projects/weather/LINUXserver/duser/duser/duser/wsgi.py

Oczywiście należy zadbać, aby Apache2 miał dostęp i odpowiednie uprawnienia do wszystkich zdefiniowanych dla niego ścieżek, plików statycznych folderów logu (logów Apache i logów Django jeśli są) oraz odpowiednie pliki mogły były wykonywalne. W przypadku posługiwania się bazą SQLite3 uprawnienia muszą być przyznane także dla folderu zawierającego plik bazy (SQLite tworzy sobie pliki tymczasowe, jeśli Apache nie będzie miał uprawnień do zapisu w folderze obejmującym bazę – wygeneruje wyjątek):

sudo chown :www-data ~/duser/db.sqlite3
sudo chown :www-data ~/duser

‚Zezwalamy’ na użycie konfiguracji – utworzy to link do wpisanego pliku w katalogu sites-enabled:

a2enconf wsgi_manage

i restart Apache2 (należy go wykonywać po każdej zmianie w plikach Django -prócz statycznych i templatek)

sudo service apache2 restart

Plik urls.py – bez zmian w stos do Debug: True (fragment z paska adresu ‚/manage’ zabiera sobie Apache2):

# duser/urls.py
from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url('^', include('app.urls')),
]

uWSGI

Uruchamiając apkę Django na serwerze zdalnym Linuxpl.com (jaki ten świat mały, apka pisana w Świnoujściu, a Linuxcom.pl w Rzeszowie – blisko kochanych Bieszczadów) jako pogoda.wiks.eu/manage nie miałem oczywiście wpływu na serwer, musiałem ją uruchomić przez sugerowany uWSGI, co spowodowało jednak, że serwer ‚nie zabierał’ fragmentu ‚/manage’ z paska adresu (pod sub-domeną były też -głownie pliki PHP, HTML…) więc musiałem to zrobić w ‚urls.py’:

# duser/urls.py

url(r'^manage/static/admin/(?P<path>.*)$', 'django.views.static.serve', 
{'document_root': '/home/wiks/python/venv/proj_gpsmim/lib/python2.7/site-packages/django/contrib/admin/static/admin'}),

url(r'^manage/admin/', include(admin.site.urls)),
url(r'^manage/', include('app.urls')),

]

Dodatkowo, nie mogąc również poprzez wsgi serwera serwować plików statycznych, zrobiłem to w pliku urls.py powyżej, rzutując wszystko co jest za ‚manage/static/admin/’ w pasku adresu na folder przechowujący pliki statyczne Admina Django w moim virtualnym środowisku ~python/venv/proj_gpsmim (…tak, wcześniej poszukałem tego poprzez FTP -analogicznie jak w przypadku w WSGI na lokalnym własnym serwerze).

Pytanie co z plikami statycznymi apki?

Serwuje je poprzez urls.py wewnątrz app w taki sposób:

# coding=utf-8

from django.conf.urls import url, include
import views_cpl
from duser import settings

urlpatterns = [
url(r'^static/(?P<path>.*)$', 'django.views.static.serve', 
{'document_root': settings.STATIC_ROOT}),
[...]

Tutaj już mamy ‚zabrane’ ‚manage/’ ze ścieżki adresu (pierwszy plik urls.py tego dokonał), i oczywiście poprawnie trzeba ustawić STATIC_ROOT w settings.py:

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
STATIC_ROOT = BASE_DIR + "/app/static"

Oczywiście -jak wcześniej- warunkiem powodzenia jest ustawienie dostępu do plików oraz uprawnień do ich uruchamiania -tam gdzie to wymagane.

Komenda uruchamiająca apkę to w moim wypadku:

-uruchomienie w wirtualnym środowisku:

source /home/wiks/python/venv/proj_gpsmim/bin/activate

i uruchomienie:

nohup uwsgi --plugin http,python --http 127.0.0.1:8892 --chdir /home/wiks/python/django/pogoda/ --wsgi-file duser/wsgi.py --master --processes 1 --workers 1 --threads 1 --daemonize=/home/wiks/python/django/pogoda/logs/log.txt

czyli:

uruchom na porcie 8892,

katalog roboczy:

–chdir /home/wiks/python/django/pogoda/

plik wsgi.py (Djangowy):

–wsgi-file duser/wsgi.py

i miejsce na log servera:

–daemonize=/home/wiks/python/django/pogoda/logs/log.txt

Resztę magii wykonają poproszeni Panowie Administratorzy Linuxpl.com .

 

ładnie jest to opisane tutaj:

https://docs.djangoproject.com/

https://www.digitalocean.com/

https://uwsgi-docs.readthedocs.io

https://support.linuxpl.com

 

Odkryłem dziś coś wspaniałego. Udało mi się uruchomić kilka bardzo starych programów napisanych w ubiegłym wieku (chyba jeszcze) w Pascalu i Delphi pod Windows. I choć mam dwa systemy – WindowsXP, którego nie używam oraz Debian Linux, spróbowałem Wine…

Od początku.
Jak zainstalować i używać Wine?

Moja instalacja została wykonana na poczciwej 32bitowej maszynie, ale jeśli pracujecie na 64bitowej, należy zacząć od instalacji starszej architektury:

sudo dpkg --add-architecture i386 

* jak wspomniałem -ja pominąłem ten krok.

Pobieramy i instalujemy klucz:

wget -nc https://dl.winehq.org/wine-builds/Release.key
sudo apt-key add Release.key

następnie:

następnie włczamy dla Wine repozytorium opracowne dla Debiana:

sudo apt-add-repository https://dl.winehq.org/wine-builds/debian/

i instalujemy Wine:

sudo apt-get update
sudo apt-get install --install-recommends winehq-stable

Sposób w jaki uruchamiamy programy windowsowe (sprawdzałem dla napisanych pod WindowsXP i Windows95) – prawoklik i Wine Windows Program Loader:

…informuje, że brakuje jakiejś biblioteki, ale może ją załadować…

uruchamia nam pogram Delphi, tadam!:

mam pięknie działający program emulujący frazy poruszającego się odbiornika GPS, założonym kursem, prędkością i z wybraną ilością widocznych satelitów.

Sprawdzę dla programu obsługującego niegdyś skonstruowany przeze mnie emulator EPROM (pierwowzoru EEPROM. Pamięci te występowały wtedy oddzielnie i służyły do przechowywania programu…):

…no i jeszcze:

…napisany dla Solarisa (wzmianka tutaj, film tutaj) kalkulator położenia Słońca dla obserwatora w dowolnym miejscu na Ziemi…

 

Normalnie czad:

 

Radości!
WikS

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

run Python script from PHP <– PHP <– JavaScript (3)

Co do tej pory robiliśmy? Przesyłaliśmy dane w postaci nazwy miejscowości i jej współrzędnych …zaraz zaraz, a czy sama nazwa miejscowości nie wystarczy? Gdyby tak (rozmarzymy się…) mieś bazę wszystkich miejscowości i ich współrzędnych (w Polsce to około 103tyś sprecyzowanych miejsc, >4000 jednostek administracyjnych i prawie 400 powiatów i miast -opisuje to TERYT).

Gdyby mieć taką bazę i po prostu zapytać o współrzędne?

Można też zapytać o to Google -udostępnia taką usługę, – miejsce na osobny wpis -jak odpytać Googla z miejscowości aby zbudować sobie taką bazę…

Spróbujmy zrobić coś takiego, aby wpisując nazwę miejscowości otrzymać jej współrzędne.

Na Django w wersji podstawowej może to wyglądać tak:

http://django.wiks.eu/lookfor

Działanie jest takie, że po każdej naszej akcji – wpisaniu kolejnego znaku nazwy z klawiatury, JavaScript wzmocniona AngularJS odpytuje server fragmentem nazwy i uaktualnia zawartość wyświetlanej strony. Zobaczcie:

Na powyższej stronie – korzystającej z własnej bazy danych, w której są wszystkie 103tyś miejsc TERYT, a około 50tyś z nich ma przypisaną pozycję geograficzną (zweryfikowane tylko miasta (ok. 400szt) i powiaty).

Ale ok. Skupmy się na AngularJS. – frameworku napisanym w JavaScript.

Jak wygląda nasz schemat?

AngularJS – czyli program napisany w JavaScript przez programistów Google, działa na naszym lokalnym komputerze i monitoruje zmiany. Każde wprowadzenie kolejnego znaku z klawiatury do formularza wywołuje reakcje -Angular wysyła pytanie do serwera z nowymi wartościami, serwer odpowiada, -zapewne na podstawie zawartości bazy danych, odpowiedź wraca do Angular i jest odpowiednio dysponowana -gdy wymaga tego – modyfikuje zawartość HTML strony i przeglądarka na komputerze wyświetla nam stronę inaczej. W opisywanym przykładzie początkowo informuje nas o ilości pasujących nazw, następnie – gdy jest ich już dość mało – wyświetla je. Na końcu wybieramy jedną i ukazuje się np. mapka.

W projekcie którym się bawimy, chcemy uzyskać współrzędne geograficzne miejscowości o wprowadzanej nazwie.

To co trzeba w takim wypadku zrobić to:

1. utworzyć formularze – widoki w HTML,

2. oprogramować to po stronie Angular/JavaScript – aby Angular wiedział, czego oczekujemy,

3. oprogramować serwer, aby na zadawane przez Angular pytania odpowiadał -również w sposób jaki chcemy.

Serwer może być napisany w czymkolwiek, nawet prosty skrypt PHP na lokalnym komputerze może dostarczyć sporo frajdy.

run Python script from PHP (2)

Pomyślałem sobie, skoro umiem testować to i wywołać ze strony www poprzez PHP, to może pójść krok dalej? JavaScript?

Da to większe możliwości operacyjności stroną, dzięki temu JavaScript – strona www zyska moc Pythona. No oczywiście – można to zrobić poprzez Django. Ale nie zawsze mamy server Django, czasem po prostu PHP/Apache, który można w ten sposób nomen-omen ubogacić…

Skoro wiem już jak to hula w środku, usunąłem wyświetlanie komentarzy z Pythona i wewnętrznego PHP.

skrypt wewnętrzny Pythona mógłby wyglądać tak:

#!/usr/bin/env python
# coding=utf-8

import os, sys
from datetime import datetime
from time import sleep
import logging.handlers

log_main = logging.getLogger('main')
formatter = logging.Formatter('%(asctime)s.%(msecs)03d  %(levelname)-8s  %(name)-15s  %(message)s',
                       datefmt='%Y-%m-%d %H:%M:%S')
file_handler = logging.handlers.RotatingFileHandler(os.path.join(
                                          os.path.dirname(os.path.abspath(__file__)),
                                          'log.txt'
                                       ),
                                       maxBytes=1000000,
                                       backupCount=3)
file_handler.setFormatter(formatter)
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(formatter)
log_main.setLevel(logging.DEBUG)
log_main.addHandler(file_handler)
log_main.addHandler(stream_handler)

log_main.info(u'start ' + unicode(datetime.now()) + u' ...')

args = {}
if sys.argv:
    for pair in sys.argv:
        one = pair.split('=', 1)
        if one and len(one) == 2 and one[0]:
            args[one[0].decode('utf-8')] = one[1].decode('utf-8')
args[u'pdt'] = unicode(datetime.now())
args[u'p3g'] = u'Pythona 3 grosze'
sleep(3)
print(args)

log_main.info(u'stop ' + unicode(datetime.now()) + u' ...')

Wprowadzone zmiany to:

– dołożony log w lokalnym pliku <code>log.txt</code> dzięki czemu wiemy co się wewnątrz dzieje, -tutaj jedynie wpisuję czasy startu i stopu, ale można cokolwiek 🙂

– Python dokłada swoje dane do otrzymanych data wejściowych -tutaj pola ‚p3g’ i ‚pdt’

– zakładam, że procedura Pythona będzie trwała chwilę, zanim zwróci wynik do JavyScript (Python –> PHP –> JavaScript) -tutaj 3 sekundy stąd dodane sleep(3)

Tworzenie logu, wiąże się z koniecznością dostępu do pliku logu (folderu w którym jest/będzie utworzony plik logu) – trzeba więc zadbać o uprawnienia do tego katalogu ( chown/chmod )

Właściwie, aby było to (jeszcze) ładniej, obuduję to w klasę:

#!/usr/bin/env python
# coding=utf-8

import os, sys
from datetime import datetime
from time import sleep
import logging.handlers


class PyMain:
    """

    """

    def __init__(self):
        """

        """

        self.log_main = logging.getLogger('main')
        formatter = logging.Formatter('%(asctime)s.%(msecs)03d  %(levelname)-8s  %(name)-15s  %(message)s',
                                      datefmt='%Y-%m-%d %H:%M:%S')
        file_handler = logging.handlers.RotatingFileHandler(os.path.join(
            os.path.dirname(os.path.abspath(__file__)),
            'log.txt'
        ),
            maxBytes=1000000,
            backupCount=3)
        file_handler.setFormatter(formatter)
        stream_handler = logging.StreamHandler()
        stream_handler.setFormatter(formatter)
        self.log_main.setLevel(logging.DEBUG)
        self.log_main.addHandler(file_handler)
        self.log_main.addHandler(stream_handler)
        self.log_main.info(u'init ' + unicode(datetime.now()))

    def clue(self):
        """
        return dict inomming plus some new fields
        :return:
        """

        self.log_main.info(u'start ' + unicode(datetime.now()) + u' ...')

        args = {}
        if sys.argv:
            for pair in sys.argv:
                one = pair.split('=', 1)
                if one and len(one) == 2 and one[0]:
                    args[one[0].decode('utf-8')] = one[1].decode('utf-8')
        args[u'pdt'] = unicode(datetime.now())
        args[u'p3g'] = u'Pythona 3 grosze'
        sleep(3)
        self.log_main.info(u'stop ' + unicode(datetime.now()) + u' ...')
        # print(args)
        return args


if __name__ == "__main__":
    c = PyMain()
    print(c.clue())

Musiałem zmienić z print(args) na return args, print zaś dopiero po wyjściu z metody.

Teraz pora na PHP odpalające …i też odrobina zmian:

<?php
header("Access-Control-Allow-Origin: *");

$log = 'log.txt';
$tolog = 'start';
file_put_contents($log, (new DateTime())->format('Y-m-d H:i:s').' '.$tolog.PHP_EOL, FILE_APPEND | LOCK_EX);

$python_filename = 'php_fired2.py';
$python_dir_path = '/home/wiks/Dokumenty/projects/weather/python2';

$raw_data = file_get_contents('php://input'); 
$data = json_decode( $raw_data, true ); 
$cli_data = ''; 
if($data) { 
    foreach($data as $key => $value) { 
        $cli_data .= '"'.$key.'='.$value.'" '; 
    } 
} 
$command = $python_dir_path.'/'.$python_filename.' '.$cli_data; 
$output_from_python = shell_exec($command); 
echo $output_from_python; 
$tolog = print_r($output_from_python, true); 
file_put_contents($log, (new DateTime())->format('Y-m-d H:i:s').' '.$tolog.PHP_EOL, FILE_APPEND | LOCK_EX); 
$tolog = 'stop'; 
file_put_contents($log, (new DateTime())->format('Y-m-d H:i:s').' '.$tolog.PHP_EOL, FILE_APPEND | LOCK_EX);

Zmiany:

– dodałem log zamiast echo, tak więc interesujące momenty, wartości i zachowania będą umieszczone w pliku log.txt;

– linijka header("Access-Control-Allow-Origin: *"); umożliwiająca dostęp skryptowi JavaScript do tego pliku. Wcześniej dostęp nie był potrzebny, bo usyskiwał go skrypt PHP będący na tym samym serwerze. Teraz dostęp uzyskuje JavaScript – działająca gdziekolwiek na komputerze użytkownika…

 

Właściwie 🙂 , aby było to (jeszcze) ładniej, obuduję to w klasę:

<?php
header("Access-Control-Allow-Origin: *");

class PhpFiredPython {
/**
 *
 */

/** ustala plik logu, zapisuje moment startu
 * ustala zmienne dla klasy
 */
public function __construct() {

    $this->filelog = 'log.txt';
    $this->log('>start');
    $this->python_filename = 'php_fired2.py';
    $this->python_dir_path = '/home/wiks/Dokumenty/projects/weather/python2';
}

/** prowadzi plik logu
 *
 * @param type $tolog
 */
private function log($tolog) {

    file_put_contents($this->filelog,
            (new DateTime())->format('Y-m-d H:i:s').' '.
            $tolog.
            PHP_EOL,
            FILE_APPEND | LOCK_EX);
}

/** wykonuje właściwą pracę - odpala skrypt Pythona
 *
 * @return type
 */
public function get_post_and_fire_python() {

    $raw_data = file_get_contents('php://input');
    $data = json_decode( $raw_data, true );
    $cli_data = '';
    if($data) {
        foreach($data as $key => $value) {
            $cli_data .= '"'.$key.'='.$value.'" ';
        }
    }
    $command = $this->python_dir_path.'/'.$this->python_filename.' '.$cli_data;
    $output_from_python = shell_exec($command);
    $this->log(print_r($output_from_python, true));
    $this->log('>stop');
    //echo $output_from_python;
    return $output_from_python;
    }

}

$c = new PhpFiredPython();
echo $c->get_post_and_fire_python();

Podobnie jak z Pythonem zamieniłem echo na return.

Do szczęścia tego etapu potrzeba jeszcze JavaScript. która to wszystko potrząśnie:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="resp">miejsce na odpowiedź</div>
<?php
require 'url_and_data.php';
$dataj = json_encode( $data );
?>
<script>
    var d1 = new Date();
    console.log('JS start: ' + d1.toLocaleTimeString());
    var xhr = new XMLHttpRequest();
    xhr.open('POST', '<?php echo $url; ?>', true);
    xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    xhr.onload = function () {
        var resp = this.responseText;
        console.log(resp);
        var element = document.getElementById("resp");
        if (resp) {
            element.innerHTML = resp;
            var d2 = new Date();
            console.log('JS stop: ' + d2.toLocaleTimeString());
        }
    };
    xhr.send('<?php echo $dataj; ?>');
</script>
</body>
</html>

Co my tu mamy:

Miejsce w HTML, do którego JS wpisze wynik: <div id="resp">miejsce na odpowiedź</div>, czyli DIV identyfikowany poprzez id=”resp” ,  oraz skrypt, który wywoła XMLHttpRequest metodą POST, na adres -nieładnie wdrukowany przez PHP: <?php echo $url; ?>  i wyśle tam dane w postaci JSON -również niełądnie wdrukowane przez PHP.

Po otrzymaniu zaś odpowiedzi (a będzie to czas który ustaliliśmy na 3 sekundy w skrypcie Pythona) w element DIV o id-„resp” wpisze otrzymaną odpowiedź. Ta sama odpowiedź pojawi się w konsoli przeglądarki (te miejsca, w których wpisujemy console.log(… ).

Na konsoli Firefoxa wygląda to podobnie do:

Albo nawet:

Gdzie ładnie widać co JS wysyła (dane formularza) i co otrzymuje (przedostatnia linijka).

Jak teraz wygląda nasz schemat?

A jak działa?

Otwieramy stronę PHP/HTML, przeglądarka tworzy element DIV z zawartością ‚miejsce na odpowiedź’.

Po wczytaniu skryptu JavaScript wykonuje go, czyli: wysyła dane JSON utworzone przez PHP na adres również utworzony przez PHP.

Dane trafiają do servera i wywołują skrypt PHP. Skrypt wywołuje skrypt Pythona, ten zaś po 3 skundach zwraca odpowiedź -też w formie JSON.

Odpowiedź trafia do PHP a następnie jest zwracana do JavaScript, który po jej odebraniu wykonuje
xhr.onload = function () { odszukuje element DOM o ID=”resp” i wpisuje otrzymaną odpowiedź do jego wnętrza jako HTML.

…to właściwie został mały kroczek do AngularJS, kiedyś to opiszę. Początek będzie zapewne wyglądał tak…

…run Python script from PHP

Kolejnym krokiem – chciałem sprawdzić to co wcześniej udało się osiągnąć, ale w ten sposób, aby miast schematu Python <–> PHP <–> Python użyć PHP <–> PHP <–> Python. Idąc dalej, można sobie wyobrazić, że to pierwsze PHP to strona z której wysyłamy zapytanie do (drugiego) PHP etc…

Rozbudowałem także część opisową, teraz schemat wygląda tak:

kod końcowego skryptu – Pythona:

Python wczytuje argumenty podane przez wywołujący program w linii komend, są to pary klucz=wartość, rozdzielone spacją, sys.argv odczytuje je jako listę, przy czym na pierwszym miejscu [0] występuje ścieżka pliku Pythona.
Dodałem też przedstawienie się versji Pythona (będzie to ważne przy testowaniu np. z venv) i moment startu i końca działania skryptu (wrócimy do tego później).

#!/usr/bin/env python
# coding=utf-8

import sys
from datetime import datetime

print 'Python: start: ' + str(datetime.now()) + '<br>'
args = {}
if sys.argv:
   for pair in sys.argv:
      #print pair
      #print '<br>'
      one = pair.split('=', 1)
      if one and len(one) == 2 and one[0]:
         #print one[0]
         #print '<br>'
         args[one[0].decode('utf-8')] = one[1].decode('utf-8')
print 'Python: Python-version: ' + str(sys.version) + '<br>'
print 'Python: RX-DATA:<br>'
print(args)
print '<br>Python: stop: ' + str(datetime.now()) + '<br>'
print 'Python: tested by: http://blog.wiks.eu'

wyjście Pythona może wyglądać np tak:

Python: start: 2018-05-12 09:10:49.836750
Python: Python-version: 2.7.9 (default, Aug 13 2016, 16:41:35) [GCC 4.9.2]
Python: RX-DATA:
{u'city_name': u'Cz\u0119stochowa', u'lat': u'50.7651978', u'lon': u'19.1587636'}
Python: stop: 2018-05-12 09:10:49.836967
Python: tested by: http://blog.wiks.eu 

kod (środkowego/)’kopiącego’ PHP:
Podobnie jak w Pythonie – przedstawiamy się versją i mierzymy czas.
Wyświetlamy także sposób w jaki wywołujemy Pythona i to co nam Python zwrócił.

<?php
/* PHP running Python
 */
echo 'PHP: PHPversion: '.phpversion().'<br>'."\n"; // 5.6.33-0+deb8u1
echo 'PHP: start: '.date("Y-m-d H:i:s.").gettimeofday()['usec'].'<br>'."\n";
$raw_data = file_get_contents('php://input');
echo 'PHP: RX RAW-DATA: '.gettype($raw_data).' --> '; // string
print_r($raw_data); // {"arg1": "tralala", "arg2": 123.45, "arg3": "unicode str ąśćęłóźć "}
echo "<br>"."\n";
$data = json_decode( $raw_data, true );
echo 'PHP: JSON DECODED: '.gettype($data).' --> '; // string
print_r( $data );
echo "<br>"."\n";
$cli_data = '';
if($data){
    foreach($data as $key => $value) {
        $cli_data .= '"'.$key.'='.$value.'" ';
    }
}
$python_dir_path = '/home/wiks/Dokumenty/projects/weather/python2';
$python_filename = 'php_fired.py';
$command = $python_dir_path.'/'.$python_filename.' '.$cli_data;
echo 'PHP: COMMAND-LINE for Python: '.$command."<br>"."\n";
$output_from_python = shell_exec($command);
echo 'PHP: RX from Python: <br>'."\n";
echo '--------<br>'."\n";
echo $output_from_python;
echo "\n".'<br>--------<br>'."\n";
echo 'PHP: stop: '.date("Y-m-d H:i:s.").gettimeofday()['usec'].'<br>'."\n";
echo 'PHP: tested by: http://blog.wiks.eu'."\n";
?>

kod (pierwszego) PHP/html:
PHP osadzony w kodzie HTML -nadaje się do odpalenia ze strony www. Sprawdziłem działanie na localhost i na zdalnym host -oba postawione na Apache2.

Pobieramy URL i dane do wysłania require 'url_and_data.php';, przedstawiamy się versją, zamieniamy array wejściową na JSON, wysyłamy – przy pomocy cURL /niże też bez cURL/, wyświetlamy to co odebrano. Voila:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>test PHP fired Python</title>
</head>
<body>
<?php
require 'url_and_data.php';
echo 'PHP-TEST: PHPversion: '.phpversion().'<br>';
echo 'PHP-TEST: ARRAY TO SEND: ';
print_r($data);
echo '<br>';
$dataj = json_encode( $data );
echo 'PHP-TEST: TX JSON_ENCODE --> ';
print_r($dataj);
echo ' --> TYPE: '.gettype($dataj).'<br>'; // string

$curl = curl_init($url);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, array("Content-type: application/json"));
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $dataj);
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ( $status != 200 ) {
    die("Error: call to URL $url failed with status $status, response $result, curl_error " . curl_error($curl) . ", curl_errno " . curl_errno($curl));
}
curl_close($curl);
print 'PHP-TEST: RX-result: (with curl:)<br>';

print '------------<br>';
print($result);
print '<br>------------<br>';
print 'PHP-TEST: tested by: http://blog.wiks.eu';
?>
</body>
</html>

Pod require 'url_and_data.php' kryją się definicje URL i DATA do przesłania, np:

<?php 
$url = 'http://localhost/run_python.php'; 
$data = array('city_name' => 'Częstochowa',
              'lat' => '50.7651978', 
              'lon' => '19.1587636'
              );        

w powyższych danych przykładowo – przesyłam nazwę miejscowości i współrzędne geograficzne…

Można to oczywiście też wykonać bez cURL`a:

echo ' --> TYPE: '.gettype($dataj).'<br>'; // string

// use key 'http' even if you send the request to https://...
$options = array(
    'http' => array(
        'header'  => "Content-type: application/x-www-form-urlencoded\r\n",
        'method'  => 'POST',
        'content' => $dataj 
    )
);
$context  = stream_context_create($options);
$result = file_get_contents($url, false, $context);
if ($result === FALSE) { // Handle error  
}
print 'PHP-TEST: RX-result: (curl-less:)<br>';

wynik działania:

PHP-TEST: PHPversion: 5.6.33-0+deb8u1
PHP-TEST: ARRAY TO SEND: Array ( [city_name] => Częstochowa [lat] => 50.7651978 [lon] => 19.1587636 )
PHP-TEST: TX JSON_ENCODE --> {"city_name":"Cz\u0119stochowa","lat":"50.7651978","lon":"19.1587636"} --> TYPE: string
PHP-TEST: RX-result: (with curl:)
------------
PHP: PHPversion: 5.6.33-0+deb8u1
PHP: start: 2018-05-12 09:10:49.818623
PHP: RX RAW-DATA: string --> {"city_name":"Cz\u0119stochowa","lat":"50.7651978","lon":"19.1587636"}
PHP: JSON DECODED: array --> Array ( [city_name] => Częstochowa [lat] => 50.7651978 [lon] => 19.1587636 )
PHP: COMMAND-LINE for Python: /home/wiks/Dokumenty/projects/JS_PHP_Python/clue/python/php_fired.py "city_name=Częstochowa" "lat=50.7651978" "lon=19.1587636"
PHP: RX from Python:
--------
Python: start: 2018-05-12 09:10:49.836750
Python: Python-version: 2.7.9 (default, Aug 13 2016, 16:41:35) [GCC 4.9.2]
Python: RX-DATA:
{u'city_name': u'Cz\u0119stochowa', u'lat': u'50.7651978', u'lon': u'19.1587636'}
Python: stop: 2018-05-12 09:10:49.836967
Python: tested by: http://blog.wiks.eu
--------
PHP: stop: 2018-05-12 09:10:49.838728
PHP: tested by: http://blog.wiks.eu
------------
PHP-TEST: tested by: http://blog.wiks.eu

…a gdyby tak zrobić to z JavaScript?