SEMModbus – a hardvertől a böngészőig

A SEMModbus fő célja, hogy könnyen a hálózatunkra illeszthessünk új eszközöket, és integráljuk is őket. Integrálás alatt azt értem, hogy minél egyszerűbben elérhetőek legyenek egy általánosnak tekinthető interfészen keresztül. Ennek az interfésznek elég magas szintűnek kell lennie ahhoz, hogy könnyen használható legyen, azonban elég alacsony szintűnek ahhoz, hogy minden kívánt funkcionalitást meg tudjunk valósítani. Minél inkább belegondolunk a helyzetbe, annál több kérdés vetődik fel bennünk, az ezekre nyújtott megoldásokról szól ezen cikk.

Kezdjük egy rövid áttekintéssel: a SEM-be tervezett modbus TIA485 fizikai réteget használ. Ennek előnye, hogy egyetlen differenciális jelet körbevezetve (három ér, melyből kettő impedancia-illesztett), akár 32 eszközt is felfűzhetünk bármilyen aktív eszköz használata nélkül. Mindössze párhuzamba kell kapcsolnunk az eszközünk adatvonalait (nagyobb sebességeknél természetesen figyelembe véve a hullámterjedést). A differenciális vonal miatt a kommunikációs csatorna érzéketlennek tekinthető a közös módosú zaj ellen. Az eddig ideálisnak feltüntetett megoldásnak azonban nagy hátránya, hogy kizárólag Half-Duplex kommunikáció valósítható meg rajta, azaz egyszerre csak egy eszköz “beszélhet” a vonalon. Habár a modbus protokoll igazából ezt elrejti előlünk, azt el kell fogadnunk, hogy csak a Master eszköz (jelen esetben a szerverünk), tud kommunikációt kezdeményezni. Az eszközök nem tudnak interurptot kérni, a szervernek folyamatosan pollolnia kell az eszközöket változás után lesve.

Az első probléma, ami felvetődik, hogy a szerverről a programjaink hogy érjék el az eszközöket. Az a megoldás sajnos nem működik, hogy minden egyes program futásakor, lefoglaljuk a soros portot, mint erőforrást, és lefutás után, vagy közben átadjuk másik programnak. A megoldás instabilitása abból származik, hogy folyamatos versenyhelyzet alakul ki a programok között, illetve sok holtidőt jelentene mindig lefoglalni, és újra felszabadítani a soros portot.
A problémára egy lehetséges (és általam alkalmazott) megoldása, hogy egyetlen egy központi program fogja kezelni a kommunikációt. Mivel ez az egyetlen program fogja használni a sorosportot, mint erőforrást, ezért nem tud kialakulni versenyhelyzet a soros portért, illetve a soros port megnyitására se kell várakozni.

A következő probléma a fenn említett programhoz való kapcsolódás. Jogos a felvetés, miszerint átmigráltuk a problémát egyik helyről a másikra. Most nem a sorosportért folyik versenyhelyzet, hanem a programmal való kommunikációért. Ezt könnyen megoldhatjuk azzal, hogy egyszerre több klienst is engedünk kapcsolódni a programunkhoz, és a pollolások eredményét -amennyiben lehetséges- gyorsítótárazzuk.

Nagyjából kifogytunk az elméleti korlátokból, nézzük meg a gyakorlati megvalósítást. A kiszolgálót C++ nyelven programoztam. A program írása során kihasználtam a C++11 szabvány több elemét is, például a kényelmes szálkezelést.
A működés egyik alapköve egy semmodbus_device nevű absztrakt osztály. Ilyen osztályt nem tudunk példányosítani a benne lévő tisztán virtuális függvények miatt, azonban lehetővé teszi a program többi részének, hogy egységesen kezeljék az eszközöket. Három tagfüggvényt kötelező megvalósítani egy eszközben.  Az elsőt meghívva (paraméterként átadva a modbus handlert) az osztály frissíti privát változóit, a második a bejövő kérésből előállítja a választ, és visszaadja azt. A harmadik csupán egy egyedi azonosítóval tér vissza. Habár elegendő lenne egy szövegként eltárolni az eszköz nevét, ehhez -későbbi kreatív felhasználásokra gondolva- egy külön tagfüggvényt deklaráltam.
A programom, mint a fentiekből sejthető, több szálon fut. A globális részben található egy lista, amiben elérhetőek a megvalósított eszközök. A fő szál létrehoz egy modbus handlert, és a listán újra és újra végigiterálva, minden eszközt lekérdez, meghívva a megfelelő tagfüggvényét. A másik szálon a program folyamatosan figyel egy socketet, és várja a kapcsolatokat. Amennyiben bejövő kapcsolat érkezik, annak rögtön nyit egy új szálat, ezáltal folyamatosan lehetőség van fogadni új kapcsolatokat.
A programmal JSON objektumokkal kommunikálhatunk. Ez eléggé rugalmas ahhoz, hogy tetszőleges célra használjuk bármely jövőbeli eszközhöz.
Amikor kérésünk beérkezik a szerverre, a programunk megnézi, hogy a listában van-e olyan eszköz definiálva, aminek a neve megegyezik a kérésben definiálttal. Amennyiben igen, meghívja az adott objektum megfelelő tagfüggvényét, ami a kérés alapján legenerál egy választ, és ez aztán visszajut a kérőnek.

