Raspberry Pi

41) Dálková komunikace s RasPi přes webové rozhraní nebo přes SMS

Účelem tohoto článku je zprovoznění dálkového ovládání RasPi pomocí webového rozhraní, nebo pomocí SMS zpráv z mobilního telefonu.

Typickým příkladem použití této funkce může být ostraha nějakého objektu (když narušitel rozbije okno, odešle se SMS), nebo ovládání domácnosti na dálku (odesláním SMS se například zapne topení).

 

Většina běžných uživatelů nemá k dispozici veřejnou IP adresu, na kterou by připojili raspíčko. Kvůli tomu se nedá přistupovat k RasPi vzdáleně přes internet.

I přesto je ale možné (alespoň omezeně) komunikovat s RasPi na dálku.
K tomu je potřeba zřídit veřejně přístupnou webovou stránku s podporou PHP skriptů a FTP přenosem. Takovouto službu dnes nabízí mnoho webhostingových společností a mnoho z nich je možné zřídit i zdarma.

 

 

 UPOZORNĚNÍ

Všechny příklady jsou psané co nejjednodušším způsobem.
Vůbec jsem se v nich nezabýval síťovou bezpečností.

Kromě jednoduché ochrany heslem není v následujících příkladech
žádná další ochrana. Příklady nejsou odolné ani proti odposlechu.

Mějte na paměti, že veškerá komunikace mezi RasPi a internetem
probíhá po nezabezpečených linkách, takže případný narušitel
 může získat kontrolu nad ovládáním RasPi.

 


První fáze: Ovládání RasPi přes webové rozhraní

Umožňuje pomocí příkazů zadávaných do formuláře na veřejně přístupné webové stránce, ovládat činnost RasPi.

Postup:

