Projekt S.A.R.A.H – Ovládanie svetla v domácnosti na diaľku

K vytvoreniu projektu S.A.R.A.H ma inšpiroval kamarát Patrik Naď, ktorý zrovna v tej dobe uvažoval nad kúpou RaspberryPi z dôvodu domácej automatizácie. Vždy ma fascinovala automatizácia a nakoľko Paťo ešte len začína s programovaním v Pythone, rozhodol som sa, že táto séria článkov bude venovaná postupnému vývoju tohoto projektu aby ľudia ako on mali k dispozícii ďalší zdroj informácii.

Projekt S.A.R.A.H – Ovládanie svetla v domácnosti na diaľku

Prečo zrovna S.A.R.A.H?

Alfou aj omegou každého dobrého projektu je názov 🙂

Názov môjho projektu je skratka zo seriálu Eureka znamenajúca: Self Actuated Residential Automated Habitat. Tý čo pozerali serial Eureka už určite vedia o čo sa jedná. V seriály to bol pokročilý, plne automatizovaný dom, ktorý bol obsluhovaný umelou inteligenciou. Môj projekt síce umelou inteligenciou disponovať nebude (zatiaľ) ale inak je to úplne rovnaká myšlienka, tak prečo nezachovať trocha symbolizmu.

 

Cieľ projektu, komponenty, cena.

Mojím prvým cieľom v projekte bolo rozsvietiť svetlo v izbe. Akonáhle som začal vyhľadávať bližšie informácie o probléme, zistil som že je to celkom jednoduché. RaspberryPi som už doma mal, stačilo kúpiť zopár prepojovacích káblov na GPIO porty, ktoré stáli ani nie euro (link) a relátko, ktoré stálo pár euro (link). Súčiastky prišli v priebehu mesiaca a mohol som začať pracovať na projekte.

 

Zapojenie relátka k RasPi cez GPIO.

DSC_0103Relátko je naozaj malé a jednoduché zariadenie. Svojou podstatou sa veľmi nelíši od bežného prepínača. Privediete logický signál na vstup, a podľa toho či je na vstupe 1 alebo 0 sa obvod preklopí. Svorky po bokoch sú vstupy a v strede je výstup. Výstup je tá časť, s ktorou sú prepojené oba vstupy. V prípade že chcete relátko využiť ako jednoduchý spínač, jednoducho využijete len jeden vstup. Prepínať tak budete medzi vstupom, na ktorom bude privedený prúd a vstupom na ktorom nie je žiaden vodič.

Ako vidieť na obrázku, relátko má tri GPIO porty. Na Ucc prípojíte napájanie 5V, na Gnd pripojíte zem a Inl slúži na samotné prepínanie. Tam teda privediete signál. K tomu aby ste vedeli správne zapojiť jednotlivé GPIO porty, potrebujete vedieť v akom poradí idú na RasPi. K tomu vám dopomôže nasledujúci obrázok.

Zdroj: http://raspi.tv/2014/rpi-gpio-quick-reference-updated-for-raspberry-pi-b
Zdroj: http://raspi.tv/2014/rpi-gpio-quick-reference-updated-for-raspberry-pi-b

Následne stačí už len správne pripojiť vodiče. Výsledná práca vyzerá približne nasledovne.

 

Pripojenie relátka na Raspberry Pi
Pripojenie relátka na Raspberry Pi

Keď je relé zapojené, môžme otestovať jeho funkčnosť. Ako prvé, zapneme RasPi a nainštalujeme aplikáciu WiringPi. Postupy na inštaláciu sú minimálne dva, a oba viete nájsť na oficiálnej stránke projektu. Teraz, keď je už WiringPi úspešne nainštalovaný, vieme relé otestovať. Stačí použiť nasledujúce príkazy:

#resetuje pin
gpio mode 1 out

#nastaví na pin číslo 1 logickú 0
gpio write 1 0

#nastaví na pin číslo 1 logickú 1
gpio write 1 1 

WiringPi používa svoj vlastný systém číslovania pinov (GPIO portov). Nasledujúca tabuľka zobrazuje číslovanie pinov WiringPi a ich originálne číslovanie na RasPi.

