Az opc da szerver írása
OPC - a heterogén rendszerek és eszközök különböző csere-protokollokhoz való integrálásának interfésze. Jelenleg az automatizálási és számviteli rendszerek adatcseréje az OPC mechanizmusa és specifikációja. Az OPC DA e tekintetben már kissé elavult, de még mindig a legelterjedtebb szabvány. A helyén már évek óta új, egységes, többplatformos szabványos OPC UA érkezett. Ezenkívül két kevésbé gyakori OPC HDA specifikáció létezik (az archivált adatok lekérdezéséhez, vagyis amikor egy címke másik dimenzió-idővel rendelkezik) és az OPC AE-vel (egy speciális szabvány a riasztások és események továbbítására). Nem írtam OPC HDA és AE szervereket, így nem tudok semmit hasznosnak mondani.
Azt fogja mondani, de mi van azzal az alapvető feladattal, hogy adatokat szerezzenek olyan eszközökről, amelyek időben tárolják és tárolják az adatokat csatornákon keresztül, mivel nem továbbíthatók az OPC DA-n keresztül? Igazad van - sőt lehetetlen, de én ebben az esetben egy kicsit trükkös, és átadta időszeletet következetes egyetlen tag vagy kódolva egy, és ebben az esetben a probléma adatok dekompressziós feküdt az ügyfél OPC, ami sok esetben lehetővé teszi számunkra, hogy végre az adatfeldolgozásra bennük.
Térjünk vissza egy egyszerű OPC DA szerver írásához egy tipikus eszközhöz. Egyébként nem szükséges, hogy a szerver íródjon az eszközök integrálásához. Nem, a másik oldalról bármi lehet az adatbázisból, a szöveges fájlokhoz, vagy akár az a személy, aki az adatokat bármely felületen keresztül bevezeti. Általában csak a hardver interfészt kell tudnunk a távoli objektummal és a csere protokollal.
Így van:
egy bizonyos eszköz soros interfésszel
a cserélési protokoll a kérés-válasz üzemmódban történik
A mester eszköz, az ügyfél-kiszolgáló összeköti és kér adatokat
Természetesen más interfészek a kurzus fog menni más programok, például az ethernet szippantók. A modbus protokollt használva egyszerre több segédprogramot is telepíthet, amelyek lehetővé teszik, hogy lekérdezzen minden regisztert, hogy ellenőrizze a saját feltételezéseit a helyesség érdekében.
Ehhez szükség lesz a szerver hibakeresésére és:- a saját szerver és a szerviz szoftver által generált parcellák összehasonlítása
- árfolyam-nyilvántartások összehasonlítása, parcellák közötti időtúllépések
- hogy nem a kódról van szó
A kiszolgáló a szokásos könyvtár alapján ír le számomra lighopc (www.ipi.ac.ru/lab43/lopc-ru.html). Számos más könyvtár és keretrendszer létezik az interneten található szerverek és ügyfelek számára, sok mással dolgoztam, de az egyszerűség miatt megálltam a lightopc-on. Ezt a projektet hosszú ideje nem fejlesztették ki, és nem támogatja más specifikációkat az OPC DA kivételével.
Most menjen közvetlenül a szerver írásához. Futtassa a VS parancsot, válassza ki a projekt - konzolalkalmazás típusát, és maga a projekt üresen marad. A konzolalkalmazást használom, mivel a régi jó konzolban célszerű megjeleníteni a szerver által jelenleg kezelt információkat, beleértve az aktuális paraméterértékeket, az eszközökre vonatkozó kéréseket / válaszokat vagy a saját hibáit.
Hozzon létre az eredeti üres kiszolgáló fájlt. Ezután nem fogok részt venni az osztályok tervezésében és írásában, elosztásuk különálló fájlokkal és modulokkal, mivel feladatom csak a lényeg, azaz a szerver legegyszerűbb példányának megteremtése. Tehát a minimális OPC szerverprojekt C forrásfájlból, fejlécfájlból, interfészfájlból és több könyvtárból áll.
Fájlok létrehozása és hozzáadása a projekthez:
- device.h
- device.cpp
A lighopc webhelyről (www.ipi.ac.ru/lab43/lopc-ru.html) le kell tölteni az összegyűjtött könyvtárakat az lightopc-0.888-0313bin. Az archívumban lesznek fájlok lightopc.dll, lightopc.lib, hozzá kell adni a projekthez.
Innen letöltjük a könyvtárat az unilog-0.55-1227 szerver naplózására. Bent lesz fájlok unilog.dll és unilog.lib, akkor is hozzá a projekthez.
Ezután szükségünk van egy opcda könyvtárra az SDK-ból (opcfoundation.org), fejlécje pedig egy dolog, és a programból kapcsolódunk.
#include "opcda.h"
A soros interfész programmodulja az interneten bemutatott sok közül bármelyiket használhatja, vagy írhatja a saját osztályát. Egy kis osztályt fogok használni a megfelelő soros port nevével és tipikus függvényekkel a port megnyitásához, bezárásához, íráshoz és olvasáshoz a portról. A forráskód megtekinthető az archívumban, amely ehhez a cikkhez kapcsolódik.
Ez a legmegfelelőbb megoldás, bár még rugalmasabb lesz egy réteg létrehozása olyan modul formájában, amely az interfész típusától függően bizonyos funkciókat fog használni, és a főprogram külső hívásai ugyanazok maradnak. Ez lehetővé teszi, hogy egy forráskódot használjon, és egyszerűen váltson a különböző típusú interfészek között, vagy akár egyetlen kiszolgáló futtatásakor is kombinálható.
Ha RS-CAN (RS-Ethernet) interfész-átalakítót használunk, akkor egy ehhez szükséges könyvtár szükséges a projekthez való csatlakozáshoz.
Pontosan meg kell határozni.
#define _WIN32_DCOM // Engedélyezi a DCOM kiterjesztéseket
#define INITGUID // Inicializálja az OLE állandókat
#define ECL_SID "opc.device" // OPC szerver azonosítója
Pontosan a következő könyvtárakra van szükség.
#include // szabványos I / O
#include // matematikai függvények
# a kiszolgáló "server.h" // header fájlját tartalmazza
#include "unilog.h" // könyvtár naplófájlokhoz
#include "opcda.h" // alapfunkciók OPC: DA
# include "lightopc.h" // header fájl könnyű OPC
Adjuk meg a lightopc változóit.
statikus const loVendorInfo vendor =; // szerver verzió (Major / Minor / Build / Reserv)
statikus int OPCstatus = OPC_STATUS_RUNNING; // OPC szerver állapota
loService * my_service; // lightOPC kiszolgáló példány
Memória a változókhoz - a kiszolgáló címkék dinamikusan oszthatók le, és statikusan, TAGS_NUM_MAX - a címkék maximális számával vehetők fel.
statikus CHAR * tn [TAGS_NUM_MAX]; // Címke nevek
statikus loTagValue tv [TAGS_NUM_MAX]; Tagértékek
statikus LoTagId ti [TAGS_NUM_MAX]; // Tag ID-ek
#include "tinyxml / tinyxml.h" // XML parser lib
Az alap osztály myClassFactory: nyilvános IClassFactory és típusfüggvényeim általában külön fájlt választok, így nem hívja a szemét. Nem változik, és kódja nem szükséges az elején rendezni. Csak töltse le és illessze be a fő kódot.
A program minden lehetséges bemenete a függvényünkhöz vezet.
Ezenkívül szétszereljük a fő funkció tartalmát, közvetlenül a pontokon.
INT mymain (HINSTANCE hInstance, INT argc, CHAR * argv []);
1) Rögtön létrehozunk egy naplófájlt, hogy leírjuk a szerver összes műveletét. Mindannyiunknak van saját véleményünk a hibakeresésre. Például Szeretek írni az összes programot, és minden köztes változók értékét a naplóban, majd apalizirovat viselkedését a program lépésről lépésre, és csak akkor, ha már van valamilyen ütközés, majd végezze el lépésről lépésre hibakeresés az integrált debugger.
Tehát létrehozok egy fájlt, itt a LOG_FNAME a fájl neve.
logg = unilog_Create (ECL_SID, LOG_FNAME, NULL, 0, ll_DEBUG); // szint [ll_FATAL. ll_DEBUG]
UL_INFO ((LOGID, "Unknown device OPC server start"))>;
2) Mi legyen a szerverünk futása? Először is működik, és a szerver együttműködik az OPC klienssel (nem, persze kalapács és üresjárat, de nem kapcsolódik ügyfeleihez, ez mindig nem lesz értelme). Bár ezt a pontot a saját belátása szerint hagyom. Végül senki sem tiltja meg, hogy olyan kiszolgálót írjon, amely mindig működik, és akár összekapcsolt ügyfelek nélkül is, és valami hasonlóan hasznos, mint az adatszolgáltatás. Például beillesztettem a kiszolgálóba az adatok írását szöveges fájlokba és adatbázisba, és az ügyfelek átvitték ezeket az adatokat, amikor csatlakoztatták őket. Ez nem teljesen helyes az ideológia szempontjából, de nagyon praktikus.
Annak érdekében, hogy az ügyfelek csatlakozzanak a szerverhez, tudniuk kell, hogy van-e szerver a regisztrált szerverek listáján a helyi vagy távoli gépen. Ehhez a készüléket regisztrálni kell a rendszerben.
Ehhez a loServerRegister (GID_ECLOPCserverExe, eProgID, eClsidName, argv0, 0)
GID_ECLOPCserverExe - az előzőleg generált UID
eProgID, eClsidName - azonosító, kiszolgáló neve - const char eProgID [] = ECL_SID; const char eClsidName [] = ECL_SID;
argv0 - parancssor (valójában ez az elérési út és regisztrálja az operációs rendszert a rendszerleíró adatbázisban)
Azt javaslom, hogy ezt a funkciót hagyományos módon elindítsam - a parancssoron lévő kulcs átadásával.
Például a / r vagy a / regiszter kapcsoló megmondja a kiszolgálónak, hogy regisztrálja a kiszolgálót a rendszeren.
Az / u vagy a / unregister kulcs egyértelművé teszi a kiszolgálónak, hogy ki szeretné távolítani a kiszolgálót a rendszerből.
A kiszolgáló eltávolításának a rendszerből a loServerUnregister (GID_ECLOPCserverExe, eClsidName) felelős.
Ne felejtse el törölni a napló létrehozott példányát
unilog_Delete (logg); logg = NULL;
Más esetekben a kiszolgálónak normál üzemmódban kell működnie.
3) A munka megkezdése előtt a COM objektumok kulcskönyvtárát inicializálja a CoInitializeEx (NULL, COINIT_MULTITHREADED) funkcióval.
4) Az események fejlesztésének két külön változata is van. Megpróbáljuk megkezdeni az interfészt, megpróbáljuk megtalálni az eszközt, és ha sikeres leszünk, akkor továbbhaladunk, és ha nem sikerül befejezni a kiszolgálót. Bármelyik esetben regisztrálunk egy kiszolgáló példányt, anélkül, hogy várnánk arra, hogy az interfész és a válasz az eszköz készen álljon és készenléti állapotban legyen, várva a kapcsolatot. A mi esetünkben ez nem nagyon fontos, így a kezdők számára elindítom az InitDriver () interfész inicializálási funkcióját, amelyet később fogok leírni. Ha az inicializálás sikertelen, nem szabad elfelejteni, kivéve a napló modul bővítményének törlését és a COM CoUninitialize () deinicializálását. Emellett töröljük a loService-t - loServiceDestroy (my_service).
5) A lekérdező meghajtó struktúra létrehozása és feltöltése logikusan az InitDriver () függvényhez van hozzárendelve az interfész inicializálásával együtt.
A konfigurációs fájl elemzéséhez nem fogom megnyitni és konfigurálni a soros portot. Végül nem fogunk dolgozni egy valódi eszközzel, és nem fogjuk fel a programot addig, amíg meg nem csinálom. Szükség esetén ezt a szakaszt később ismertetem.
6) A címkék inicializálása. Készítsen két, egyenként 3 címkével ellátott próbabábut. Az első címke egy egész szám lesz, a másik kettő lebegőpont.
Itt a tag_add a hozzáadott címkék számlálója.
8) csatlakoztatása esetén egy új ügyfél számláló értéke eggyel nő, ami a funkció az osztály myClassFactory -> AddRef (), my_CF.Release () - éppen ellenkezőleg azt a célt szolgálja, és fordított elpusztítja egy vevő, csökkenti a számláló eggyel. Amíg a csatlakoztatott ügyfelek száma nullánál nagyobb, in_use visszaad egy.
9) Minden. Most teljes egészében dolgozhatunk. Például kérdezze meg az eszközt egy hurokban.
míg (my_CF.in_use ()) ha (WorkEnable) poll_device (); Ez azt jelenti, hogy amíg legalább egy kliens csatlakozik a kiszolgálóhoz, és a szerver saját zászlója van beállítva, a készüléket hurokban vizsgáljuk. A Saját WorkEnable zászlóba be kell állítanom, hogy bármikor leállíthassa a kiszolgálót anélkül, hogy minden ügyfél leállna.
10) Az adatlekérdezési funkció a poll_device () a kiszolgáló fő funkciója, amelyben az eszköz lekérdeződik és a címkeértékek frissülnek. Ha több példányt is ugyanazt a felületet (igen, akkor is, ha nem tervezi, hogy erre most), akkor kell elkülöníteni az egyes elkülönített patak, illetve a kialakulását / kérés / válasz feldolgozására kerül sor egymás után az egyik, ami rendkívül kedvezőtlen hatással van a sebesség a teljes szerver. Egyszerűen fogalmazva, mindegyik COM portot, mindegyik socketet és minden egyes ügyfelet külön szálat rendelünk, amely foglalkozik az ezen a felületen lévő adatcserével.
Íme egy példa az ilyen munkák megszervezésére:
Példa egy jól működő szerver naplófájljára. Projekt letöltése