Na nějakém FreeHostingovém serveru (např. http://www.hostuju.cz, nebo http://ic.cz ) si zaregistrujte svou stránku.
(já jsem ten příklad realizoval na vlastním webhostingu, takže webové adresy v příkladech jsou trochu jiné).


Do takto vzniklého prostoru uložte stránku index.htm, která obsahuje tento zdrojový kód:

<HTML>
<HEAD>
<META http-equiv="cache-control" content="no-cache">
<META http-equiv="pragma" content="no-cache">
<META name="robots" content="noindex, nofollow, noarchive">
</HEAD>


<BODY>

<p align="center">Příklad dálkového řízení přes webové rozhraní</p>
<hr>
<form method="POST" action="odeslat.php">
  <div align="center">
    <center>
    <table border="0" width="236">
      <tr>
        <td width="300">Příkaz :</td>
        <td width="164"><input type="text" name="prikaz" size="20" value=""></td>
      </tr>
      <tr>
        <td width="300">Heslo:</td>
        <td width="164"><input type="password" name="heslo" size="20"></td>
      </tr>
      <tr>
        <td width="300" colspan="2">
          <p align="center"><input type="submit" value="Odeslat" name="B1"></td>
      </tr>
    </table>
    </center>
  </div>
</form>

</BODY>
</HTML>

 Tento HTML kód vytvoří vstupní formulář:

 

Dále je třeba vytvořit PHP skript, který bude zpracovávat zadaná data z formuláře.
Funkce je jednoduchá:  V případě, že bude vloženo správné heslo, uloží zadaný příkaz do souboru "kod.txt".

Jméno souboru s PHP skriptem bude "odeslat.php" a bude obsahovat následující kód:

<HTML>
<HEAD>
<META http-equiv="cache-control" content="no-cache">
<META http-equiv="pragma" content="no-cache">
<META name="robots" content="noindex, nofollow, noarchive">
</HEAD>

<BODY>

<?php
// zjištění hodnoty předávaného parametru z formuláře na strance "index.htm"
  $prikaz=$_POST['prikaz'];
  $heslo=$_POST['heslo'];


  if ($heslo != "-H-E-S-L-O-")   // tady si zvolte vlastní heslo
    {
      echo "Spatne heslo";
    }
  else
    {

      if ($prikaz == "")
        {
        echo "Prikaz chybi, takze se nic nezapsalo";
        }
      else
        {
          // pokud je příkaz zadaný, uloží ho do souboru "kod.txt"
          $soubor = "kod.txt";
          $fh = fopen($soubor, 'w+');
          fwrite($fh, $prikaz);
          fclose($fh);

          echo "Prikaz '" .  $prikaz . "' byl zapsan do souboru 'kod.txt'";
        }
    }

?>

</BODY>
</HTML>


Protože kvůli přístupovým právům bývá problém s vytvořením nového souboru "kod.txt" pomocí PHP skriptu, vytvořte na webu prázdný soubor s názvem "kod.txt" ručně a pak mu nastavte přístupová práva na "777". Toto je třeba udělat pouze jednou. Když už bude mít soubor neomezená práva, nebude problém s jeho automatickým přepisováním.

Tím je hotová webová část projektu.

 

Dále je třeba v RasPi vytvořit skript, který bude v pravidelných intervalech kontrolovat soubor "kod.txt" na internetu a podle jeho obsahu bude vykonávat požadované akce.

Pro jednoduchý příklad jsem v RasPi vytvořil skript, který vyhodnocuje dva příkazy:

rozsvit24 ... rozsvítí LEDku na portu GPIO24
zhasni24 ... zhasne LEDku na portu GPIO24

#!/usr/bin/python
# -*- encoding: utf-8 -*-

import time                # nacteni systemovych podprogramu pro praci s casem
import RPi.GPIO as GPIO    # nacteni systemovych podprogramu pro praci s konektorem GPIO
import urllib2             # cteni souboru umisteneho na internetu

GPIO.setwarnings(False)    # zruseni vystraznych hlaseni (neuzavrene kanaly)
GPIO.setmode(GPIO.BCM)     # styl cislovani podle signalu (nejsou to HW piny)
GPIO.setup(24, GPIO.OUT)   # nastaveni GPIO24 (HW pin 18) na vystup (LED)


def rozsvit_LED(signal):             # podprogram pro rozsveceni LED na prislusnem GPIO
    print "ropzsvecuji LED"
    GPIO.output(signal, GPIO.HIGH)


def zhasni_LED(signal):              # podprogram pro zhasnuti LED na prislusnem GPIO
    print "zhasinam LED"
    GPIO.output(signal, GPIO.LOW)


stary_kod = ""

while True:                       # nekonecna smycka

  try:
    # adresa verejne pristupneho souboru "kod.txt" s ridicim prikazem
    response = urllib2.urlopen('http://www.astromik.org/raspi/webdrive/kod.txt') 
    novy_kod = response.read()
  except:
    novy_kod= "chyba prenosu"

  print "precteny kod z internetu: " , novy_kod


  # podle kodu precteneho z internetu se vykonavaji prislusne akce:
  if (stary_kod != novy_kod):
    
    if (novy_kod == "rozsvit24"):
      rozsvit_LED(24)

    if (novy_kod == "zhasni24"):
      zhasni_LED(24)


  stary_kod = novy_kod
  time.sleep(10)     

Pak už jen stačí tento skript v RasPi spustit.

Když pak z libovolného místa otevřete na internetu stránku s formulářem (index.htm), můžete zadávat přikazy. Tyto příkazy si RasPi nejdéle po 10 sekundách přečte a vyhodnotí. 


Druhá fáze: Zobrazování dat z RasPi na webových stránkách

Pomocí této operace je možné například zobrazovat na internetu teplotu naměřenou čidly připojenými k RasPi, zobrazovat fotku z webkamery, nebo zobrazovat stav GPIO portů.

Princip spočívá v tom, že se naměřené hodnoty nejdříve ukládají do nějakého souboru v RasPi a tento soubor se pak přes FTP přenáší na veřejně přístupný web. 

Protože by při častém přepisování tohoto souboru mohlo dojít k poškození SD karty, je třeba nejdříve zprovoznit RAMdisk a soubor pak ukládat do takto vzniklého prostoru, kde přepisování nevadí.

Návod na vytvoření RAMdisku je zde: RAMdisk 

Pro příklad jsem do předchozího skriptu doplnil měření osvětlení (nově přidané věci jsou doplněny červeně):

#!/usr/bin/python
# -*- encoding: utf-8 -*-

import time                # nacteni systemovych podprogramu pro praci s casem
import RPi.GPIO as GPIO    # nacteni systemovych podprogramu pro praci s konektorem GPIO
import urllib2             # cteni souboru umisteneho na internetu

from ftplib import FTP     # podprogramy pro prenos souboru na internet pres FTP
import smbus               # vyuzito jen pro priklad I2C komunikace s cidlem osvetleni

bus = smbus.SMBus(1)       # novejsi varianta RasPi (512MB)
#bus = smbus.SMBus(0))     # starsi varianta RasPi (256MB)
addr = 0x23                # i2c adresa cidla osvetleni

GPIO.setwarnings(False)    # zruseni vystraznych hlaseni (neuzavrene kanaly)
GPIO.setmode(GPIO.BCM)     # styl cislovani podle signalu (nejsou to HW piny)
GPIO.setup(24, GPIO.OUT)   # nastaveni GPIO24 (HW pin 18) na vystup (LED)


def rozsvit_LED(signal):             # podprogram pro rozsveceni LED na prislusnem GPIO
    print "ropzsvecuji LED"
    GPIO.output(signal, GPIO.HIGH)


def zhasni_LED(signal):              # podprogram pro zhasnuti LED na prislusnem GPIO
    print "zhasinam LED"
    GPIO.output(signal, GPIO.LOW)


def posli_na_web(html_text):         # podprogram pro odeslani dat na webovy server
    print "odesilam data na web"

    # zacatek a konec webove stranky, mezi ktery se vlozi html_text
    web_hlavicka="<html><head><meta http-equiv='refresh' content='5'></head><body>"
    web_paticka ="</body></html>"

    # vytvoreni mistniho souboru, ktery se ma odeslat na web
    soubor=file("/home/pi/ramdisk/webdata.htm",'w')    
    soubor.write(web_hlavicka + html_text + web_paticka)
    soubor.close()

    # odeslani souboru na web s osetrenou moznou chybou komunikace
    try:
      ftp = FTP('ftp.astromik.org', 'raspiftp.astromik.org' , 'TAJNE-FTP-HESLO')
      ftp.storbinary('STOR /www/raspi/webdrive/webdata.htm', open('ramdisk/webdata.htm', 'rb'))
    except:
      print "nepodarilo se navazat spojeni na server"



stary_kod = ""

while True:                       # nekonecna smycka

  try:
    # adresa verejne pristupneho souboru "kod.txt" s ridicim prikazem
    response = urllib2.urlopen('http://www.astromik.org/raspi/webdrive/kod.txt') 
    novy_kod = response.read()
  except:
    novy_kod= "chyba prenosu"

  print "precteny kod z internetu: " , novy_kod


  # podle kodu precteneho z internetu se vykonavaji prislusne akce:
  if (stary_kod != novy_kod):
    
    if (novy_kod == "rozsvit24"):
      rozsvit_LED(24)

    if (novy_kod == "zhasni24"):
      zhasni_LED(24)

  # v pravidelnych intervalech se ctou nejaka data a ukladaji se na web: 
  # pro priklad je tu pouzito mereni osvetleni, ale stejne se muze zjistovat
  #    treba teplota, nebo stav GPIO portu

  svetlo = bus.read_i2c_block_data(addr,0x11)
  hodnota_svetla= (svetlo[1] + (256 * svetlo[0])) / 1.2
  nejaky_data = "Aktualni osvetleni cidla na RasPi je: <b>" + str(int(hodnota_svetla))  + "</b> lx"  
  posli_na_web(nejaky_data)

  stary_kod = novy_kod
  time.sleep(10)     

Pomocí tohoto kódu vznikne na webu soubor webdata.htm, který obsahuje aktuální hodnotu osvětlení. 
Tato hodnota se každých 10 sekund aktualizuje. 

Ukázka činnosti je tady:

Odkaz na YouTube
V horní části je spuštěný webový prohlížeč, pomocí kterého je možné odkudkoli ovládat LEDku, která je připojená k RasPi.
 Dole je pak terminál RasPi, ve kterém běží automatický skript na vyhodnocování obsahu souboru "kod.txt".
Každých 10 sekund se na jiné webové stránce aktualizuje stav osvětlení čidla.

 


Třetí fáze: Řízení RasPi přes SMS

Aby bylo možné ovládat RasPi přes SMS, je třeba se zaregistrovat na nějaké SMS bráně.
Já jsem využil tuto webovou službu:
  http://sms.sluzba.cz

(Tato služba je placená - jedno spuštění skriptu stojí asi 1,80Kč. K tomu je třeba připočítat i cenu SMS podle operátora.)

V této službě se musí nastavit položka "SMS Action", která umožňuje po přijetí SMS speciálním telefonním číslem, spustit libovolný PHP skript na serveru.
Já jsem provedl toto nastavení:


 

Jak je uvedeno v nastavovacím formuláři, při přijetí SMS se spouští skript "http://www.astromik.org/raspi/webdrive/sms-write.php"

Zdroják toho skriptu "sms-write.php" vypadá takto:

<?php

  // zjištění parametrů příkazové řádky po přijetí SMS
  $odesilatel = $_GET['sender'];       # číslo ze kterého byla SMS odeslana
  $keyword    = $_GET['keyword'];      # první kód, který se definuje v registraci služby "SMS action"
  $identifier = $_GET['identifier'];   # druhý kód, který se definuje v registraci služby "SMS action"
  $parametr   = $_GET['text'];         # text SMS (hlavní předávaný parametr, který obsahuje příkazy)
  $smsid      = $_GET['smsid'];        # unikátní kód SMS
  $cas        = $_GET['time'];         # čas přijetí SMS


  if ($parametr == "osvetleni")
    {
      $content = join ('', file ('http://www.astromik.org/raspi/webdrive/webdata.htm'));
      echo $content . "<br>";
    }


  // pokud je parametr zadaný, uloží ho do souboru "kod.txt"
  if ($parametr == "")
    {
      echo "Parametr chybi, takze se nic nezapsalo<br>";
    }

  else
    {
      $soubor = "kod.txt";
      $fh = fopen($soubor, 'w+');
      fwrite($fh, $parametr);
      fclose($fh);
      echo "Prikaz '" .  $parametr . "' byl zapsan do souboru 'kod.txt'<br>";
  }

?>

Skript je hodně podobný prvnímu příkladu pro ovládání RasPi přes webový formulář.
 I tady jde o to, že parametr, který je odeslán jako text SMS, se uloží do souboru "kod.txt".  

POZOR:

V tomto příkladu už není ani ochrana heslem. Narušiteli, který by znal tento zdrojový kód, by stačilo otevřít adresu :
  
http://www.astromik.org/raspi/webdrive/sms-write.php?text=HACKNUTO

... a tím by do souboru "kod.txt" uložil text "HACKNUTO".
V horším případě by mohl jako text použít i nějaký kus škodlivého kódu.
Důrazně proto doporučuji použít ještě nějaké další techniky zabezpečení.

 

Odesílaná SMS vypadá takto:

CODE 31682 rozsvit24

CODE je klíčové slovo
31682 je identifikátor
 tyto dvě hodnoty jsou předdefinovány v nastavení funkce "SMS Action"
pak následuje vlastní příkaz (rozsvit24), který se zapisuje do souboru "kod.txt".

Po úspěšném spuštění PHP skriptu se na mobil vrátí text s výstupem toho PHP skriptu. 
Takže se tímto způsobem může třeba zjistit aktuální stav osvětlení. 

Video s příkladem ovládání LEDky připojené k RasPi přes SMS je tady:

Odkaz na YouTube
Odezva na SMS nějakou dobu trvá, ale je vidět, že to funguje.

Detail přijaté SMS s hodnotou aktuálního osvětlení (na videu je to nějak špatně čitelné)
 


Čtvrtá fáze: Odeslání SMS z RasPi 

Tato funkce může být dobrá například k ostraze nějakého objektu. Když narušitel sepne spínač (nebo třeba aktivuje infračidlo), RasPi to vyhodnotí a přes SMS bránu odešle SMS na zadaný mobil.
 

Tady jsem také využil web http://sms.sluzba.cz.
I tato služba je placená (jedna odeslaná SMS stojí asi korunu).

Zprovoznění API pro odesílání SMS je popsáno na jejich webu a neměl jsem s tím žádný problém, tak asi nemá cenu to sem znova všechno opisovat.


Jen v krátkosti:

1) Uložit na web soubor "apipost30.php" (ke stažení je tady ).
2) V tom samém adresáři na webu vytvořit soubor "sms_posli.php" s tímto obsahem (telefonní číslo upravit podle potřeby):