Zdroj: https://www.trafex.nl/2014/08/25/connect-a-relay-board-to-your-raspberry-pi/
Zdroj: https://www.trafex.nl/2014/08/25/connect-a-relay-board-to-your-raspberry-pi/

Taktiež viete použiť príkaz gpio readall, ktorý vám vypíše nasledujúcu tabuľku priamo v terminály

gpio readall

Pri nastavení logickej hodnoty na relé, by ste mali počuť zvuk preklopenia (určite ho rozpoznáte). V prípade, že tento zvuk nepočuť, ale LEDka sa prepína, pravdepodobne ste spravili chybu pri pripájaní relé na RasPi. Presne táto chyba sa mi stala prvý krát, keď som testoval relé. Vtedy som si číslovanie posunul o jedna a relé nebolo pripojené na správne porty.

 

Ako pripojiť relé na rozvody

Sú dva spôsoby ako viete pripojiť relé do elektrickej siete. Viete úplne nahradiť pôvodný vypínač alebo viete relé prepojiť s vypínačom tak, aby ste vedeli svetlo ovládať aj hardvérovo (klasický vypínač) alebo elektronicky (S.A.R.A.H). Zásahy do elektrickej siete sú však životu (a majetku) nebezpečné, preto pokiaľ nemáte potrebné oprávnenia (alebo know-how) prenechajte túto časť na niekoho skúseného. Predsa len 230V kope celkom slušne a 50Hz vás už len tak nepustí 🙂

Taktiež nezabudnite, že relé je pod napätím a teda sa treba postarať o jeho správne osadenie, aby sa zamedzilo kontaktu s vodivými časťami. Ja som relé osadil do plastovej krabice a pre efekt som vyrezal z plexiskla kryt aby bolo samotné zariadenie vidieť.

Obal bol pôvodne kúpený na väčšie relé, avšak časom som ho vymenil za to ktoré je aktuálne na fotke
Obal bol pôvodne kúpený na väčšie relé, avšak časom som ho vymenil za to, ktoré je aktuálne na fotke

Dve rady k osadeniu.

  1. Plexisklo odporúčam rezať uhlovou brúskou (flexka) avšak nie v sandáloch alebo šľapkách. I keď je to robota na pár min, plastové piliny sú zlo 😀
  2. Priesvitný materiál na krabicu použite len v prípade, že ste si vedomý ako veľmi ledka svieti. Ja som to neodhadol a cez noc mám izbu vysvietenú krvavo červenou farbou.

Plne elektronické ovládanie

Nahradíte pôvodný vypínač relé. Pri tomto zapojení ostáva jeden vstup na relé prázdny. Takéto zapojenie však veľmi neodporúčam. Predsa len vždy existuje možnosť že sa vyskytne nejaká chyba a v takom prípade je dobré mať aj záložný spôsob ako zapnúť svetlo.

Čiastočne elektronické ovládanie

Toto riešenie využívam doma (viď. foto vyššie). Pôvodný vypínač nahradíte novým tzv. prepínačom (ten, čo býva na schodiskách). V takomto prípade máte zapojený prepínač č. 1 a namiesto prepínača č. 2 zapojíte relé. Teraz však treba využiť už oba vstupy a na stred zapojíte výstup, ktorý ide na žiarovku. Na wikipedii je to celkom pekne vysvetlené

 

VPN, alebo prvý krok k zapínaniu svetla online

VPN je skratka pre Virtual Private Network. Názov sám o sebe môže znieť dosť zložito ale koncept VPN je dosť jednoduchý.

Zoberme si situáciu v ktorej som sa ocitol ja. Mal som pripojené RasPi do domácej WiFi siete. Acess Point (pre laikov jednoducho WiFi) je pripojený na switch a switch je pripojený na router, ku ktorému nemám prístup. Na routery je nastavené NAT a pripája sa do lokálnej siete ISP (poskytovateľa internetu) a ďalej do internetu. Problémy sú hneď dva. Neviem si nastaviť pravidlá pre smerovanie trafficu do lokálnej siete nakoľko nemám prístup na router a aj keby som prístup získal, nemám verejnú IP adresu na ktorú by som sa vedel z internetu pripojiť. Avšak mám kúpeny server v Čechách (volajme ho VPS), ktorý má verejnú IP adresu a viem ho manažovať tak, ako uznám za vhodné.

