Tytuł : Delphi w automatyce Podtytuł : Wykorzystanie elementów środowiska programistycznego Delphi przy tworzeniu aplikacji nadzorującej pracę maszyny, weryfikującej uprawnienia operatora i wykonywaną operację technologiczną,
Czego się dowiesz : - jak stworzyć w środowisku Delphi aplikację monitorującą pracę maszyn. - co to jest OPC - jakie są różnice między odczytem z portu szeregowego danych opartym o Win 32 API a bazującym na OPC serwerze Powinieneś wiedzieć: - znać środowisko Delphi w stopniu podstawowym - co to jest komunikacja przez port szeregowy Wstęp Nowoczesne systemy pomiarowo-kontrolne obecnie oferuje większość producentów oprogramowania klasy ERP. Problemem takich systemów jest ich pewna ogólność, natomiast tworzenie przez zewnętrznych producentów oprogramowania dedykowanego rozwiązania pod konkretne zastosowanie przybiera wymiar finansowy. Zadanie jakie postawiliśmy przed sobą można ująć w sposób następujący: jak wykorzystać posiadane środowisko programistyczne Delphi Professional w celu wprowadzenia kontroli czasu pracy maszyny, zarządzania pracą operatorów maszyn oraz operacjami technologicznymi jakie te maszyny będą wykonywać. Co daje nam rozwiązanie takiego zadania? Po pierwsze pełną identyfikację osoby która w zadanym czasie wykonuje konkretną operację technologiczną na konkretnej maszynie oraz zostaje przy tym zmierzona ilość wyprodukowanych elementów, czyli zostaje dokonany pomiar wydajności pracy danego pracownika dla pewnych parametrów wejściowych. Po drugie, pracownik mając świadomość, że jego praca podlega kontroli, zaczyna zwracać większą uwagę na jakość własnej pracy, na stosunek do powierzonych mu maszyn, narzędzi. Realizacja Powyższy problem podzielony został na dwie części. Pierwsza część to jak zrealizować możliwość zarządzania dostępem pracowników na daną maszynę oraz wykonywaną technologię. Dla przykładu zobrazuję to zagadnienie następująco: nie każdy z pracowników jest uprawniony do pracy na danej maszynie i nie każda operacja technologiczna może być wykonywana na niej, bądź przez danego pracownika. Druga część problemu to tzw. pomiar cykli pracy maszyny i ich archiwizacja. To z kolei możemy zobrazować jako zliczanie impulsów pochodzących z czujnika indukcyjnego, zliczanych w sterowniku PLC, a następnie odczytanych z niego na drodze komunikacji poprzez łącze szeregowe i zapisanie do bazy danych na komputerze PC. Rozwiązując pierwszą część zadania, zdecydowaliśmy się tutaj zastosować centralę alarmową typu Integra128 wraz z modułami zewnętrznymi w postaci klawiatur i kart magnetycznych dla pracowników. Dla każdej maszyny produkcyjnej przeznaczono jedną klawiaturę. Wszystkie klawiatury zostały podłączone do jednej centrali. Każda klawiatura została podłączona pod jedno wejście. Centralę połączyliśmy z komputerem monitorującym PC poprzez port szeregowy RS232. Drugą część zadania rozwiązujemy za pomocą zastosowania na każdej maszynie w odpowiednich miejscach czujników indukcyjnych, zadaniem których jest rejestrowanie pojedynczego cyklu pracy maszyny i przesłanie sygnału 24VDC do sterownika Unitronics M90T. Czujnik każdej maszyny jest podpięty pod jedno wejście sterownika. Program sterownika, utworzony w języku drabinkowym, w przypadku pojawienia się na wejściu sygnału zwiększa wartość licznika dla danej maszyny o 1. Liczniki dla maszyn są zapisywane w komórkach typu integer sterownika PLC i jest ich tyle ile obsługiwanych maszyn. Sterownik następnie łączymy również bezpośrednio poprzez port RS232 z komputerem monitorującym PC. Rysunek 1 obrazuje rozwiązanie sprzętowe. Na tym kończy się podobieństwo współpracy centrali alarmowej i sterownika z komputerem. Realizacja programowa przebiega dwoma różnymi torami. Dla centrali wykorzystamy interfejs Win32 API, dla sterownika architekturę OPC.
Rys.1 Schemat rozwiązania sprzętowego Centrala alarmowa ma zaimplementowany własny protokół komunikacji szeregowej, więc najpierw rozwiązujemy zagadnienie polegające na odczycie danych przesłanych z centrali do komputera PC poprzez łącze RS232. Ogólnie można powiedzieć, że stosujemy tutaj standardowe rozwiązanie bazujące na interfejsie programisty Win32 API - do otwarcia skonfigurowanego portu szeregowego, i następnie w pętli czasowej odbieraniu z niego komunikatów, ich przetwarzaniu, a po zakończeniu pętli - zamknięcia portu szeregowego. Pętle czasową realizujemy przy wykorzystaniu komponentu TTimer. Fragment kodu realizujący skonfigurowanie i otwarcie łącza szeregowego wygląda następująco: procedure TForm1.Otworz_RS; lpfilename := 'COM2'; //sztywna deklaracja numeru portu szeregowego hcommdev := CreateFile(lpFileName, GENERIC_WRITE or GENERIC_READ,0, NIL, OPEN_EXISTING, 0, 0); //funkcja otwarcia portu szeregowego if (hcommdev <> INVALID_HANDLE_VALUE) then SetupComm(hCommDev, cbinqueue, cboutqueue); dcb.dcblength := sizeof(dcb); GetCommState(hCommDev, dcb); //odczytanie poprzednich ustawień portu //deklaracja ustawień portu dcb.baudrate := CBR_4800; dcb.flags := RTS_CONTROL_DISABLE or dcb_fparity; dcb.parity := NOPARITY; dcb.stopbits := ONESTOPBIT; dcb.bytesize := 8; SetCommState(hCommDev, dcb); //przesłanie ustawień do urządzenia, czyli portu GetCommMask(hCommDev, fdwevtmask); SetCommMask(hCommDev, EV_TXEMPTY); Timer1.Enabled:=True; //inicjalizacja pętli czasowej end else case hcommdev of IE_BADID: MessageDlg('Niewłaściwa nazwa portu '+lpfilename+ ' lub jest on aktywny ',mterror, [mbok], 0); Listing1. Procedura inicjalizująca port Com2 Bazą danych w naszym przykładzie była baza MS SQL 2008 Express Edition, zapewniając nam szybkość i prostotę obsługi na poziomie kodu aplikacji. Do obsługi bazy zastosowaliśmy standardowe komponenty dbgo załączone w środowisku projektowym Delphi. Korzystamy z TADOConnection za pomocą którego nawiązujemy połączenie z bazą danych, oraz w celu zminimalizowania obciążenia komputera przetwarzaniem rekordów danych z bazy, operację tę zlecamy procedurom składowanym,
wykonującymi się bezpośrednio na serwerze MS SQL. Dlatego też na projektowanej formatce znajdują się komponenty TADOStoredProc. Aplikacja w założeniu ma działać w sposób ciągły bez ingerencji użytkownika w odczyt danych, co wymusza na nas stosowanie komponentu TTimer. W obsłudze jego metody OnTimer umieszczamy fragment kodu odpowiedzialny za odczyt z portu szeregowego danych i ich przetworzenie-> Listing2. Jest to zarazem jedna z kluczowych procedur w programie, gdyż odpowiada ona także za weryfikacje uprawnień pracownika oraz operacji technologicznej jaka będzie wykonywana na danej maszynie. W tej procedurze prowadzimy również zapis informacji do bazy danych. var.. Buffer_O : ARRAY[0..cbOutQueue] of Char; // bufor wyjściowy Buffer_I : ARRAY[0..cbInQueue] of Char; // bufor wejściowy procedure TForm1.Timer1Timer(Sender: TObject); // Zdefiniowanie odpowiedzi aplikacji w protokole komunikacyjnym centrali alarmowej case Buffer_I[3] of 'a'..'z' : query_2:='+'+buffer_i[3] else query_2:='?';. // Wysyłanie zapytania lub potwierdzenia otrzymania informacji query_2_p:=pchar(query_2+#13); //ponieważ protokół transmisji centrali wymaga znaku CR StrCopy(Buffer_O, query_2_p); Zapisz_RS(hCommDev, StrLen(Buffer_O)); Sleep(100); FlushFileBuffers(hCommDev); // Odczyt wyniku zapytania if ((Odczytaj_RS(hCommDev, SizeOf(Buffer_I))>0)) then // Jeżeli pojawiły sie na porcie Com2 dane if buffer_i[3]<>'.' then. // zmienna akcja opisuje jaka czynność została wykonana na maszynie: // kod 401 zalogowanie / wylogowanie pracownika, kod 121 podano numer operacji technologicznej akcja:=strtoint(copy(buffer_i,15,3)); if (akcja=121) and {..} then // Pierwsza operacja technologiczna na danej maszynie.. // Weryfikacja uprawnień pracownika-czy może wykonywać daną technologię na danej maszynie // Parametry wejściowe z danych odczytanych na case FUNKCJA_WERYFIKACJI(numer_maszyny[maszyna_int],narzedzie_na_maszynie[maszyna_int], pracownik_int[maszyna_int]) of // Pracownik posiada uprawnienia na maszynę i do wykonywania operacji technologicznej 1 : if kolejna_operacja[maszyna_int]=false then // Jeżeli jest to pierwsza operacja technologiczna na danej maszynie w danym dniu to ustaw wszystkie wartości początkowe. // Wykorzystując serwer OPC ingerujemy w wartości komórek sterownika, tutaj zerując licznik dla nowej operacji dopcserver1.opcgroups[1].opcitems[maszyna_int-1].writeasync(0,-1,-1); // Wywołanie procedury składowanej zakładającej nowy rekord do bazy INSERT_DO_BAZY; kolejna_operacja[maszyna_int]:=true; // Pracownik nie posiada wymaganych uprawnień 2 :. end else
// Kolejna operacja technologiczna na danej maszynie // Odczytanie ilości wyprodukowanych detali bezpośrednio z komórki pamięci sterownika Unitronics impulsy[maszyna_int]:=dopcserver1.opcgroups[1].opcitems[maszyna_int-1].value; // Wywołanie procedury składowanej uaktualniającej istniejący rekord UPDATE_DO_BAZY; // Ponieważ zmieniliśmy tylko technologię, pracownik nadal pracuje więc tworzymy nowy rekord w bazie danych // Ponownie zerujemy licznik elementów w sterowniku. dopcserver1.opcgroups[1].opcitems[maszyna_int-1].writeasync(0,-1,-1); INSERT_DO_BAZY; // Wartość 401 jest kodem przesłanym przez centralę i informuje nas o zdarzeniu jakim jest // przyłożenie karty magnetycznej danego pracownika do klawiatury przy maszynie: zalogowanie/wylogowanie if akcja=401 then // Odczytaniez bufora wejściowego wartości: 3 zalogowanie, <>3 wylogowanie kod_log_maszyna[maszyna_int]:=strtoint(copy(buffer_i,13,1)); if (kod_log_maszyna[maszyna_int]=3) then // Po zalogowaniu ustawiamy pierwszą operację technologiczną.. end else // Jeżeli pracownik wylogował się z danej maszyny zakańczamy ostatnią operacje technologiczną // zanotuj impulsy impulsy[maszyna_int]:=dopcserver1.opcgroups[1].opcitems[maszyna_int-1].value; // zeruj licznik dopcserver1.opcgroups[1].opcitems[maszyna_int-1].writeasync(0,-1,-1); // Wywołanie procedury składowanej uaktualniającej istniejący rekord UPDATE_do_bazy; Listing 2. Procedura realizująca współpracę aplikacji z portami Com1 i Com2 wraz z przetworzeniem uzyskanych danych. W powyższym kodzie zostały użyte funkcja odczytu z portu jak i zapisu do portu. Funkcje odczytu i zapisu pokazane odpowiednio na Listingu 3 i 4 to nic innego jak standardowe funkcje Win 32 API. Zadaniem odczytu jest odebranie komunikatu wysłanego przez centralę alarmową, która ma postać EC=<a..z>,1,1234,<kod logowania/wylogowania>,<kod wykonanego działania na centrali>,<numer maszyny>,<numer pracownika>. Informacja w tej postaci jest odbierana przez funkcję Odczytaj_RS i następnie przetwarzana przez kod programu zamieszczony w Listingu 2. function TForm1.Odczytaj_RS(hCommDev: THANDLE; Buf_Size: DWORD) : integer; var nnumberofbytestoread: DWORD; ClearCommError(hCommDev, Errors, @Stat); if (Stat.cbInQue > 0) then if (Stat.cbInQue > Buf_Size) then nnumberofbytestoread :=Buf_Size else nnumberofbytestoread := Stat.cbInQue; ReadFile(hCommDev, Buffer_I, nnumberofbytestoread, Number_Bytes_Read, NIL); read_comm:=1; end
else Number_Bytes_Read:=0; read_comm:=0; Listing 3. Odczyt danych z portu Com2. Funkcja zapisu do portu, pokazana na Listingu 4 realizuje zadanie potwierdzenia każdego odebranego komunikatu z portu Com2. Potwierdzenie ma postać +<a..z>#13 i jest standardowym poleceniem w protokole komunikacyjnym centrali Integra128, inaczej mówiąc, jeżeli komunikat odebrany jest postaci EC=a,1,1234, to potwierdzenie ma postać +a#13. function TForm1.Zapisz_RS(hCommDev: THANDLE; nnumberofbytestowrite: DWORD) : integer; var NumberOfBytesWritten : DWORD; WriteFile(hCommDev, Buffer_O, nnumberofbytestowrite, NumberOfBytesWritten, NIL); if (WaitCommEvent(hCommDev,fdwEvtMask, NIL)=true) then Write_comm:=1 else Write_comm:=0; Listing 4. Zapis danych do portu Com2
Jak widać na Listingu 2, nie mamy jawnie zadeklarowanych funkcji współpracy z portem szeregowym w przypadku odczytu z lub zapisu do portu na którym pracuje sterownik PLC. Dzieje się tak, gdyż odczytywanie informacji zawartych w komórkach pamięci sterownika PLC realizujemy zupełnie inną metodą. Ponieważ firma Unitronics w swoich rozwiązaniach wprowadziła nowoczesny standard komunikacji przemysłowej zwanej OPC, ang. OLE for Process Control, więc może nieco przybliżymy to zagadnienie. Rys.2 Architektura pracy serwer-klient w standardzie OPC Zarządzanie informacjami znajdującymi się na różnorakich urządzeniach przemysłowych wymaga zintegrowania wielu protokołów komunikacyjnych, platform sprzętowych i rozwiązań programistycznych. Często napisanie aplikacji łączącej wiele technik jest bardzo trudną drogą. Taka sytuacja uległa radykalnej zmianie wraz z wprowadzeniem OPC, czyli otwartego standardu komunikacyjnego w automatyce przemysłowej. Ogólnie mówiąc, standard ten oparty jest na architekturze serwer-klient. Serwer OPC jest aplikacją będącą źródłem danych, związaną z konkretnym urządzeniem przemysłowym, od którego pobiera informacje i udostępnia je klientom OPC. Klientem jest aplikacja wykorzystująca ten dane. Przykład takiego modelu zobrazowano na Rysunku 2 Zamiast pisać dziesiątki linii kodu aby nawiązać komunikację na porcie szeregowym i odczytać w sterowniku PLC komórkę pamięci o adresie np. MI100, wystarczy zastosować serwer OPC dla danego sterownika, a po stronie tworzonego oprogramowania wystarczy zastosować tzw. klienta OPC, który komunikuje się z serwerem za pośrednictwem specjalnego interfejsu. Najszybszym rozwiązaniem w naszym projekcie jest zastosowanie gotowego klienta OPC. Zaletą takiego podejścia jest niewątpliwie zaoszczędzony czas, jaki musielibyśmy poświęcić na stworzenie własnego rozwiązania, a który pozwala skoncentrować się na realizacji całego zadania. Koszt związany z zakupem wynosi od 200 do 700 USD, co dla średniej wielkości firmy nie jest dużym wydatkiem. Na potrzeby niniejszej prezentacji zastosowaliśmy komponenty firmy Kassl GmbH. Skorzystamy z dwóch komponentów oferowanych przez producenta : TdOPCServer i TdOPCGUI. Za pomocą TdOPCServer konfigurujemy połączenie z serwerem OPC, w naszym konkretnym przypadku z UniOPC sterownika M90T firmy Unitronics, podając w Inspektorze Obiektów środowiska Delphi dla tego komponentu nazwę bądź adres maszyny na której serwer OPC jest zainstalowany i adres serwera OPC. Ponad to definiujemy te wszystkie dane, które pobierać będziemy ze sterownika, czyli np. adresy komórek typu integer, bitowe, czasowe. Komponent TdOPCGUI możemy bezpośrednio wykorzystać związując elementy takie jak TCheckbox, TEdit itp. ze zdefiniowanymi w TdOPCServer komórkami.
Aby nasza aplikacja nam zadziałała potrzebujemy w kodzie programu zdefiniować połączenia naszej aplikacji z serwerem OPC np. w zdarzeniu tworzenia formy głównej: otwarcie procedure TForm1.FormCreate(Sender: TObject); //otwarcie transmisji dla centrali Integra128- port Com2 Otworz_RS; //otwarcie transmisji dla UniOPC serwera- port Com1 dopcserver1.active:=true; Listing 5. Uruchomienie komunikacji aplikacji z serwerem OPC. oraz serwer OPC musi być uruchomiony i skonfigurowany do współpracy ze sterownikiem. W naszym przykładzie serwer OPC pracuje na porcie Com1 przy ustawieniach transmisji zalecanych przez producenta sterownika. Podsumowanie Nowoczesny standard komunikacji przemysłowej wypiera powoli inne techniki, takie jak opisana na początku metoda wykorzystująca polecenia interfejsu programistycznego Win32 API. Zaletą nowego podejścia jest prostota obsługi i niezawodność w oparciu o serwer OPC. Wzrasta w sposób znaczący przejrzystość kodu i następuje jego znaczne uproszczenie biorąc pod uwagę bardziej złożone systemy, kiedy mamy do czynienia nie z jednym ale kilkoma obsługiwanymi urządzeniami obsługującymi standard OPC. Oczywiście również on ewoluuje. W naszym zastosowaniu bazowaliśmy na wersji OPC DA (Data Access), który umożliwia dostęp do aktualnych danych w czasie rzeczywistym, natomiast najnowszą wersją jest OPC UA (Unified Architecture), która uniezależnienia programistę od rodzaju obsługiwanego urządzenia dzięki standaryzacji protokołu komunikacyjnego, który implementuje się obecnie w większości urządzeń do automatyki takich jak sterowniki, przekaźniki programowalne czy panele operatorskie.