A sok elmélet után nézzünk egy konkrét példát:

A jelenlétjelző hardver osztálya:

Igyekeztem a kódot olyan formában tálalni, hogy a lényeg könnyen érthető legyen. Ahogy az látszódik (legalábbis remélem), az osztály a saját, privát változójával köti össze a modbust figyelő szálat, és a hálózatot figyelő szálat.
Ha például egy hőmérőt kötnénk a modbus rendszerre, akkor a következő változtatásokat kellene elvégeznünk:

  • Létre kellene hoznunk (a jelenlegi stat változó helyett), egy változót, amiben eltároljuk az aktuálisan leolvasott hőmérsékletet
  • A modbus polling részben, nem az 1-es című eszközről kellene olvasnunk (mivel az a jelenlétjelzőé), hanem az aktuális címről. Ezen kívül természetesen lehetséges, hogy másik regiszterben tároljuk a végponton az adatot, ezesetben másik regiszterből kell kiolvasnunk az adatot
  • A hálózatot kiszolgáló függvényben egy beszédesebb változóban adjuk vissza az értéket
  • más egyedi azonosítót adunk az eszköznek (módosított getName függvény)

Most, hogy tisztáztuk a cache lényegi működését, nézzünk egy példát a kliens oldali felhasználásra.

A fenti paranccsal csatlakoztam a programhoz, és elküldtem neki egy JSON objektet, melyben azonosítottam a kívánt eszközt. Ennek hatására megkaptam a fenti osztálytól a választ. Természetesen az állapot folyamatosan frissül a másik szál által. A JSON objekteket már könnyen tudjuk kezelni JavaScriptben. Mindössze annyit kell tennünk, hogy egy összekötőprogrammal összekapcsoljuk a http kéréseket, a program által hallgatott “nyers” adatokkal. Ehhez például használhatunk egy PHP scriptet. Jelenleg a weboldalon lévő jelenlétjelző box is így működik. Amikor meghívjuk a php-t, az mindig ugyanazon (fenn látott) kérést küldi el a cachenek, és az visszaadja a választ.

Természetesen semmi sem akadályozza meg, hogy a php egy pl POST üzenetben átadott paraméter által kontrollálja az eszközöket. Az objektumok kódjába írhatunk kódrészletet, ami egy jelszó mezőt komparál. Továbbá azt se akadályozza meg semmi, hogy a kéréssel a cachebe írjunk adatot, ami a polling során a slave eszközökhöz jut. Ilyen módszerrel például egy kijelzőre írhatunk adatot távolról, vagy (kitekintve a SEM-ből) akár szobatermosztátot állíthatunk.

Összefoglalva egy rugalmas módszert sikerült találni arra, hogy egyszerű, magasszintű megoldásokat használva elérjük az alacsonyszintű  környezet.

Megjegyzés: A szoftvert a KSZK alá tartozó SecurITeam vizsgálja át biztonsági szempontból.

Kitekintés a jelenlétjelző ügyében:
Jelenleg egy prototípus üzemel, a jövőben el fog készülni két példány is az eszközből, melyek egymás állapotát fogják frissíteni. A frissítés könnyen megoldható a fenti eszközökkel, mindössze okosan kell megírni a polling függvényt.