A teraz príde na radu samotné VPN. Nakonfigurujem na VPS službu OpenVPN, ktorá vytvorí virtuálnu privátnu sieť (VPN) do ktorej sa následne pripojí RasPi. Ak to ešte stále znie príliš zložito tak si predstavte, že sme zrovna vytvorili lokálnu sieť LAN cez internet. Skrátka dve zariadenia, ktoré sú každé v inej republike vedia komunikovať medzi sebou úplne rovnako, ako keby boli fyzicky vedľa seba.

Týmto sa vyriešil problém ako komunikovať zo zariadením, ku ktorému by sme nemali mať verejne prístup. Postup, ako nakonfigurovať VPN je možné najsť napríklad na DigitalOcean. Ja osobne som využil postup z oficiálnej stránky OpenVPN a s menšími zmenami som ho uviedol v dokumentácii SOČky, ktorú popíšem v niektorom z budúcich článkov. Zatiaľ dávam k dispozícii aspoň časť dokumentácie týkajúcu sa konfigurácie VPN.

Pokiaľ vám to príde stále príliš zložité, a stačí vám ovládanie svetla len v rámci lokálnej siete. Nastavenie VPN môžete vynechať. Ja som však chcel mať prístup k svetlu z ľubovoľného miesta a preto som potreboval VPN.

 

Začíname s programovaním

Ovládanie cez python

O prácu s GPIO portami sa v pythone stará balík RPi.GPIO, ktorý viete nainštalovať príkazom:

sudo pip install RPi.GPIO

Jeho nainštalovanie viete overiť tým, že spustíte v terminály python interpreter a to „veľmi zložitým“ príkazom:

python

do ktorého následne zadáte nasledujúci text:

import RPi.GPIO

Pokiaľ nevyhodí žiadnu chybu, balík bol nainštalovaný správne.Môžme teda prejsť k nasledujúcemu programu:

#!/usr/bin/python
# -*- coding: utf8 -*-
#prvý riadok určuje cestu k samotnému pythonu. Nie je to potrebné, avšak odporúča sa uvádzať
#druhý riadok určuje kódovanie v dokumente. UTF-8 je potrebné na podporu špeciálnych znakov v Slovenčine ako sú mäkčene a dĺžne, ktorým by ste sa mali ak sa to dá, aj tak vyvarovať. Ja som ich však pre prehľadnejšie komentáre aj tak použil. 

import RPi.GPIO as GPIO #importuje balík RPi.GPIO pod aliasom GPIO
import time #importuje balík, ktorý ma na starosti prácu s časom

GPIO.setmode(GPIO.BCM) #určí spôsob číslovania portov

GPI.setup(18, GPIO.OUT) #nastavi portu 18 mód OUT

while True: #spúštame nekonečnú slučku
    GPIO.output(18, GPIO.HIGH) #nastaví na port 18 logickú 1
    time.sleep(1) #program sa uspí na 1 sekundu
    GPIO.output(18, GPIO.LOW) #nastaví na port 18 logickú 0
    time.sleep(1) #program sa znovu uspí na 1 sekundu

Program bude presne každú sekundu prepínať relé. Avšak to nie je veľmi užitočné a preto kód upravíme a pridáme ovládanie online. Podobný kód som písal ako v jazyku C++ tak aj v Jave a môžem povedať že v Pythone je to tak jednoduché, ako len môže byť. Tu je už upravený kód:

#!/usr/bin/env python
# -*- coding: utf8 -*-

import socket,time
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.OUT)