<HTML><head>
<META http-equiv="cache-control" content="no-cache">
<META http-equiv="pragma" content="no-cache">
</head>
<BODY>


<?php

$zprava   =$_GET['zprava'];
$LOGIN    =$_GET['jmeno'];
$PASSWORD =$_GET['heslo'];


require_once("apipost30.php");
$apipost = new ApiPost30($LOGIN, $PASSWORD);
$apipost->set_recipient("606123456");       // telefonni cislo prijemce
$apipost->set_text($zprava);
$apipost->send();

echo "Odeslano <br>" . $zprava



?>

</BODY>
</HTML>

3) Na RasPi vytvořit a spustit tento skript (jméno a heslo upravit podle přihlašovacích údajů do sms.sluzba.cz):

#!/usr/local/bin/python

import RPi.GPIO as GPIO
import urllib2 
import time


GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(8, GPIO.IN, pull_up_down=GPIO.PUD_UP) 
GPIO.wait_for_edge(8, GPIO.FALLING)

print "bylo stisknuto tlacitko"


odeslano = False

while odeslano == False:

  try:
    adresa = 'http://www.astromik.org/raspi/webdrive/sms_posli.php'
    response = urllib2.urlopen(adresa + '?jmeno=jméno&heslo=heslo&zprava=TLACITKO_STISKNUTO') 
    sms = response.read()
    odeslano = True
  except:
    print "chyba prenosu"

  time.sleep(5)

Skript čeká na stisk tlačítka a když k tomu stisku dojde, tak pomocí API odešle SMS se zprávou "TLACITKO_STISKNUTO".
 (Zprava nesmi obsahovat mezery. Pokud potřebujete odesílat text s mezerami, nahraďte je podtržítkem, nebo kódem %20.)

Video:

Odkaz na YouTube


Detailní fotka přijaté SMS:

 


Aktualizace 2.8.2016

Doplnění RAMdisku při odesílání dat na webserver.

 


úvodní strana webu AstroMiK.org

poslední úprava stránky 2.8.2016