Skip to content
Snippets Groups Projects
Select Git revision
  • d587825d578867cf4699b38f7178c955bbd96740
  • main default protected
2 results

6-tervezes.tex

Blame
  • 6-tervezes.tex 33.28 KiB
    \chapter{Tervezés}
    
    \chapterintro{
    	Az előzetes kutatás szerint ismerkedem a projekthez illő technológiákkal,
    	megkeresem a megvalósításhoz használt eszközöket és megválasztom azokat az arra
    	vetett megkötéseinket figyelembe véve. Miután konkretizáltam a projekt kereteit,
    	a megvalósítandó rendszer tervezésével foglalkozom. A mikrokontroller az ESP32
    	hardver platfomon, Rust szoftveres környezetben valósul meg. Az okosotthon
    	integrációhoz a rendszer vezetékes hálózaton MQTT protokollon fog kommunikálni
    	a Home Assistant okosotthon szoftverrel. A választott technológiákkal ismerkedem
    	és megindoklom a választásaimat helyesség, biztonság, a fejlesztés kényelmessége
    	és a későbbi bővíthetőség szempontjai alapján.
    }
    \section{Döntések}
    \label{dontesek}
    
    \paragraph{} \Aref{feladat}. fejezetben definiáltuk a rendszer alapfeladatait.
    A megvalósításához meg kell választanom a megfelelő hardveres platformot és
    szoftveres környezetet, figyelve a megismert biztonságtechnikai kérdésekre. A
    döntéseknél figyelembe veszem a már létező kereskedelmi rendszerek sajátosságait
    és megoldásait; azok előnyei és hátrányai között keresem a kompromisszumot.
    
    A központi egységhez az Espressif ESP32 platformcsaládját választom, a
    szoftveres implementációt Rust programozási nyelven, a hivatalos Rust alapú
    framework használatával fogom megvalósítani. Az ESP32 viszonylag olcsó,
    általános célú mikrokontroller. Szoftveres támogatottsága az elmúlt néhány
    évben növekvő figyelmet élvez, különösen a Rust programozási nyelv körében.
    \cite{rust-iot} Ez a hardver és szoftver kombináció az iparágban meglehetősen
    újkeletűnek mondható például egy Arduino és a hozzá tartozó széleskörű hardveres
    és szoftveres támogatottsággal szemben. Vitatható, hogy egy biztonságkritikus
    rendszer megvalósításában ésszerű döntés lenne, ha a gyártó által ajánlott
    és szélesebb körben használatos C/C++ alapú ESP-IDF framework-öt használnám,
    a fiatalabb, kevésbé elterjedt változattal szemben. Azért nem így döntöttem,
    mert \aref{inf-bizt}. fejezetben a szoftverbiztonság fényében fontosnak
    tartom a memóriabiztonságot. Legalább olyan fontosnak, hogy az már a tervezés
    legelején kereteket szabjon. Ezért válaszottam a Rust-ot, ami fundamentálisan
    a memóriabiztonságra épülő rendszerek programozására akalmas alacsony szintű
    nyelv.
    
    A rendszerem kizárólag vezetékes kommunikációt fog alkalmazni. Ez
    \aref{kereskedelmi}. fejezetben megismert két kategória közül a hagyományos
    rendszerekre jellemző. Láttuk, hogy előny az egyszerűbb architektúra, de
    hátrány a rugalmatlanság. Célom, hogy ne adjunk esélyt a kommunikációs médiumon
    való információ kiszivárgására, ezért ez a fizikai biztonságon fog múlni.
    A rendszeren belül titkosításra emiatt nincs szükség, de a külső hálózaton
    mindenképpen kell, ezért erre alkalmas integrációs protokollt fogok választani.
    A külső hálózati interfész szerepében egy Ethernet vezérlőt választok,
    ami például egy WiFi rádió modulhoz képest kevesebb hibaforrást jelent az
    architektúra egyszerűsége miatt szoftver oldalon is. A látott rugalmatlanság itt
    nem lesz jellemző -- az okosotthon integráció fogja ellensúlyozni.
    
    Az okosotthon integrációhoz előszor meg kell választanom, milyen szoftverrel
    kell tudnia a rendszernek kommunikálnia. Ha ezt megtettem, ezután a
    kommunikációhoz használt protokollt pontosítom. Ahogy \aref{kereskedelmi}.
    fejezetben láttuk, hogy a DIY rendszerek mindegyike önmagában egy IoT platform
    is, a gyártó által kiadott zárt szoftverrel együtt. Illetve a nagy hátrányuk
    az volt, hogy a legtöbb esetben előfizetés keretében volt elérhető a teljes
    funkcionalitás, vagyis hosszútávú költségek magasak voltak. A mi rendszerünknek
    azt a célt tűzöm ki, hogy egy nyílt ökoszisztéma legyen, azaz a hardveres
    megoldások és a szoftver forrása nyílt és szabadon felhasználható legyen.
    Ne legyenek egyáltalán hosszútávú költségek, illetve a rövidtávú költségek
    keretében csak a rendszer anyagára legyen számottevő. Ezekkel a megkötésekkel
    a rendszer mechanikai telepítése és a szoftveres telepítés időköltsége
    kompromitál. A megkötést az integrációra is érvényesítem, ezért az okosotthon
    megoldások körében a helyileg futtatott, ``self-hosted'', nyílt forrású és
    szabadon felhasználható szoftverek körében szeretnék elsősorban integrációra
    lehetőséget keresni. Ezeknek a Home Assistant okosotthon felel meg, ezzel
    a szoftvercsomaggal fog integrációt élvezni a rendszerem, ami a feladatban
    megfogalmazott feltételeknek megfelel. Továbbá a feladatban említett értesítési
    módok mindegyikét lehetséges vele használni. \cite{hass-integrations} A
    megfogalmazott feltételből felmerülő hátrány a következő: a rendszer ettől
    kezdve feltételezi és elvárja, hogy a felhasználónak legyen egy működő
    Home Assistant példánya, és nem pedig egy felhős megoldással integrálódik.
    Láthatóan a kényelem árára célom növelni a biztonságot itt is azzal, hogy maga
    a riasztórendszer számára nem lesz internetelérés, hanem kizárólag a belső
    hálózaton lesz képes kommunikálni az okosotthon-rendszerrel. A Home Assistant
    készítői közzé tettek egy teljeskörű telepítési útmutatót számos lehetőséggel.
    \cite{hass-install} Ennek tudatában ezt nem tartom hátráltató tényezőnek az
    onboarding élmény során.
    
    Az okosotthon integráció megválasztása után a hálózati kommunikációra
    fordítom a tervezés menetét. Választanom kell egy olyan protokollt, mely képes
    titkosításra, a Home Assistant támogat és a Rust platformon van hozzá megbízható
    implementáció. Ezeknek az MQTT protokoll felel meg leginkább, ezért választottam
    azt. Egyetlen hátránynak tűnhet, hogy egy önálló szerver futtatását igényli,
    az úgynevezett ``broker''-t. Azért lehet hátrány, mert ez plusz terhet helyezne
    a végfelhasználóra alapesetben, de mivel a Home Assistant szoftvercsomagában
    széleskörű támogatást élvez, sőt egy hivatalos kiegészítőként is működtethető,
    nem vonatkozik esetünkre az állítás. A telepítés lépései könnyűek és rövidek.
    \cite{hass-mqtt-broker} Miután elindul a broker, és a rendszer csatlakozik
    rá, azonnal láthatóvá fog válni a Home Assistantban. Ez \aref{kereskedelmi}.
    fejezetben látottakban kimagasló előnyt jelent a DIY rendszerekhez képest, mert
    az onboarding élmény elméletben megbízható lesz a vezetékes kommunikáció miatt,
    és a kifejlett protokoll és integráció miatt. A hibafaktor és a használhatóság
    az a saját implementáláson fog leginkább múlni, melyet a gyakorlatban fogunk
    tudni felmérni.
    
    Végezetül, a megtervezett rendszert és a megválasztott komponenseket
    \aref{diag:rendszer}. ábrán illusztráltam. A megválasztás során felületesen
    szereztem tudást az adott technológiákról, megoldásokról. A következő fejezetben
    részletesen adok ismertetést azokról.
    
    
    \begin{figure}[htbp!]
    	\includesvg[width=\textwidth]{images/rendszer.drawio.svg}
    	\caption{A teljes rendszer magas szintű vázlata}
    	\label{diag:rendszer}
    \end{figure}
    
    \section{Megoldásom ismertetése}
    \label{valasztott-tech}
    
    \subsection{ESP32 platform}
    \label{esp}
    
    \paragraph{} Az Espressif más megközelítéssel tervezi a hardvereit, mint hasonló
    platfomok. Egy alacsony fogyasztású beépített rendszer általában minimális számú
    perifériával van ellátva, elterjedten a mikrokontroller tokozásában nincsenek
    kiegészítő perifériák, csak a processzor, órajel vezérlés, debug interfész,
    és egyéb alapvető komponensek. Ez jellemző például az STMicroelectronics
    ARM mikroprocesszor alapú chipjeire. Ez ideális egy olyan rendszernél,
    ahol a hardver tervezését is szeretnénk maximálisan magunk elvégezni minden
    részletében. Ezzel a módszerrel a végeredmény nagyon optimalizált összetételt
    fog eredményezni egy nagyon specifikus célra. Hátránya, hogy a későbbi
    változtatás felmerülő igényével számolni nehezebb, és azt legtöbb esetben
    nehezebb kivitelezni, ezért rugalmatlanabb, mint itt az ESP32 esetében. Az
    analógia a következő: legyen egy komplett, jól megválasztott perifériákkal
    ellátott alaprendszer a chipen (``Systen on a Chip'' -- SoC) a kezünknél. Ez a
    hardver tervezésének fázisát segíti elő, illetve a később felmerülő bővítéssel
    számolni legtöbb esetben triviális az integrált perifériák jelenlétében. Ez az
    analógia jól illeszkedik IoT célú eszközök körében, hiszen beépített WiFi és
    Bluetooth rádióval van ellátva, mellé a szoftveres támogatás is integrált, a
    gyors tervezés és implementálást elősegítve. \cite{esp32-family} Ahogy láttuk
    \aref{kereskedelmi}. fejezetben, egy hagyományos riasztórendszerhez az előbbi
    megközelítés áll jobban, és a DIY rendszerekhez az ESP32 megközelítését tartom
    ideálisabbnak. Ezért esett rá a választás, hiszen a dolgozat keretében egy
    alapot tervezek készíteni egy bővíthető rendszer számára.
    
    Az ESP32 családjában több mikroprocesszor-architektúra is megtalálható,
    nevezetesen: Xtensa, ARM és RISC-V. A legtöbb modell esetében Xtensa
    magok találhatóak, mely kizárólag az ESP-k körében használatos manapság. A
    mikrokontroller tokozásának egy általános felépítését a gyártó dokumentációja
    \aref{diag:esp32}. ábrán mutatja be. A mag 32 bites ALU-val és 16 darab 32 bites
    általános célú regiszterrel rendelkezik, 16 bites utasításkészlet mellett (ISA).
    Az alap ISA 82 RISC utasítást tartalmaz. \cite{xtensa} Az általam választott
    ESP32 alapú fejlesztőpanel (ESP32-D1-MINI) az ESP32-WROOM-32 chipet használja.
    A chip tartalmaz 4MB beépített SPI flash memóriát, 520KB SRAM-ot. Megítélésem
    szerint ez elegendő erőforrás a fehasználásunkra, hiszen nem fogjuk használni a
    beépített WiFi rádiót, ami működtetése szignifikáns ROM és RAM-ot felhasználna.
    \cite{esp-wroom} A chip tartalmaz egy beépített MAC interfészt (Media Access
    Controller), amely 17 vezetéken (MII -- Media Independent Interface) vagy
    9 (RMII -- Reduced Media Independent Interface) vezetéken képes egy fizikai
    médium vezérlővel (PHY) kommunikálni. \cite{esp} Ez esetünkben praktikus, mert
    csupán egy Ethernet aljzat és PHY vezérlő elegendő Ethernet LAN hálózatokkal
    való integráláshoz. Továbbá a chipen a MAC egyedi DMA (Direct Memory Access)
    vezérlővel működik beépített adó és vevő oldali 512 szavas FIFO-val. Ezt
    alkalmazva az adatátvitel akár $100\ Mbit/s$ sebességen tud működni. \cite{esp}
    Számunkra nem a sebesség a fontos, hanem a stabilitás, megbízhatóság, melynek
    ezek a beépített perifériák eleget tesznek. Az adatátvitel szoftveres kezelését
    a gyártó által fejlesztett framework-ben implementált driver fogja végezni, mely
    kevésbé rizikósabb, mint harmadik fél által fejlesztett megoldások.
    
    \begin{figure}[htbp!]
    	\centering
    	\includesvg[width=0.6\linewidth]{images/esp32_blockdiagram.svg}
    	\caption{Az ESP32 mikrokontrollerek általános funkcionális blokkdiagramja \cite{esp}}
    	\label{diag:esp32}
    \end{figure}
    
    Az Espressif fenn tart ehhez az architektúrához készült fordítóprogramokat,
    melyek az ESP-IDF (Espressif IoT Development Framework) szofverfejlesztői
    csomagban használatosak. \cite{idf-tools} A framework alapvetően C/C++ nyelvekre
    célzott. A Rust támogatás esete speciális, mely két különböző módon adott.
    Az egyik mód keretében az ESP-IDF függvénykönyvtárának felhasználásával
    implementált Rust standard library (std) segítségével tudunk fejleszteni
    alkalmazást. Vagy a sokkal kisebb binárist eredményező és memóriahasználatú,
    alacsonyabb szintű út: std és ESP-IDF nélkül (``\verb|no_std|''), úgynevezett
    ``baremetal Rust'' környezetben tudunk fejleszteni. \cite{esp-rust-approaches}
    A dokumentáció jól szemlélteti a két megközelítés közötti különbségeket,
    illetve mikor érdemes melyiket választani. A projektünkhöz az std használatát
    választom az ESP-IDF kifejledt ökoszisztémája miatt, mellyel korábban pozitív
    tapasztalataim voltak. A korábbi eszköz szoftverében a működése óta nem találtam hibát,
    ami hasonlóan egy ESP32-WROVER-32 alapú rendszer volt titkosított adatközléssel
    WiFi hálozat felett. \cite{onlab} Az ESP-IDF több biztonságot garantál a program
    stabilitása érdekében, mint a bare metal megoldás, hiszen ott futásidőben
    nincsen operációs rendszer, ezért minden a fejlesztőre van bízva -- nincsen
    heap, ezért a memóriakezelés visszaesik ránk. \cite{rust-nostd} \Aref{inf-bizt}.
    fejezetben indokoltak miatt a projekt keretében a bare metal használata nem
    ideális. Több minden van a fejlesztőre bízva, több befolyást ad a kezünkbe,
    de ezzel egyben biztonság kárára optimalizálnánk a program memóriahasználatát
    és bináris méretét. A bare metal megközelítést inkább elszigetelt, beágyazott
    célhardverekhez tartom jobban igazodónak, nem magas szintű IoT-barát
    megoldásoknak.
    
    \subsection{Rust nyelv és környezet}
    \label{rust-env}
    
    \paragraph{} A Rust nyelv ötlete egy Mozilla-nál dolgozó fejlesztő fejéből
    pattant ki, amikor egy liftbe beszállt és az elromlott, hibás szoftver miatt.
    Kiábrándítónak tartotta, hogy a modern információs technológia korában még
    mindig előfordulhatnak ilyenek; helytelen szoftver futásából keletkező hibák.
    \cite{rust-history} Ahogy \aref{inf-bizt}. fejezetben is láttuk, a legtöbb
    hiba helytelen memóriakezelésből származik. A nyelv megálmodója azt a célt
    tűzte ki a Rust számára, hogy a memóriakezelést tegye biztossá, biztonságossá
    és egyértelművé egy új megközelítéssel. \textsl{``A programozási nyelvek
    	tervezésénél a magas szintű ergonómia és az alacsony szintű vezérlés gyakran
    	ellentétben áll egymással; a Rust szembemegy ezzel a konfliktussal.''}
    \cite{rust-book} -- írja a nyelv hivatalos dokumentációja.
    \Aref{tab:lang-compare}. táblázatban kigyűjtöttem összehasonlításra néhány
    szempontot a Rust és valahány elterjedt programozási nyelv között.
    
    A memóriakezelés programozási nyelvek körében általában futásidőben vagy
    fordítási időben történik (pontosabban a fejlesztőre van bízva -- allokálás
    és deallokálás saját kezűleg történik). Az előbbit az úgynevezett ``garbage
    collector'' végzi, ami tipikusan az egyes memóriaterületekre mutatott
    referenciák számát figyelve dönti el, hogy a mutatott memóriaterületet fel
    lehet szabadítani vagy sem -- használatban van-e még vagy már nem. Ezzel a
    feljesztő válláról leveszi a memória kezelésének terhét, hiszen azt a program
    futtatókörnyezete teszi helyette. Ennek a megközelítésnek az a hátránya, hogy
    több memóriát használ, mint amennyire szükség lenne. Egy embedded rendszer
    kereteiben ez kritikus, ezért zártam ki a választáskor az ilyen nyelveket. Az
    utóbbi eset; a fordítási időben való memóriakezelés már nem ilyen triviálisan
    megoldható kérdés. A Rust is egy ilyen nyelv, és ehhez különböző megkötéseket és
    módszertanokat vet be.
    
    \begin{table}[htbp!]
    	\begin{tblr}{
    			vlines,
    			hlines,
    			colspec={X[c] X[c] X[c] X[c] X[c]},
    		}
    		\textbf{Nyelv} & \textbf{Előre fordított (AOT/JIT) / interpretált} & \textbf{Futásidejű virtuális gép (VM)} & \textbf{Garbage collector (GC)} & \textbf{Fordítási idő} \\ \hline
    		Javascript     & JIT                                               & igen (böngésző, Node.js)               & igen                            & -                      \\
    		Typescript     & JIT                                               & igen (böngésző, Node.js)               & igen                            & -                      \\
    		Python         & interpretált                                      & igen                                   & igen (reference counting)       & -                      \\
    		Java           & JIT                                               & igen (JVM)                             & igen (tracing)                  & gyorsabb               \\
    		Go             & AOT                                               & nem                                    & igen                            & gyorsabb               \\
    		Rust           & AOT                                               & nem                                    & nem                             & lassabb                \\
    		C/C++          & AOT                                               & nem                                    & nem                             & átlagos                \\
    	\end{tblr}
    	\begin{tblr}{
    			vlines,
    			hlines,
    			colspec={X[c] X[c] X[c] X[c] X[c]},
    		}
    		\textbf{Nyelv} & \textbf{Memóriabiztonság} & \textbf{Típusbiztonság}  & \textbf{``Null'' biztonság} & \textbf{``Data race'' biztonság} \\ \hline
    		Javascript     & van (GC)                  & gyenge                   & nincs                       & van (egyszálú)                   \\
    		Typescript     & van (GC)                  & erős (strict mode alatt) & van                         & van (egyszálú)                   \\
    		Python         & van (GC)                  & erős                     & nincs                       & van (egyszálú)                   \\
    		Java           & van (GC)                  & erős                     & nincs                       & nincs                            \\
    		Go             & van (GC)                  & erős                     & nincs                       & van                              \\
    		Rust           & van (RAII, ownership)     & erős                     & van                         & van                              \\
    		C++            & van, részben (RAII)       & gyenge                   & nincs                       & nincs                            \\
    		C              & nincs                     & gyenge                   & nincs                       & nincs                            \\
    	\end{tblr}
    	\caption{Elterjedt programozási nyelvek áttekintő összehasonlítása \cite{gh-lang-perf}}
    	\label{tab:lang-compare}
    \end{table}
    
    Az egyik alapvető különbség más statikus típusú, procedurális és imperatív
    nyelvektől, hogy minden változó első hozzárendelése után az nem megváltoztatható
    -- a változó ekkor ``immutable''. Többszöri hozzárendelés esetén a program nem
    fordul, a fordító hibaként jelzi, hogy az immutable. Ha szükséges a változó
    értékét változtatni a program élete során, akkor azt a \verb|mut| kulcsszóval
    lehetséges ``mutable'' változóvá tenni. Ekkor megengedett a többszöri
    hozzárendelés, de ez más megkötésekkel fog járni, ha a referenciát hozunk
    létre a mutable változóra. \cite{rust-book} Minden változóra lehet referenciát
    létrehozni, de egyszerre csak egy referencia létezhet egy változóra. Továbbá
    a referenciák maguk is lehetnek immutable vagy mutable referenciák, és rájuk
    is egyéb korlátozások vannak helyezve. Ezek a szabályok ugyanúgy a fordítás
    idejében kerülnek ellenőrzésre. \cite{rust-book} Jól látható, hogy a helytelen
    használatból fakadó hibaforrások sokkal hamarabb és kontrolláltabb módon
    felszínelhetőek, mint egy futásidő alatt ellenőrzött módon.
    
    Minden változónak van egy tulajdonosa és tulajdonosból kizárólag egyetlen
    lehet. Amikor a tulajdonos kilép a hatóköréből, akkor a változó értéke is
    el lesz dobva. Ha az a heap-en volt, akkor itt történik a deallokáció. Ez
    az ``ownership'' fogalma a Rust-ban. Ennek segítségével tudja garantálni
    a memóriabiztonságot fordítási időben, egy futás idejű garbage collector
    használata nélkül. \cite{rust-book} Furcsa lehet első látásra, hogy minden
    változónak egyetlen tulajdonosa lehet, amiből azt a következtetést lehet
    levonni, hogy így lehetetlen konkurrens hozzáférést implementálni egy több szálú
    programban. Ahhoz, hogy ez lehetséges legyen, a Rust bevezeti az ``interior
    mutability'' paradigmáját. A standard library tartalmaz olyan építőkocka jellegű
    adattípusokat, melyek enkapszulálják a mutable memóriaterületet. Hívó fél
    számára elegendő egy immutable reference az adattípusra, mégis van lehetőség
    a belső memória tartalmának változtatására. A következő bekezdésben ismertetem
    ez hogyan lehetséges. Az ilyen építőkocka adattípusokkal vannak implementálva
    például a mutexek, read-write lock-ok, atomic reference counted (Arc) pointer.
    \cite{rust-book}
    
    Alapesetben nincsenek nyers pointer típusok, amivel az ownership szigorú
    modelljét ki lehetne kerülni. Felmerül a kérdés: így hogyan lehet alapvető
    adatszerkezeteket optimálisan megvalósítani a nyelvben? Emiatt létezik az
    úgynevezett ``unsafe Rust'', melynek célja, hogy mégis tudjunk saját kezűleg
    optimális, manuálisan memóriakezelt kódot írni. Ez továbbra is megkötésekkel
    jár, de sokkal több kontrollt áthelyez a programozóra (egyben felelősséget
    is), de ennek árán nem tud memóriabiztosságot garantálni a fordító --
    ezért ``unsafe''. Unsafe Rust kereteiben léteznek nyers pointer típusok, és
    megengedett egy pointer dereferenciája. Így egy éles vonalat tudunk húzni egy
    adatszerkezet belső implementációja és az azt használó fél között. Ez úton
    lehetséges az interior mutability is. A belső implementációt jól optimizált
    módon, unsafe kóddal végezhetjük, és az adatszerkezetet használó félnek nincs
    szüksége onnantól kezdve unsafe kódot írnia. \cite{rust-book} Az unsafe kód
    szintaktikailag ugyanúgy Rust forráskód, csak egy \verb|unsafe| kulcsszóval
    nyitott kódblokkba kell helyezni, amivel a fordító felé jelezzük, hogy itt
    szeretnénk úgymond átvenni a totál irányítást.
    
    A dokumentáció által ajánlott az unsafe kód írását kerülni egy felhasználói
    alkalmazás keretében, illetve ha komolyan szeretnénk venni a Rust
    memóriagaranciáját, akkor ésszerű döntés. Igyekszem ezt betartani a központi
    egység firmware írásában, kivéve ha más módon nem implementálható valami, vagy
    ha optimálisnak látom annak használatát. Az utóbbi esetében jól dokumentálom
    annak okát és helyes használatát. Ezzel a hozzáállással a szoftverbiztonság
    garanciáját a nyelvi elemekre és a fordítóra helyezem át ahol csak lehet,
    csökketve az emberi hibából fakadó helytelenségeket.
    
    % TODO: async rust + ábra az async runtime működéséről
    
    \subsection{MQTT protokoll}
    
    \paragraph{} Az MQTT protokoll az OSI réteg tetején ül, az Application Layer-en,
    TCP/IP hálózatok felett. \textsl{``Az MQTT célja, hogy egyszerű, nyílt,
    	könnyű és nagy sávszélesség-hatékonyságú legyen, így jó választás a gépek
    	kommunikációjára korlátozott környezetben.''} \cite{mqtt-vs-coap} Az MQTT egy
    publish/subscribe modellre építő üzenetküldő protokoll, ami az IoT világában
    jól alkalmazható. Egyetlen nagy előnye a megbízhatósága, mivel TCP felett
    van implementálva. Emiatt minden átvitt adat rendezett és veszteségmentes.
    \cite{mqtt-vs-coap}
    
    A publish/subscribe vagy röviden pub/sub modell egy hierarchikus rendszer.
    Minden üzenet egy adott kategóriába (``topic'') van sorolva. Ha egy üzenetet
    küldünk (publish) egy adott topic-on belül, akkor az arrra feliratkozott
    (subscribe) kliensek mind megkapják az üzenetet. Egy kliens akárhány topic-ra
    iratkozhat fel, illetve küldhet üzenetet. A protokoll megköveteli azt az
    architektúrai elemet, hogy a kliensek között legyen egyetlen szerver, elnevezés
    szerint a ``broker''. A broker fogadja a kliensek csatlakozási, feliratkozási,
    üzenetküldési, leválasztási és egyéb kérelmeit. Nyilván tartja a klienseket,
    azok jogosultságait és a topic-on közzétett üzenetek továbbítását végzi.
    Számos implementációja van mind kliens oldali és broker oldali szerepeknek.
    \cite{mqtt-vs-coap}
    
    Az architektúra modelljét \aref{diag:mqtt}. ábrán rajzoltam le szemléltetés
    végett. Az első lépésben a kliens feliratkozik a ``topic1'' üzenet kategóriára.
    A második lépésben egy másik kliens iratkozik fel egy másik kategóriára:
    ``topic2''. 3. lépésben egy harmadik kliens küld üzenetet a ``topic1''
    kategórián, melyet fogad az első kliens. További lépésekben szemléltetem,
    hogy bármilyen kliens feliratkozhat bármilyen kategóriára, illetve bármilyen
    kategóriára küldhet üzenetet bármilyen kliens. Egy üzenetet akkor továbbít a
    broker, ha annak kategóriájára van feliratkozott kliens.
    
    \begin{figure}[htbp!]
    	{\footnotesize\includesvg[width=\textwidth]{images/mqtt.drawio.svg}}
    	\caption{Az MQTT protokoll architektúra-modell bemutatása 8 lépésben}
    	\label{diag:mqtt}
    \end{figure}
    
    Az architektúra előnye, hogy a komplex feldolgozás feladata az a broker-re van
    hagyva és egy kliens működtetése minimális erőforrásokat igényel. További előnye
    más megoldásokkal szemben, hogy beépített Quality of Service (QoS) megoldást
    kínál az átvitt üzenetek fogadásának és célba érkezésének garanciájához. A TCP
    önmaga ilyen garanciát nem tud adni -- csak integritás és rendezettséget --
    illetve csomagok újraküldésével tud próbálkozni de ez még nem garancia az adott
    üzenet célba érkezésére, hiszen alacsonyabb absztrakciós szinten tartózkodik.
    Ezért ez a feladat magasabb egy szintű protokollra marad. \cite{tcp} Az MQTT
    három szinten definiál QoS-t:
    
    \begin{itemize}
    	\item \textbf{QoS 0} (``At most once delivery''): Az üzenetek átviteléről kvázi
    	      nincs garancia, tehát itt nincs semmilyen újraküldésre tett kísérlet.
    	\item \textbf{QoS 1} (``At least one delivery''): Az üzenetküldés sikerességét jóvá kell hagyni,
    	      ellenben az üzenet újra küldésre kerül. Ez a szint garantálja hogy
    	      az üzenet célba fog érni, de lehetséges, hogy többször is.
    	\item \textbf{QoS 2} (``Exactly once delivery''): Az üzenetküldés maga egy
    	      négy utas kézfogás (``four-way handshake'') első üzenete, ami
    	      garantálja, hogy az üzenet pontosan egyszer fog célba érni.
    	      \cite{mqtt-vs-coap}
    \end{itemize}
    
    \Aref{esp}. fejezetben megismert ESP-IDF környezet beépített implementációt
    kínál MQTT kliens szerepben, Rust nyelven is. Az integrációt ezzel tervezem
    megvalósítani. Minden QoS szintet támogat, illetve aszinkron (async, nem
    blokkoló) változatú implementáció is van hozzá. Darabolt adatfogadásra
    is van lehetőség, amivel nagyobb mennyiségű adatot is fel lehet dolgozni
    kevés memóriával is. \cite{esp-idf-svc} Ez jól fog jönni firmware frissítés
    implementálásakor, amivel potenciálisan azonnal a flash memóriába lehet majd
    írni az újabb binárist. Bár ez nem az alapfeladat része, de jó alapokat ad a
    framework; előre tervezés szempontjából hasznos tudásnak tartom.
    
    \subsection{Home Assistant okosotthon}
    \label{hass}
    
    \paragraph{} A Home Assistant egy nyílt platform, otthon-automatizációs
    célokra. Egy központi, egységes felületet ad a felhasználó számára, ahol
    eléri az összes okos eszközét és azok funkcióit. A teljes szoftver moduláris,
    bővíthető integrációk segítségével, Python nyelven. A nyílt forrású kód
    és licensz jóvoltából számos gyártó által fejlesztett protokoll, eszköz és
    architektúrával van integrálva, melyet a Home Assistant fejlesztői közössége
    épít. \cite{hass-github} A GitHub felmérése szerint 2024-ben a Home Assisstant
    volt a legnépszerűbb projekt a platformon, csupán a hozzájárulók száma
    alapján. \cite{github-2024} A projekt támogatást élvez az Open Home Foundation
    jóvoltából, kinek tevékenységét számos vállalat szponzorál -- köztük az
    Espressif is. \cite{openhome}
    
    Nem csupán a népszerűsége és nyíltsága miatt választottam ezt a megoldást, hanem
    az egyedi architektúrai felépítése miatt is. A szoftver minden adatot entitások
    formájában értelmez. Egy entitásnak van egy egyedi azonosítója, típusa (ún.
    ``domain''), neve, állapota, illetve egyéb attribútumai. Egy entitás domain-je
    sokféle lehet, ezek közül megemlítendő: nyomógomb, kapcsoló (ki/be), bináris
    szenzor (ki/be, igaz/hamis), szenzor (hőmérséklet, légnyomás, áramfogyasztás
    stb.), lámpa, zár, kamera és természetesen riasztó vezérlőpanel, mozgásérzékelő.
    \cite{hass-entities} Jól látható, hogy egyes entitások csupán információt
    közölnek, mások pedig felhasználói interakciót engednek meg. Lehetőség van
    adott entitásokat csoportosítani felhasználó által definiált területekre (area).
    Ez például hasznos akkor, ha több lámpa van egy szobában és mindet egyszerre
    szeretnénk irányítani. A szoftver definiálja az eszköz (device) fogalmát;
    ami szintén egy entitások csoportosítása, de azt nem a felhasználó, hanem a
    gyártó/megalkotó határozza meg -- például egy IP kamera, ahol van egy kamera
    entitás, és egy nappali és éjszakai mód között váltó gomb. Vagy vegyünk egy okos
    kapu vezérlőt, ahol az entitások a következők: a kapu két szárnyát egyszerre
    nyitó gomb, csak az egyik szárnyat nyitó gomb, és akár egy állapot visszajelző
    szenzor.
    
    Az entitásokat a felhasználó eléri a Home Assistant webes felületén
    (frontend). \Aref{diag:hass-demo}. ábrán látható egy példa a frontend
    kinézetére. A felületen a felhasználó az entitások vezérlésén kívül azokra
    automatizmusokat is tud létrehozni. Egy automatizmus három fázisból áll:
    indító események (trigger), feltételek (conditions), és műveletek (actions).
    A felhasználó megadja, hogy milyen események -- entitás állapotváltozások
    -- hatására induljon el a műveletek listája. A feltételek megszabják, hogy a
    kiváltott esemény mellett definiált megkötések keretében szabad-e futtatni a
    műveleteket. \cite{hass-automations} A dokumentáció a következő példa mondattal
    magyarázza a felépítést: \textsl{``Amikor Paulus 	hazaér és a nap már lement,
    	akkor kapcsolódjanak be a lámpák a nappaliban.''} \cite{hass-automations} A
    riasztórendszer-kezelőpanel típusú entitások élesítésére és hatástalanítására is
    van művelet. Ezzel a rendszerünk frontend oldalról is teljeskörűleg integrálható
    lesz. Szabadságot tudunk adni a végfelhasználónak, hogy a saját igényei szerint
    lehessen a rendszert működtetni -- automatizált módon is. Ha a mozgásérzékelő
    típusú bináris érzékelőket a Home Assistant számára elérhetőve tesszük,
    nem csak a biztonságtechnikai szerepet tudná betölteni a rendszer, hanem az
    otthonautomatizáció szerepét is bővítené. Például, a következő két automatizmust
    lehetne megvalósítani egy mozgásérzékelő entitás jelenlétével: Amikor mozgás
    van a szobában és a rendszer hatástalanítva van és a nap már lement, akkor
    kapcsolódjon fel a szobában lévő lámpa. Illetve ha nincs mozgás legalább 10
    percen át, akkor kapcsolódjon le a lámpa.
    
    \begin{figure}[htbp!]
    	\centering
    	\includegraphics[width=\textwidth]{images/hass-demo.png}
    	\caption{A Home Assistant frontend felülete - gyártói demo \cite{hass-demo}}
    	\label{diag:hass-demo}
    \end{figure}
    
    Az MQTT egy első osztályú integráció a Home Assistantban. Az MQTT minden QoS
    szintjét támogatja, illetve a többi integrációhoz képest sokkal általánosabb
    interfészt ad. Ez kimagaslóan jobb élményt ad a végfelhasználó számára,
    mert egységes és konzisztens marad a frontend. A fejlesztő számára is jobb
    élmény, mert az intgrációt így nem szükséges a Home Assistant forráskódján át
    implementálni Pythonban. MQTT-n keresztül lehetőség van entitások és eszközök
    telepítésére manuális konfigurációval vagy az úgynevezett ``MQTT-discovery''
    vagy ``autodiscovery'' segítségével. Mindkét esetben az eszközöknek és
    entitásoknak van egy deklaratív leíró sémája, ami tartalmaz meta-adatokat
    és leírást azok működtetésére. Manuális konfiguráció esetén ezt a Home
    Assistantban YAML formátumban kell megadni, autodiscovery során pedig egy
    előre meghatározott MQTT topic-on kell küldeni JSON formátumban a rendszer
    felé. Látható, hogy az utóbbi esetben az azt támogató eszköz végfelhasználói
    onbarding élménye sokkal kényelmeseb. Például, az eszköz első indítása után
    az autodiscovery üzenet küldésével a Home Assistant frontend felületén azonnal
    látni fog a felhasználó egy jóváhagyandó üzenetet, hogy az készen áll a
    használatra. Jóváhagyás után a meghirdetett entitások importálásra kerülnek a
    Home Assistantba. \cite{hass-mqtt} Célom úgy megvalósítani a saját rendszert,
    hogy az alapból támogassa az autodiscovery-t. Így \aref{kereskedelmi}.
    fejezetben megismert DIY rendszereknél is potenciálisan jobb élményt tudna
    nyújtani, hiszen a kommunikáció vezetékes médiumon történne, ahol az Ethernet
    csatlakoztatásával azonnal a hálózatra kerül az eszköz, egyúton megjelenne a
    felhasználó Home Assistant felületén. Ez elméletben kényelmesebb, mint például
    a WiFi konfigurációja. Ennek realitását szintén a gyakorlatban fogjuk tudni
    megállapítani.
    
    Ezzel a Home Assistant belső architektúráját magas szinten kielemeztük.
    Szemléltetés céljával lerajzoltam a modellt \aref{diag:hass-arch}. ábrán.
    
    \begin{figure}[htbp!]
    	{\footnotesize\includesvg[width=\textwidth]{images/hass.drawio.svg}}
    	\caption{A Home Assistant architektúra-modellje \cite{hass-diagram}}
    	\label{diag:hass-arch}
    \end{figure}
    
    \clearpage % Ez azért kell, hogy nehogy képek átcsússzanak a következő fejezethez