TCP_IP = '10.113.13.6' #nastavíme IP adresu, na ktorej má počúvať. V prípade že to skúšate všetko na jednom PC zadajte 0.0.0.0 alebo 127.0.0.1
TCP_PORT = 5006 #port na ktorom počúva
BUFFER_SIZE = 1024 #veľkosť bufferu

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #Mágia, ktorú netreba riešiť. V podstate sa tu vytvára socket, ktorý bude slúžiť na online komunikáciu
s.bind((TCP_IP, TCP_PORT)) #Ďalšia mágia. Socketu sa pridelí vyšie uvedená IP adresa a port na ktorom bude počúvať a čakať na spojenie

stav = None #definujeme premennú stav, ktorá nám neskôr bude slúžiť na zistenie v akom režime sa nachádza relé, aby sme ho vedeli podľa toho prepnúť

while True: #znova nekonečná slučka
    s.listen(1) #čaká na spojenie

    conn, addr = s.accept() #vytiahne zo spojenia adresu odosielateľa a taktiež port na ktorom bude server očakávať odpoveď

    data = conn.recv(BUFFER_SIZE) #začína sa prijímanie dát. Dáta odoslané serverom sa uložia do premennej data

    if stav == None: #pokiaľ hodnota premennej stav je None (bez hodnoty) tak sa nastaví na výstup portu 18 logická 1 a premenná zmení svoj stav na False (boolean)
        GPIO.output(18, GPIO.HIGH)
        stav = False
    elif stav == False: #pokiaľ hodnota premennej stav je False, na port 18 sa nastaví logická 0 a hodnota premennej stav sa zmení na True
        GPIO.output(18, GPIO.LOW)
        stav=True
    elif stav == True: #pokiaľ hodnota premennej stav je True, na port 18 sa nastaví logická 1 a hodnota premennej stav sa zmení na False
        GPIO.output(18, GPIO.HIGH)
        stav = False

Keď je klientská časť hotová, ostáva vytvoriť už len serverovú časť.  Serverovú časť tvorí komunikácia cez sockety, ktorú som popísal vyššie a samotný Pythónovský webový server, ktorý je napísaný v Pyramid-e. Poďme teda na samotný kód:

#!/usr/bin/python
# -*- coding: utf8 -*-

from wsgiref.simple_server import make_server #balíky, ktoré potrebuje pyramid framework
from pyramid.config import Configurator #balíky, ktoré potrebuje pyramid framework
from pyramid.response import Response #balíky, ktoré potrebuje pyramid framework
import socket, time

def send_data(MESSAGE): #definujeme si funkciu (časť kódu), ktorú môžme kdekoľvek v programe zavolať ako: send_data("text_spravy") s tým, že "text_spravy" sa automatický uloží do premennej MESSAGE a kód vo vnútri funkcie sa vykoná
    TCP_IP = '10.113.13.6'
    TCP_PORT = 5006
    BUFFER_SIZE = 1024

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(5) #toto je nové, nastavujeme čas ako dlho má server čakať na odpoveď o úspešnom nadviazaní komunikácie
    s.connect((TCP_IP, TCP_PORT))
    s.send(MESSAGE) #posielame správu, ktorá je uložená v premennej MESSAGE

    s.close() #uzavrieme spojenie po úspešnom odoslaní správy (keby sme to nespravili tak by sme sa opätovne nevedeli pripojiť)

def run(): #vo vnútri tejto funkcie voláme funkciu popísanú vyššie. Na RasPi teda posielame správu s textom: RELAY
    send_data("RELAY") 

def be_light(request): #ďalšia funkcia, tento krát už prepojená s pyramid web frameworkom (vysvetlím nižšie)
    run() #voláme funkciu run()
    return Response() #toto je návratová hodnota funkcie. Určuje, či request prešiel bez problémov (200 OK), poprípade sa vyskytol nejaký problém (404, 503 a pod)

if __name__ == '__main__':
    config = Configurator() #tu si pyramid nastaví nejaké potrebné dáta
    config.add_route('light', '/sarah/{name}') #vytvoríme cestu s názvom: nasweb.sk/sarah/light
    config.add_view(be_light, route_name='light') #pridáme pohlaď (view) pre cestu: nasbew.sk/sarah/light v ktorom určíme, že po prístupe na túto cestu sa zavolá funkcia be_light(), ktorú sme definovali vyššie. Zvyčajne takáto funkcia obsahuje html kód, ktorý vykreslí stránku.
    app = config.make_wsgi_app() #v tomto riadku sa vytvorí WSGI aplikácia
    server = make_server('185.8.236.187', 8080, app) #naštartujeme server, ktorý bude počúvať na adrese 185.8.236.187 a porte 8080. Taktiež mu určíme WSGI aplikáciu, ktorú ma poskytovať
    server.serve_forever() #jednoducho povedané nekonečná slučka, aby sa webserver neukončil

Je mi jasné že som ani zďaleka dostatočne nepopísal ako to celé funguje (to by bolo na samostatný článok). Potreboval som pythonovský webserver, tak som si prečítal pár strán v dokumentácii pyramidu a upravil kód v dokumentácii tak, aby bol použiteľný pre môj projekt. Originál dokumentáciu z ktorej som čerpal nájdete v nasledujúcom linku.

 

Samotná webová stránka

Zdrojový kód webovej stránky je čisté html a trocha javascriptu. Znova som však pri javascripte len upravil už existujúce skripty (google-fu for the win). Verte alebo nie, bola to z celého projektu pre mňa najťažšia časť, nakoľko javascript ovládam len na minimálnej úrovni a s html som nepracoval snáď od základnej školy. V hlavičke stránky sa cez metódu loadJSON() načíta JSON zo súboru ./data.json a vypíše údaje v tabuľke, ktorá sa však už nachádza v tele stránky. V tele stránky je taktiež definovaná ďalšia metóda pomenovaná fun(), ktorá po kliknutí na tlačítko odošle request na stránku 185.8.236.187/sarah/light avšak nepresmeruje užívateľa (nakoľko daná cesta nič nenavracia = prázdna stránka)

<html>
<head>
    <meta content="text/html; charset=UTF-8" http-equiv="content-type">
    <script type="application/javascript">
    function loadJSON() {
        var data_file = "data.json";
        var http_request = new XMLHttpRequest();
        try {
            // Opera 8.0+, Firefox, Chrome, Safari
            http_request = new XMLHttpRequest();
        } catch (e) {
            // Internet Explorer Browsers
            try {
                http_request = new ActiveXObject("Msxml2.XMLHTTP");
            } catch (e) {
                try {
                    http_request = new ActiveXObject("Microsoft.XMLHTTP");
                } catch (e) {
                    // Something went wrong
                    alert("Your browser broke!");
                    return false;
                }
            }
        }
        http_request.onreadystatechange = function() {
            if (http_request.readyState == 4) {
                // Javascript function JSON.parse to parse JSON data
                var jsonObj = JSON.parse(http_request.responseText);

                // jsonObj variable now contains the data structure and can
                // be accessed as jsonObj.name and jsonObj.country.
                document.getElementById("Name").innerHTML = jsonObj.name;
                if (jsonObj.active == true) {
                    document.getElementById("Status").innerHTML = "Aktívne";
                    document.getElementById("Details").innerHTML = "Zariadenie je aktívne";
                } else if (jsonObj.active == false) {
                    document.getElementById("Status").innerHTML = "Offline";
                    document.getElementById("Details").innerHTML = jsonObj.error_message;
                }
            }
        }
        http_request.open("GET", data_file, true);
        http_request.send();
    };
    loadJSON()
    </script>
    <title>S.A.R.A.H</title>
</head>

<body>
    <h1 align="center">S.A.R.A.H projekt</h1>
    <h2 align="center">Self Actuated Residential Automated Habitat</h2>
    <h3 align="center">Zatial len zapina/vypina svetlo :)</h3>
    <table class="src" border="1px solid" align="center">
        <tr>
            <th>Name</th>
            <th>Status</th>
            <th>Podrobnosti</th>
        </tr>
        <tr>
            <td>
                <div id="Name">Loading...</div>
            </td>
            <td>
                <div id="Status">Loading...</div>
            </td>
            <td>
                <div id="Details">Loading...</div>
            </td>
        </tr>
    </table>
    <br>
    <script>
    function fun() {
        loadJSON();
        if (window.XMLHttpRequest) {
            xmlhttp = new XMLHttpRequest();
        } else {
            xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
        }
        var url = "http://185.8.236.187:8080/sarah/light";
        xmlhttp.open("POST", url, false);
        xmlhttp.send(null);
        var respo = xmlhttp.responseText;
        document.getElementById("some_id").innerHTML = xmlhttp.responseText;
    }
    </script>
    <div class="central" align="center">
        <button type="button" onclick="fun()">Turn light ON/OFF</button>
</body>

</html>

Stav zariadenia

Ako ste si mohli všimnuť, súčasťou projektu je aj tabuľka, ktorá informuje o tom, či je zariadenie aktívne a pokiaľ nie je, tak informuje kde nastala chyba. Koncept je znova veľmi jednoduchý. Na strane RasPi mám script, ktorý vám je už pravdepodobne dôverne známy.

#!/usr/bin/python
# -*- coding: utf8 -*-

import socket,time

TCP_IP = '10.113.13.6'
TCP_PORT = 5005 #treba zvoliť iný port ako sme mali v predchádzajúcom skripte. Na to si treba dať pozor
BUFFER_SIZE = 1024
time.sleep(10)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((TCP_IP, TCP_PORT))
s.listen(1)

conn, addr = s.accept()

while 1:
    data = conn.recv(BUFFER_SIZE)
    if not data:
        time.sleep(1)
        pass
    else:
        conn.send("PONG")
conn.close()

Serverovú časť tvorí zase dosť podobný skript, ktorý však výstup zapisuje do súboru „data.json“. Taktiež som tu musel ošetriť niektoré výnimky, ktoré mohli nastať. Príkladom výnimky je neuspešné pripojenie na server. Normálne by program padol, takto však o tom len informuje a pokračuje dalej v skúšaní pripojenia.

#!/usr/bin/env python
# -*- coding: utf8 -*-

import socket, time, datetime, json

TCP_IP = '10.113.13.6'
TCP_PORT = 5005
BUFFER_SIZE = 1024
MESSAGE = "PING" #ako ste si mohli všimnúť v predchádzajúsom skripte. RasPi očakáva správu PING

def wrote_json_error(active, error, error_message): #táto funkcia ma za úlohu vytvoriť súbor data.json a zapísať do neho informácie v JSON tvare
	json_data = {} #vytvoríme prázdny slovník (dictionary)
	json_data["time"] = str(datetime.datetime.utcnow()) #pridáme do slovníka aktuálny čas, ktorý sme prekonvertovali na string
	json_data["active"] = active
	json_data["name"] = str(socket.gethostname()) #zatial vytiahneme len hostname servera a pre istotu ho prekonvertujeme na string (neskôr možno vytiahneme aj hostname RasPi)
	json_data["error"] = error
	json_data["error_message"] = error_message
	with open('/var/www/sarah/data.json', 'w') as outfile: #tu prebieha samotný zápis do súboru
			json.dump(json_data, outfile)

'''
V nasledujúcom cykle je už spomínaná výnimka.
To, čo je v "try:" bloku program skúsi vykonať. Pokiaľ vykonanie prebehlo v poriadku, príkaz "break" ukončí cyklus a pokračuje sa ďalej v programe.
'''

while True:
	try:
		s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		s.connect((TCP_IP, TCP_PORT))
		break
	except Exception as err:
		active = False #zaznam informujúci o tom, že zariadenie nie je aktívne (boolean záznam)
		error = True #nakolko je to chybové hlásenie, musíme informovať budúci script, ktorý bude JSON spracovať (v našom prípade javascriptový kód v súbore: index.html) že má vytiahnuť aj samotné chybové hlásenie. Preto ho znova upozorníme že nastala chyba (v skutočnosti by stačila aj položka active, ale na to som pri návrhu app nepomyslel)
		error_message = str(err) #samotné chybové hlásenie prekonvertujeme na string a pridáme do slovníka
		wrote_json_error(active, error, error_message) #zavolá sa funkcia, ktorú naplníme potrebnými dátami
		time.sleep(1) #počká jednu sekundu, a znova sa pokúsi o pripojenie

while True:
	try: #v podstate to isté čo vyšie. Až na to, že spojenie už bolo nadviazané a ošetrujeme samotné odoslanie správy. Môže sa totiž stať, že server dokáže nadviazať spojenie a až neskôr sa spojenie preruší (odborne sa tomu povie že vyhnije :D)
		s.send(MESSAGE)
		data = s.recv(BUFFER_SIZE)
		if not data: #pokiaľ RasPi neodpovie na PING request, patrične o tom informuje
			wrote_json_error(active = False, error = True, error_message = "PONG response was not recieved") #iný zápis toho, ako vieme naplniť funkciu hodnotami
		else: #ak všetko prebehlo tak, ako malo zapíše sa to do JSON súboru
			wrote_json_error(active = True, error = False, error_message = None) #aj keď žiadna chyba nenastala, je dobré zachovať integritu json súboru a naplniť premennú error_message hodnotou None
	except Exception as err: #ak nastane chyba, zapíše to do JSONu a opätovne sa pokúsi vytvoriť spojenie
		wrote_json_error(active = False, error = True, error_message = str(err))
		while True:
			try:
				s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
				s.connect((TCP_IP, TCP_PORT))
				break
			except socket.error:
				wrote_json_error(active = False, error = True, error_message = str(err))
				time.sleep(1) 

 

Bezpečnostné riziko

V mojom prípade to nedostatok nie je, avšak radšej vás na to upozorním.

Ako ste si mohli všimnúť, v komunikácii medzi VPS a RasPi nijako neriešim autorizáciu a šifrovanie prenášaných dát. V mojom prípade to nedostatkom nie je, nakoľko komunikácia medzi zariadeniami prebieha cez VPN sieť, ktorá zaručuje autorizáciu každého uživateľa/zariadenia a taktiež sa stará o šifrovaný prenos. Avšak v prípade, že komunikáciu riešite v rámci domácej lokálnej siete a nebodaj ešte aj s otvorenou wifi sieťou (bez hesla alebo s WEP čo je to isté). Útočník, ktorý má znalosti zdrojových kódov alebo odpočúval a analyzoval komunikáciu v sieti vie veľmi jednoducho ukradnúť spojenie a odosielať príkazy podľa svojho uváženia. Teraz sa jedná síce len o svetlo, avšak nech vás ani nenapadne tento môj PoC (proof-of-concept) používať na akýkoľvek kritický systém. V takom prípade sa zahrávate s ohňom a časom sa môžte popáliť.

 

Produkčné nasadenie

Server

  • Nainštalujeme Apache webserver príkazom
    apt-get install apache2
  • Premiestnime súbori server_switch.py, server_ping.py a index.html do priečinku /var/www
  • Pomocou textového editora nano pridáme do súboru /etc/rc.local nasledujúce riadky:

python /var/www/server_switch.py &

python /var/www/server_ping.py &

Príkazy v rc.local sa spustia až potom, akonáhle sú pripravené všetky ostatné časti OS.

  • vytvoríme v priečinku /var/www súbor .htaccess a vložíme do neho nasledujúci kód:

AuthName "S.A.R.A.H project - Restricted area! (do not expect a warning shot)"
AuthType Basic
AuthUserFile /var/www/.htpasswd
require valid-user

  • vytvoríme heslo príkazom:
htpasswd -c /usr/local/apache/passwd/passwords moj_nick

Klient

  1. Premiestnime súbori raspi_controller.py a raspi_pong.py do ľubovolného adresára
  2. Pridáme ich rovnako ako v serverovej časti do súboru /etc/rc.local

 

A to je všetko. Celkom jednoduché že? 🙂

Pokiaľ sa vám článok páčil, budem rád za zdieľanie. Taktiež ak by ste mali akékoľvek problémy s implementáciou, stačí napísať do komentárov, poprípade ma môžte kontaktovať na sociálnych sieťach alebo cez e-mail. Nikto nie je dokonalý a teda ak by ste našli v kóde akúkoľvek chybu, určite mi napíśte aj ja ju opravím.

Pridaj komentár

Vaša e-mailová adresa nebude zverejnená. Vyžadované polia sú označené *