Arhitectura Si Interfata Aplicatiilor Orientate pe Servicii In Windows 8

Arhitectura și interfața aplicațiilor orientate pe servicii în Windows 8

Introducere

În prezent în tot mai multe domenii se folosesc aplicații specializate pentru a putea prelucra datele provenite din diverse surse(baze de date, senzori, fișiere text etc.). Indiferent că este vorba de medicină, industrie siderurgică sau industria gazelor naturale mereu vor fi prezente anumite aplicații care vor face prelucrarea automată a datelor minimizând astfel riscul producerii erorilor datorate factorului uman. Totuși factorul uman nu este eliminat din context ci el devine un supervizor al aplicației care se ocupă de prelucratea datelor sau a informațiilor.

O astfel de industire este și industria gazelor naturale unde prelucrarea corectă a datelor obținute din diferite surse este curcială pentru buna funcționare a instalației de forare. Foarte multe din datele obținute pe parcursul procesului de forare necesită unele transformări(ex.: aplicarea unei formule specifice) pentru a putea fi interpretate de operatorul uman iar in funcție de aceste rezultate să se poată lua deciziile corecte.

Aplicația pe care mi-am propus să o constuiesc se dorește a fi o aplicație care prelucrează în timp real datele primite din diferitele surse existente în incinta sondei de forare iar apoi expune rezultatele prelucrărilor sub diferite forme. Pe lângă expunere aceste valori sunt stocate într-o bază de date de tip Redis. Astfel aceste valori pot fi folosite pentru studii ulterioare terminării procesului de forare. De asemenea aplicația monitorizează constant anumiți parametrii considerați importanți pe durata procesului de forare(ex.: concentrația de acid sulfuric, conexiunea cu placa de achiziție). O altă facilitate a acestei aplicații o constituie faptul că valorile parametrilor monitorizați pot fi urmărite și din alte locații, în timp real, prin intermeriul unui proces cu rol de server web care pune la dispiziția unui utilizator aceste informații.

Am ales această temă deoarece am considerat că o astfel de aplicație mi-ar putea folosi la locul de muncă și ar ușura de asemenea și munca celorlalți colegi. Din discuțiile purtate cu colegii și cu conducerea firmei a reieșit că o astfel de aplicație ar fi foarte utilă pe durata procesului de forare. De asemenea clienții firmei au fost foarte receptivi la această idee astfel că foarte repede ideea s-a conturat într-un proiect de de dezvoltare ambițios.

De cele mai multe ori sursele din care sunt colectate datele sunt senzori amplasați pe diferite instalații din incinta sondei, astfel se pune problema transformării impulsurilor electrice primite de la senzori în date care pot să fie prelucrate de către aplicație. Această transformare este realizată de către o placă de achiziție a datelor care convertește inpulsurile în numere reale pe care le trimite apoi către un computer pe care aplicația rulează.

Prelucrarea datelor provenite de la senzori se face o dată la fiecare secundă. Numărul senzorilor este de aproximativ șaizeci astfel că la fiecare secundă se prelucrează o cantitate semnificativă de informație acest lucru fiind imposibil pentru un operator uman.

Această lucrare este structurată pe patru capitole. Primul capitol se va ocupa cu prezentarea unor concepte cu care aplicația lucrează și se dorește a fi totodată o scurtă introducere pentru persoanele care iau contact pentru prima dată cu această parte din industrie. În capitolul doi vor fi prezentate tehnologiile folosite, arhitectura aplicației și de asemenea se va prezenta Redis. Capitolul trei se va ocupa cu prezentarea aplicației. În acest capitol se va prezenta aplicația din punct de vedere al utilizatorului și se va face o prezentare generală a modului în care este structurată baza de date. De asemenea în cadrul acestui capitol vor fi prezentate și aspecte tehnice legate de de aplicație. Cel de-al patrulea capitol va conține concluziile acestei lucrări și de asemenea posibile dezvoltări ulterioare ale aplicației.

Capitolul 1. Noțiuni teoretice prelucrate în cadrul aplicației

Cu toate că aplicația construită va rula pe un computer aceasta se folosește de o serie de echipamente hardware existente într-o sondă de foraj. Pentru ca aplicația să poată prelucra datele au loc o serie de transformări astfel încât datele să ajungă în forma digitală și astfel să poată fi prelucrate de aplicație. În acest capitol vor fi explicate noțiunile cu care aplicația rulează. De asemenea acest capitol va conține și motivarea faptului că aplicația creată este o aplicație orientată pe servicii.

Toate datele pe care aplicația le prelucrează provin de la senzori aflați în diferite locuri din sondă. Senzorii sunt de diferite tipuri: senzori de presiune, de temperatură, de proximitate. Fiecare tip de senzori codifică informația în mod diferit însă la bază transformarea informației transmise de senzor se face prin convertirea intensității unui impuls electric la un număr real. Majoritatea senzorilor trimit impulsuri electrice cu intensitatea între 0 și 10 volți. Valoarea de 0 volți corespunde valorii 0 în sistemul zecimal iar valoarea de 10 volți corespunde valorii maxime pe care senzorul o poare înregistra. Această valoare diferă pentru fiecare senzor în parte.

Toți senzorii sunt legați la placa de achiziție. Placa de achiziție este un dispozitiv care servește ca nod de legătură între senzori și computer. Funcția primară a plăcii de achiziție este de a transforma semnalele analogice provenite de la senzori în date digitale care pot apoi să fie trimise unie aplicații pentru a fi prelucrate. Cele mai multe plăci de achiziție au incluse funcții automate pentru măsurarea și procesarea semnalelor provenite de la senzori [4].

Figura 1.1 prezintă una dintre plăcile de achiziție care se folosesc în timpul procesului de forare pentru achiziția datelor.

Figura 1.1. Placă de achiziție USB-2533

Fiecare senzor are propriul canal prin care triminte impulsurile către placa de achiziție. Modelul prezentat în Figura 1.1 are 64 de canale analogice ceea ce înseamnă că o placă de achiziție de tip USB-2533 poate primi semnale de la maxim 64 de senzori [5]. Pentru fiecare canal există căte un array în care sunt stocate datele rezultate în urma procesului de conversie. Dimensiunea acestui array diferă în funcție de numărul de citiri care se fac într-o secundă. Numărul de citiri este configurabil și poate ajunge la un milion de citiri pe secundă. De obicei se fac zece mii de citiri într-o secundă înainte ca datele din array-uri să fie preluate de către aplicație. Toate datele unei secunde sunt stocate într-un buffer al plăcii de achiziție sub forma unui array bidimensional având numărul de linii egal cu numărul de canale utilizate și numărul coloanelor egal cu numărul citirilor care se fac într-o secundă.

Legătura dintre placa de achiziție și computer se face printr-un port USB. Prin intermediul acestui port se trimit atât semnale dinspre placa de achiziție spre aplicație dar se și preiau datele de la placa de achiziție. Semnalul pe care aplicația îl primește de la placa de achiziție este semanalul de „secundă încheiată”. Mai exact acest semnal semnifică faptul că s-au finalizat citirile pentru toare canalele utilizate iar datele por fi preluate de aplicație. Aplicația primește datele în același format în care acestea au fost stocate în buffer-ul plăcii de achiziție, adică sub forma unui array bidimensional. Celelalte aspecte legate de procesarea datelor vor fi prezentate în capitolul 4.

Pe lângă plăcile de achiziție de tip USB-2533 mai pot fi folosite și plăci de achiziție de tip USB-FS1208[6]. Aceste plăci de achiziție pot citi datele de la maxim 8 senzori și lucrează după aceleași principii ca și o placă de achiziție USB-2533.

Aplicația pe care am construit-o pentru a interacționa cu acest echipament hardware este o aplicație orientată pe servicii.

Primul serviciu pe care aplicația îl oferă este prelucrarea datelor provenite din procesul de forare. După ce placa de achiziție încheie citirea canalelor aplicația preia dalele din buffer-ul în care acestea au fost stocate și începe prelucrarea lor. Valorile rezultate în urma prelucrării datelor sunt apoi folosite fie pentru a desena grafice prin care se poate urmări evoluția unui parametru fie sunt afișate în interfața aplicației.

Un alt serviciu pe care aplicația îl oferă este acela de a monitoriza anumiți parametrii esențiali pentru instalația de foraj dar și parametrii opționali care sunt adăugați de utilizator. Anumiți parametrii sunt esențiali pentru funcționarea instalației de foraj. Unul dintre aceștia este concentrația de hidrogen sulfurat din aer. În concentrație prea mare poate să producă moartea într-un timp foarte scurt[7]. Anumite operații care se desfășoară în incinta sondei au ca rezutat secundar acest acid. Printre datele pe care aplicația le prelucrează se află și concentrația de hidrogen sulfurat din aer. Valoarea rezultată în urma prelucrării datelor este comparată cu o valoare prag a hidrogenului sulfurat. Acest proces se repetă după fiecare prelucrare a datelor obținute de la placa de achiziție. În cazul în care concentrația de hidrogen sulfurat depășește valoarea prag aplicația atenționează operatorul prin intermediul unui semnal vizual și al unui semnal auditiv iar cei din incinta sondei de foraj sunt înștiințați prin intermediul unui semanal auditiv de intensitate ridicată. De asemenea operatorul poate să adauge noi parametrii care să fie monitorizați. Acești parametrii sunt parametrii opționali și pot fi scoși în orice moment din lista parametrilor monitorizați spre deosebire de parmetrii esențiali care nu pot fi ștersi din această listă.

Aplicația implementează un server web pentru ca rezultatele provenite din prelucrarea datelor să poată fi vizualizate prin intermediul unui browser. Această facilitate este un alt motiv pentru care aplicația este orientată pe servicii. Serverul web este un proces care interceptează și deservește toate cererile unui browser, cereri care se fac pe un anumit port. Serverul web deservește atât pagini HTML și scripturile de care aceste pagini au nevoie cât și cererile pe care browser-ul le face prin intermediul API(Application Programming Interface)[8]. Prin intermediul cererilor API datele de la server sunt transferate la client, acesta din urmă fiind reprezentat de browser.

Toate aceste funcționalități existente în aplicație sunt argumente suficiente în favoarea afirmației conform căreia aplicația dezvoltată este o aplicație orientată pe servicii.

Capitolul 2. Prezentare a tehnologiei folosite

Tehnologiile folosite în procesul de dezvoltare a aplicației sunt tehnologii furnizate de Microsoft. Codul aplicației a fost scris și compilat folosind Microsoft Visual Studio 2013. Interfața aplicației a fost construită folosind WPF. De asemenea aplicația folosește .Net Framework

Acest capitol fi împărțit în trei subcapitole după cum urmează: în primul subcapitol vor fi prezentate aspecte ale limbajului C# și framework-ului WPF, subcapitolul doi va prezenta arhitectura aplicației Windows 8 iar în subcapitolul trei se va prezenta Redis.

2.1 C# & WPF

Limbajul C# a apărut pe piață în anul 2002 și s-a dorit ca prin acest limbaj să se simplifice modul în care se scrie codul aplicațiilor [1]. Limbajul de programare C# face parte din familia de limbaje C, familie de limbaje între care putem regăsi Java, C++ sau C. C# este un limbaj de programare de nivel înalt și lucrează orientat obiect. În prezent a ajuns să fie un limbaj de programare foarte popular fiind răspândit printre companiile care sunt orientate spre soluții Microsoft.

C# permite folosirea a trei tipuri de date: date de tip referință, date de tip valoare și pointeri. Datele de tip referință sunt reprezentate de obiectele care se creează pe timpul rulării aplicației. Aceste obiecte sunt stocate în zona de memorie HEAP. Datele de tip valoare sunt acele date care conțin o valoare fără a mai fi nevoie de a accesa o altă adresă pentru a avea access la valoarea în cauză. Pointerii sunt mai puțin folosiți în C#. De obicei acest tip de date se folosește atunci când există o cantitate mare de operații care trebuie să fie făcute cu memoria(ex.: operații de citire/scriere a biților pe un domeniu de zece milioane de adrese). Acest tip de date oferă access direct la informația aflată la o anumită adresă și se folosește doar împreună cu cuvântul rezervat „unsafe”.

Orice obiect prezent într-o aplicație scrisă în C# are un tip. Acest tip este dat de clasa folosită pentru a crea acel obiect. Platforma .Net vine cu peste 5000 de clase implementate și de asemenea permite dezvoltatorului să își implementeze propriile clase creând astfel noi tipuri de date.

O clasă este de obiecei compusă din date de tip valoare. Totuși memoria necesară pentru tipurile valore nu este alocată pe stiva de memorie ci este alocată în zona de memorie HEAP. De asemenea o clasă poate conține și alte obiecte în definiția ei și nu doar date de tip valoare. Acest lucru înseamnă că între clasa care conține obiectul și obiectul conținut se stabilește o relație de tip „has a”(are o/un). Pentru acest tip de relații între clase există conceptul de compoziție. Prin acest concept se înțelege faptul că un obiect conține alte obiecte cu care lucrează. În aplicația pe care am construit-o se utilizează de foarte multe ori acest concept.

Ca orice alt limbaj orientat obiect, C# permite într-o foarte mare măsură procesul de moștenire. Prin acest proces o clasă derivată sau clasă copil preia și extinde comportamentul unei clase de bază sau clasă părinte. Totuși C# permite doar moștenirea simplă adică o clasă copil nu poate să fie derivată din mai multe clase părinte. Pentru a simula moștenirea multiplă în C# se folosesc interfețe. O clasă poate avea un singur părinte însă nu există o restricție pentru numărul de interfețe pe care le poate implementa. O interfață este un „contract” și conține doar semnăturile metodelor, evenimentelor sau ale altor membrii. Clasa care implementrază o interfață este obligată să implemnteze toți membrii acelei interfețe. Pentru a stopa procesul de moștenire în C# există un cuvânt rezervat: „sealed”. Prin acest cuvânt se specifică faptul că acea clasă pentru care s-a folosit „sealed” nu mai poate să fie folosită ca și clasă părinte pentru ale clase. Unul dintre momentele în care se folosește moștenirea este atunci când se dorește crearea unei colecții de obiecte care să fie de același tip dar fiecare obiect să aibă un alt comportament.

Pentru a da posibilitatea ca programatorul să lucreze cu un grup de obiecte înrudite C# introduce noțiunea de colecție. O colecție este la rândul ei un obiect și memorează referința unuia sau mai multor obiecte. Toate obiectele dintr-o colecție trebuie să fie de același tip. O colecție este folosită atunci când nu se știe exact numărul de elemente pe care le va conține deoarece în momentul în care numărul maxim de elemente pe care le poate memora este atins, dimensiunea colecțieie crește automat fără intervenția programatorului. Dinter colecțiile existente în C# menționez dicționarele și listele ca și colecții pe care le-am folosit preponderent în aplicație.

Un dicționar este o colecție de elemente de tip cheie – valoare. Unei chei unice din interiorul dicționarului îi corespunde o valoare. Aceasta din urmă nu este nevoie să fie unică, aceeași valoare putând fi regăsită de mai multe ori în interiorul dicționarului dar cu chei diferite. Am folosit dicționare în aplicație în modulul de setări al aplicației pentru a face asocierea dintre modulele aplicației și fișierul de setări al fiecărui modul.

O listă este o colecție de elemente de același tip asemănătoare cu un array. Diferența dintre array și listă o reprezintă sistemul de redimensionare automată pe care lista îl implementează. În aplicație am folosit acest tip de colecție în modulu de alarme pentru a putea trata toate obiectele de tip alarmă ale acestui modul în mod unitar. De asemenea întrucât numărul acestor obiecte este variabil acest tip de colecție s-a pretat cel mai bine cerințelor.

Pentru a facilita lucrul cu colecții de elemente(array-uri, liste, dicționare etc.) C# introduce noțiunea de expresie alambda. Prin expresie lambda se întelege o funcție anonimă care conține expresii sau secvențe de operatori. Ideea a fost preluată de la limbajele funcționale(F#, Haskell) [3]. Orice expresie lambda conține operatorul lambda „=>” echivalent al expresiei „merge către”. Partea stângă(înaintea operatorului lambda) conține un parametru sau o listă de parametrii de input. În partea dreaptă a operandului este o expresie sau un bloc de cod care lucrează cu parametrii de intrare specificați și produce un rezultat de ieșire, rezultat care poate fi un obiect sau un array de obiecte. De asemena există posibilitate ca expresia lambda să nu genereze nici un rezultat valid caz în care rezultatul întors este null.

Un alt tool puternic pe care C# îl implementează este LINQ. Cu ajutorul LINQ în C# se pot scrie expresii foarte asemănătoare cu cele ale limbajului SQL pentru a lucra cu colecții de elemente[3]. Fiind foarte asemănător cu SQL, LINQ folosește o serie de cuvinte de legătură din SQL pentru a crea expresii. From și in specifică sursa datelor asupra căreia se fac operații. Pentru a specifca o condiție(sau un set de condiții) pentru sursa de date se folosește where urmat de condiția sau setul de condiții pe care un obiect trebuie sa le îndeplinească. Select se folosește în același mod ca și în cadrul limbajului SQL, pentru a selecționa după un anumit criteriu specificat de where elemente din cadrul colecției. Cu toate că în anumite situații sintaxa LINQ ușurează scrierea codului aplicației LINQ are o viteză mică de lucru, în funcție de dimensiunea colecției cu care lucrează[3]. Astfel este recomandat ca pentru colecții de elemente de dimensiuni mari să ce folosească o altă metodă de a accesa și a procesa elementele. În aplicație am folosit o formă de LINQ combinată cu expresii lambda. Un exemplu de folosire a expresiilor combinate dintre LINQ și lambda poate fi găsit la Anexa 1.

Datorită faptului că aplicația este construită din module separate trebuie să existe o cale pentru a faptul că într-un modul s-a produs o schimbare care va afecta anumite module ale aplicației pentru ca acestea să poată acționa ca atare. Comunicarea între module se realizează prin intermediul evenimentelor. Un eveniment este o modalitate de da notifica alți ascultători despre o modificare a stărării unui obiect[9]. Clasele folosite în cadrul aplicațieie pentru comunicarea între module sunt clase de evenimente create pentru nevoile aplicației. Pentru a facilita trimiterea și primirea evenimentelor aplicația utilizează un centru de evenimente. Cu ajutorul acestei clase modulele aplicației se pot „abona” la anumite tipuri de evenimente care se propagă în aplicație sau se pot „dezabona” de la evenimente la care erau „abonate”. Pentru a putea trimite un eveniment folosind centrul de evenimente clasa care definește evenimentul trebuie să fie o clasă derivată a clasei pe care C# definește pentru folositea evenimentelor și anume clasa EventArgs.

Pentru a putea manageria situațiile neprevăzute care pot apărea în timpul rulării aplicației, C# implementează și extinde sistemul de tratare a excepțiilor pe care C++. Codul care este susceptibil de a arunca o excepție la rulare este încadrat într-un bloc try – catch. Try încearcă să execute codul pe care îl conține. În cazul în care situația neprevăzută apare, blocul try aruncă un obiect de exepție în acord cu tipul situației survenite( dacă un obiect a fost folosit înainte ca să fie instanțiat se va arunca un obiect de tip NullReferenceException). Blocul catch este responsabil cu prinderea excepțiilor de un anumit tip și tratarea lor. Tot în cadrul acestui bloc mesajul excepției împreună cu momentul în care aceasta a apărut este înregistrat într-un fișier pe hard disk. Astfel există un jurnal în care sunt înregistrate mesajele excepțiilor care au apărut pe durata rulării aplicației.

Majoritatea aplicațiilor au setări care trebuie să fie salvate între sesiunile de rulare ale aplicațieie. Pentru acest lucru C# implementează procesul de serializare a datelor. Prin serializare se înțelege procesul prin care un obiect este convertit într-un șir de biți care mai apoi este salvat în memorie sau pe disc. Prin serializare se salvează starea unui obiect iar obiectul salvat poate să fie recreat mai târziu prin procesul invers celui de serializare, deserializarea. Pentru salvarea datelor aplicația folosește serializarea de tip XML. XML este un limbaj folosit pentru a descrie datele. Este bazat pe taguri asemenea limbajului HTML însă spre deosebire de HTML, XML nu are taguri predefinite, acestea fiind definite de programator. Atât pentru serializarea cât și pentru deserializarea datelor C# vine cu clase definite, clase care care au implementată modalitatea de a transforma un obiect în taguri XML și de a salva rezultatul într-un fișier sau de a deserializa fișierul de tip XML și de a construi obiectul. Un lucru care trebuie menționat este faptul că procesul de serializare poate să salveze doar acele proprietăți ale obiectului care sunt publice. Acest lucru înseamnă că orice informație din câmpurile private ale obiectelor se va pierde la momentul salvării. Pentru o clasă care se dorește a fi serializată trebuie specificat înaintea corpului clasei atributul „Serializable”. Aplicația se folosește de tip XML a obiectelor pentru a salva setările modulelor din care este compusă.

Anterior am prezentat câteva dintre aspectele generale legate de limbajul de programare C#. Toate facilitățile prezentate au legătură cu ceea ce se întâmplă în spatele interfeței, loc în care utilizatorul aplicației nu are acces. În continuare voi prezenta aspecte generale legate de WPF și XAML, cele două componente principale care au contribuit la construirea interfeței aplicației.

WPF este acronimul pentru Windows Presentation Foundation și este un framework al celor de la Microsoft pentru GUI(Graphical User Interface). Înaintea WPF itnerfața aplicațiilor era creată folosind WinForms(un alt framework pentru GUI care presupune ca toate elementele să fie adăugate într-o fereastră de tip formular). WPF este o combinație între XAML, C# și alte limbaje cuprinse în platforma .Net. XAML(Extensible Application Markup Language) este un limbaj bazat pe XML folosit pentru construirea interfeței aplicației. De asemenea se aseamănă foarte mult cu HTML prin faptul că există taguri predefinite. Existența acestor taguri predefinite diferențiază XAML de XML tagurile acestuia din urmă fiind definite de către programator. Întrucât este un limbaj descriptiv XAML descrie elementele prezente în interfața aplicației funcționalitatea acestor elemente fiind implementată folosind C# în clasa generată automat asociată paginii interfeței aplicației.

Foarte multe dintre obiectele de interfață ale unui modul al aplicației au anumite particularități care se repetă atât în cadrul modulului curent cât și în cadrul altor module. O consecință a acestui fapt o reprezintă creșterea numărului de linii necesare pentru definirea controalelor și implicit a numărului de linii necesare definirii interfeței modulului. O altă problemă o reprezintă numărul mare de locuri în care trebuie să se facă schimbări în momentul în care se dorește să se modifice aspectul interfeței. Pentru a rezolva aceste probleme am folosit stiluri. Un stil este un set de proprietăți care se aplică pentru un control. Stilul se aplică pentru mai mult de un element din interfață, fie că este vorba de elemente care fac parte din aceeași fereastă sau din ferestre diferite. Prin utilizarea stilurilor modul în care un control arată este definit o singură dată facilitând astfel viitoarele modificări de aspect ale controalelor. Definirea stilurilor care au legătură cu același control(ex:. TextBox, TextBlock, Border etc.) se face în cadrul unui dicționar de resurse care are rolul de a îngloba toare resursele care țin de un anumit control. Pentru a diferenția stilurile aceluiași control și stilurile între ele fiecare stil nou definit va avea o cheie definită de programetor. Prin intermediul acestor chei se va face atașarea stilului creat la controlul corespunzător. Un stil poate fi atribuit doar tipului de control pentru care a fost definit; nu se poate uliliza un stil definit pentru controale de tip TextBox la controale de tip TextBlock. Ulterior, pentru a folosit stilurile definite, dicționarul de resurse va fi importat în pagina în care se dorește a se folosi stilurile definite.

La fel ca și în WinForms și WPF are anumite evenimente asociate controalelor. Butoanele, spre exemplu, au evenimente pentru acținuile de click, pentru primirea sau pierderea focusului etc. Întrucât aplicația folosește pattern-ul MVVM – Model View View-Model(acest pattern va fi prezentat în subcapitolul 2 al acestui capitol) există situații când apăsarea unui buton în interfața aplicației(reprezentată View) să producă anumite schimbări în clasa care ține datele pentru interfața modulului(reprezentată de View-Model). Spre exemplu atunci când în modulul de alarme, pentru una din alarme este apăsat butonul „Delete” aceasta să fie ștearsă din lista de alarme pe care clasa view-model al acestui modul o ține. Pentru această situație WPF introduce pe lângă evenimente și „Command”. O comandă este o cale prin care se execută anumite operații în partea de logică a aplicației. Prin comenzi se încearcă decuplarea interfeței de logica din spatele acesteia.

În aplicație această facilitate este folosită exclusiv pentru butoane. Fiecare view-model ale cărui elemente interacționează cu inerfața și care necesită anumite butoane are definită cel puțin o astfel de comandă. Pentru comenzi, aplicația folosește obiecte de tip ActionCommnad clasă care implementează interfața ICommand, interfață existentă în C#. Pentru a atașa comanda definită în view-model butonului din view se folosește binding-ul. Prin binding se conectează un control din UI cu sursa de date care este luată din data context. De obicei, pentru aplicația creată, data context-ul unui view este format din instanța view-model-ului aferent acelui view. Astfel se crează un mod unitar și consistent de a face legătura dintre datele prezente în partea de logică a aplicației și controalele care trebuie să afișeze utilizatorului anumite informații.

Foarte multe aplicații folosesc evenimente ca lost focus pe care majoritatea controalelor le au pentru a face anumite prelucrări în partea de logică a aplicației. Spre exemplu se dorește ca atunci când un control de tip TextBox nu mi este focusat să se salveze într-un obiect separat conținutul existent în acel moment în controlul de tip TextBox. În WinForms, o modalitate pentru a rezolva această cerință, este să se definească o metodă în codul din spatele formularului care să se ocupe de preluarea și salvarea conținutului controlului vizat.

WPF vine cu o nouă abordare a acestei situații și introduce behaviors. Un behavior este un concept care încapsulează funcționalitate într-o componentă care poate fi reutilizată și în cadrul altor pagini de interfață ale aplicației. Pentru a putea folosi un behavior trebuie să existe referință la modulul interactivity pe care Microsoft îl pune la dispoziție. Un behavior se introduce în pagina de interfață într-un mod asemănător cu adăugarea resurselor pentru acea pagină. Un behavior se folosește prin introducerea lui ca și element copil în interiorul controlului pentru care se dorește aibă acel comportament definit de behavior. În aplicația pe care am construit-o am folosit conceptul de behavior la controalele de tip TextBox. Majoritatea controalelor de tip TextBox din aplicație conțin date constituie setările aplicației. Aceste setări trebuie să fie salvate ori de câte ori se schimbă o valoare. Pentru că mulți dintre operatorii care lucrează cu această aplicație uită să facă salvarea setărilor după ce au făcut modificări și pentru le ușura munca, salvarea acestor setări este făcută în mod automat prin intermediul unui behavior atașat controalelor de tip TextBox care au legătură datele de setări ale aplicației. Înainte ca un text box să piardă focus-ul este apelat behavior-ul și se salvează setările aplicaței.

Așa cum menționam și mai sus aplicația pe care am construit-o este împărțită în module. Pentru fiecare componentă funcțioanlă a instalației de forare există câte un modul; monitorizarea parametrilor este făcută de modulul de alarme, eficiența pompelor instalației de forare și numărul de curse pe care acestea le fac într-un minut sunt făcute de către modulu de pompe etc. Pentru fiecare dintre aceste module s-a creat câte o pagină de interfață care conține date și controale specifice acelui modul. Totuși întreaga aplicație are o singură fereastră care înglobează toate aceste module.

Pentru a rezolva această problemă Microsoft pune la dispoziția dezvoltatorilor diferite librării(Prism) sau containere pentru înregistrarea și injectarea modulelor acolo unde este nevoie de ele, containere ca și Unity. Unity este un container bazat pe dependency injection(acest pattern va fi explicat în subcapitolul 2 al acestui capitol). Unity este un container pentru uz general și poate fi folosit în orice aplicație bazată pe framework-ul .Net. Întrucât este bazat pe dependency injection conține toate caracteristicile care pot fi găsite la un mecanism de acest tip. De asemenea Unity este extensibli. Acest lucru înseamnă că dezvoltatorul poate să adauge noi funcționalități la cele pe care Unity le are schimbând astfel comportamentul pe care îl are containerul. Acest container este folosit pentru decuplarea diferitelor componente ale unei aplicații(în acest caz Unity container este folosit pentru decuplarea modulelor aplicației) dar și pentru a controla timpul de viață al obiectelor. Fiecare dintre interfețele modulelor are nevoie de un context de date de unde să poată să preia datele pentru a fi afișate și unde să poată salva modificările pe care utilizatorul le face asupra datelor. Acest context de date este injectat în fiecare view prin intermediul cotainerului pe care Unity îl oferă. În întreaga aplicație se folosesc două metode de înregistrare a claselor. Înregistrarea claselor ale căror obiecte se dorește a fi single tone(pentru fiecare cerere pe care containerul o primește pentru a furniza un obiect de acel tip se întroarce referința către un singur obiect) se folosește metoda RegisterInstance. Dacă se dorește ca un nou obiect să fie instanțiat ori de câte ori o cerere pentru o clasă este făcută atunci metoda folosită pentru înregistrarea clasei este RegisterType. Un lucru pe care este bine să îl precizăm este faptul că view model-ul oricărui modul este single tone. Anexa 2 prezintă un exemplu de înregistrare a view model-ului unui modul în containerul Unity și apoi injectarea acestuia în view-ul corespunzător.

O modalitate de extindere a funcționalității containerului Unity existentă în aplicație este crearea unor manageri de viață ai obiectelor pentru care containerul este responsabil să determine momentul în care obiectul creat este eligibil de a fi îndepărtat din memorie prin intermediul lui garbage collector. De asemenea, obiectele create folosind manageri de viață ai obiecteleor sunt de tip single tone. Toți manageri de viață ai obiectelor folosiți în aplicație sunt de tip ContainerControlledLifetimeManager însă Unity pune la dispoziție și alte tipuri de manageri ca: PerThreadLifetimeManager, TransientLifetimeManager etc.[4]

O altă caracteristică pe care WPF o are este prezența convertorilor. Un convertor este o clasă care primește ca parametru un tip de date și prin intermediul unor operații interne face conversia de la tipul de date primit la un alt tip de date, conversie care în condiții normale ar fi imposibil de făcut. WPF vine cu un convertor pentru transformarea din valoare de tip boolean într-o valoare care reprezintă vizibilitatea unui obiect din interfață. Clasa care se ocupă de această conversie se numește BooleanToVisibilityConverter. În aplicație am folosit această clasa pentru a arăta sau a ascunde detaliile unui parametru la apăsarea unui buton. Un convertor „custom” pe care aplicația îl folosește este convertorul dinte nivelul unei alarme și culoarea bordurii care încadrează view-ul alarmei. Nivelul unei alarme este dată de una dintre următoarele valori: NoAlarm, Low, Medium, High. Convertorul primește una dintre aceste valori și returnează o nuanță a culorii roșu. Pentru nivelul NoAlarm culoarea returnată va fi alb iar cu cât nivelul crește nuanța culorii roșu se schimbă devenind mai închisă. În mod implicit, fiecare alarmă nou creată va avea nivelul NoAlarm urmând ca operatorul să îi stabilească nivelul potrivit. Datorită implementării acestei convenții s-a eliminat pericolul de imposibiliate a întoarcerii unei culori de către convertor în timpul rulării aplicației. Anexa 3 conține codul pentru definirea ultimului convertor prezentat și controlul pe care s-a folosit acest convertor de culoare a bordurii.

2.2 Arhitectura aplicației Windows 8

Soluția curentă a acestei aplicații cuprinde peste zece proiecte, fiecare dintre acestea având rolul său în cadrul aplicației. Datorită dimensiunilor acestei soluții este necesară o organizare, din punct de vedere arhitectural, a aplicației.

Prin arhitectură software se înțelege totalitatea deciziilor luate asupra organizării sistemului, componentele din care este construit sistemul și interacțiunea cu celelalte componente, pattern-uri folosite în dezvoltarea aplicației.

Întreaga soluție a aplicației este organizată pe două mari categorii de proiecte: proiecte de tip bibliotecă și proiecte de tip aplicație. Proiectele de tip librărie sunt proiecte create pentru a înmagazina clasele, interfețele și alte definiri de elemente pe care aplicația le folosește. De asemenea pot să conțină și resurse pe care interfața aplicației le folosește, resurse ca stiluri, interfețe ale componentelor mai mici, convertori. Proiectele de tip aplicație sunt proiectele care la momentul pornirii aplicației vor rula ca și procese pe computerul utilizatorului. Un proiect de tip aplicație se folosește de resursele puse la dispoziție de unul sau mai multe proiecte bibliotecă dar pot, de asemenea, să implementeze resurse specifice pentru acelui proiect. Soluția aplicației este compusă din cinci proiecte de tip aplicație(cele cinci proiecte corespund interfeței aplicației, procesului de achiziție a datelor, serverului web, bazei de date și procesului de calcul) restul fiind proiecte de tip bibliotecă.

Fiecare modul al aplicației a fost construit folosind pattern-ul MVVM. MVVM(Model – View – ViewModel) este un pattern folosit în dezvoltarea aplicațiilor axat pe trei componente principale:

Model – această componentă conține datele care vor fi afișate utilizatorului

View – această componentă este interfața cu care utilizatorul interacționează

ViewModel – această componentă acționează ca intermediar între Model și View

Acest pattern de design al aplicație urmărește scăderea gradului de dependență între componentele amintite anterior creând astfel un mediu propice pentru adăugare de noi funcționalități și eliminând o mare parte din problemele care apăreau la apliațiile în care aceste trei componente erau amestecate.

În continuare vom aplica acesta patteren pe unul dintre modulele aplicației exemplificând astfel utilizarea lui în cadrul aplicației. Pentru acest lucru vom alege modulul alarmelor. Acest modul este construit din obiecte pe care utilizatorul le manageriază. Se pot adăuga obiecte noi de alarmă sau se pot șterge cele existente care nu mai sunt de trebuință. De asemenea datele pe care le conțin obiectele de alarmă pot fi editate. Acest modul trebuie să afișeze utilizatorului datele pe care fiecare obiect de alarmă le conține(valoarea și parametrul pe care s-a setat alarma, statusul alarmei și tipul acesteie). Prin urmare modelul pentru acest modul trebuie să conțină o listă cu datele obiectelor de alarmă prezente. Această listă va fi modificată ori de câte ori se adaugă, șterge sau se modifică datele unei alarme. Modelul modulului de alarme va fi salvat mai apoi în baza de date(salvarea modelului se face pentru fiecare modul în parte). Atunci când aplicația va fi repornită tuturor obiectelor de alarmă va fi restaurată folosind datele stocate în baza de date înainte de închiderea aplicației. Clasa care este folosită ca model pentru acest modul este salvată în baza de date sub o cheie unică. Astfel se elimină orice confuzie care poate să apară atunci când datele stocate în baza de date sunt aduse în aplicație.

View-ul modulului de alarme este constuit(ca toate celelalte view-uri de altfel) folosind XAML. Prin intermediul acestei componente utilizatorul interacționează cu modulul de alarme. Întrucât nu este vorba în acest modul de un singur obiect de alarmă ci de mai multe astfel de obiecte pot să afirm că acest view este un view compozit. În primul rând pentru fiecare dintre obiectele de alarmă prezente în model există un view corespondent în lista de view-uri ale modulului. Aceste view-uri pot sau nu să fie identice acest lucru însemnând că obiectele de alarmă adăugate de utilizator sunt de același tip sau de tipuri distincte. De asemenea acest view cuprinde și o listă cu view-uri de mesaje. Prin intermediu acestei liste de mesaje utilizatorul poate să urmărească evoluția alarmelor pe durata cât este plecat. Această lista folosește view-uri diferite de cele ale alarmelor. Astfel view-ul de alarme devine un container pentru alte view-uri, acestea din urmă fiind cele cu care utilizatorul interacționează cel mai des în acest modul.

Dintre toate componentele unui modul, view model-ul este probabil partea cu cea mai mare responsabilitate. Pe lângă faptul că intermediază legătura dintre view și model în această componentă este încapsulată logica după care un modul lucrează. View model-ul modulului de alarme este practic centrul de comanda al întregului modul. În această componentă se tratează evenimentele la care modulul s-a abonat restul fiind ignorate și de asemenea se transmit evenimente către celelalte mdoule. View model-lu este responsabil cu schimbarea stării obiecteleor de alarmă, acest lucru putând fi vizibil în view. Atunci când, după ce s-au făcut verificările, o alarmă trece din starea de off în starea de on view model-ul este notificat printr-un evenimvent și face ajustările necesare pentru ca acest lucru să fie semnalizat vizual, în interfața modulului, și auditiv, prin intermediul unui sunet distinctiv care începe să fie redat odată cu schimbarea de stare a obiectului de alarmă prezentată anterior. Așa cum am amintit mai sus utiliztorul poate să facă modificări asupra obiectelor de alarmă existente. Odată ce modificările au fost făcute view model-ul este cel care cheamă metodele de salvare a datelor în baza de date a aplicației.

Fiecare dintre componentele prezentate are propria responsabilitate. Astfel este mai ușor pentru dezvoltator să izoleze cauzele care duc la funcționarea anomală a aplicației. Totodată folosind acest pattern este mult mai facil să se adauge funcționalitate nouă chiar în interiorul unui modul fără ca dezvoltatorul să fie forțat să își ia măsuri de precauție atât de drastice ca și cele care îi erau necesare atunci când cele trei componente ale MVVM-ului ereu amestecate într-o meta construcție.

În subcapitolul anterior am prezentat Unity, una dintre tehnologiile pe care Microsoft le pune la dispoziția dezvoltatorilor pentru a putea dezvolata într-o manieră cât mai elegantă aplicații care, intern, pot fi separate în module. Fundamentul lui Unity îl constituie pattern-ul dependency injection, pattern care va fi prejentat în cele ce urmează.

Atunci când se construiesc clase și se înpart responsabilitățile între aceste clase construite, de foarte multe ori, se întâmplă ca între aceste clase să se dezvolte inerdependințe. Datorită acestui lucru este foarte greu ca programatorul să interacționeze cu codul scris fără ca undele din funcționalitățile implementate să nu aibă de suferit sau, chiar mai rău, codul să nu mai îndeplinească sarcina pentru care a fost scris. Una dintre soluțiile folosite pentru a micșora nivelul de dependență între clasele aplicației este dependency injection. Ideea de bază a acestui concept este aceea de a avea un obiect care se ocupă de popularea obiectelor din diferitele clase ale aplicației preluând astfel responsabilitatea de a-și rezolva dependințele, responsabilitate care revenea claselor. Exită trei stiluri principale pentur dependency injection: injectarea prin constructor, injectarea folosind o metodă de tip setter și injectarea prin interfață. Aplicația folosește injectarea prin intermediul Unity, un alt tip de dependency injection. Toare aceste aceste forme ale pattern-ului vor fi prezentate în cele ce urmează.

Prima formă de dependency injection este injectarea prin constructor. Fiecare clasă are o metodă numită constructor care este apelată atunci când un obiect de tipul acelei clase este creat. Această metodă este responsabilă cu instanțierea celorlalte obiecte pe care clasa le conține și cu atribuirea de valori variabilelor existente. Astfel clasa curentă are responsabilitatea de aloca spațiul de memorie necesar fiecărui tip pentru obiectele care o compun și de a chema constructorii celorlalte obiecte. Ca oricare altă metodă constructorul poate să aibă parametrii cu care să fie apelat. Astfel putem trimite prin parametrii constructorului referințe la obiectele din care este compusă clasa iar în interiorul acestuia să se facă atribuirile necesare fără a mai fi nevoie de alocare de memorie și apelarea constructorilor pentru obiectele componente. Acest lucru a fost făcut înainte de de instanțierea clasei curente de către un alt obiect. În Anexa 4 este prezentat un exemplu de injectare prin constructor.

Cea de-a doua modalitate de injectare este injectarea prin intermediul unei metode de tip setter. O metodă de tip setter este o metodă a cărei responsabilitate este aceea de a seta valoarea unei variabile sau de a atribui referința unui obiect exterior clasei unui obiect din interiorul clasei. Spre deosebire de de injectarea prin constructor acest tip de injectare necesită un obiect creat pentru a putea apela metoda care se ocupă cu atribuirea referințelor obiecteleor. Cel care se ocupă de instanțierea clasei este obiectul care face injectarea. Metoda setter a clasei în care se injectează obiectele componente poate să primească un singur obiect și să se ocupe de instanțierea unui singur obiect în clasa din care face parte sau poate primi mai multe astfel de obiecte pe care să le atribuie. Numărul maxim obiecte pe care le poate primi ca parametrii nu este limitat însă este necesar ca această metodă să aibă cel puțin un parametru. În Anexa 5 este prezentat un exemplu pentru acest tip de injectare.

Cel de-al treilea tip de injectare este injectarea prin intermediul unei interfețe. O interfață conține doar semnăturile metodelor pe care o clasa sau o structură le implementează. De asemenea o interfață poate conține și variabile sau obiecte, toate acestea fac parte din definiția interfeței. Atunci când o clasă implementează interfața acea clasă este obligată să implementeze toate metodele pe care interfața le conține în definiția ei. De asemenea toate variabilele pe care interfața le conține vor fi aduse și în clasa implementatoare. Această metodă de injecate este foarte seimilară cu injectarea folosind o metodă de tip setter. În interfață se definește metoda care va fi folosită pentru injectare împreună cu parametrii pe care aceasta îi primește. Clasa care va implementa interfața va face atribuirile dintre obiectele componente și parametrii pe care metoda îi primește. Obiectele care sunt trimise ca parametrii sunt inițializate de către o altă entitate din cadrul aplicației. Anexa 6 va prezenta un exemplu pentru acest tip de injectare de compinente.[12]

Anterior am prezentat caracteristici generale ale pattern-ului depentdency injection și cele trei tipuri principale de injectare de obiecte în interiorul unei clase. Aplicația pe care am construit-o se folosește din plin de această facilitate prin intermediul Unity. Toate obiectele de care este nevoie în aplicație sunt înregistrate în containerul Unity folosind metodele de înregistrare pe care acesta le pune la dispoziție și anume RegisterInstance și RegisterType. Diferența principală dintre aceste două metode de a înregistra obiecte în container este faptul că prima metodă face înregistrarea în manieră single tone iar cea de-a doua metodă face o înregistrare normală. Pentru injectarea obiectelor acolo unde este nevoie de acestea se folosește metoda Resolve a containerului Unity. Un neajuns pe care îl are acest container este faptul că fiecare clasă care se vrea a fi înregistrată în container trebuie să implementeze o interfață proprie pentur a fi înregistrată în container. Astfel înregistrarea se face folosind interfața clasei și clasa propriu-zisă. Mai apoi metoda Resolve se va folosi de interfața clasei pe care dorim să o obținem din container pentru a aduce referința unui obiect din container. În cazul în care clasa a fost înregistrată ca și single tone containerul va crea un singur obiect a cărui referință o va returna ori de câte ori se ca cere acea clasă din container. În caz contrar containerul va crea un nou obiect și îi va returna referința ori de câte ori se va cere un obiect de tipul clasei care a fost înregistrată în container. În Anexa 7 este prezentat un exemplu de folosire al containerului Unity pentru a injecta obiecte în diferite clase, atât obiecte single tone cât și obiecte normale.

Un alt pattern pe care aplicația îl folosește este Inversion of Control; vom referi acest pattern prin intermediul acronimului IoC. IoC presupune inversarea fluxului controlului aplicației în comparație cu fluxul pe care îl are controlul într-o aplicație de tip procedural. Pentru tipul procedural controlul este deținut de către consumatorul de servicii oferite de diferitele componente ale aplicației. Acest consumator de servicii este nevoit în acest caz să știe detalii de implementare pe care le au furnizorii acestor servicii. Se pune astfel întrebarea dacă este absolut necesar pentru consumator să știe aceste detalii. Răspunsul la această întrebare este nu. Pentru a rezolva această problemă se folosește o intefață.

Toată logica pe care consumatorul o folosește și care aparține furnizorului de servicii este, de obicei, extrasă într-o interfață pe care furnizorul o implementează. În continuare consumatorul va interacționa cu un obiect care are tipul acelei interfețe și va avea acces doar la comportamentul pe care interfața îl definește. Ceea ce consumatorul este faptul că el interacționează cu un obiect de tipul intefeței și nu cu furnizorul chiar dacă obiectul cu care interacționează este de faptu un furnizor. Totuși consumatorul nu va mai avea acces la detaliile de implementare ale furnizorului.

Unul dintre beneficiile acestei abordări o reprezintă decuplarea consumatorului de furnizor. Astfel consumatorul nu mai este legat de detaliile de implementare ale furnizorului. Un alt beneficiu al acestui tip de implementare îl constituie creșterea flexibilității. Dacă se dorește schimbarea furnizorului trebuie avut în vedere doar ca acesta să implementeze interfața definită anterior. Consumatorul nu va fi nevoit să își modifice implementarea din moment ce el depinde de ceva care implementrază acea interfață.[13]

În aplicație IoC a fost folosit pentru logul mesajelor interne ale aplicației(partea de log a aplicației va fi prezentată ulterior). Clasa care se ocupă cu logul mesajelor(prin logul unui mesaj mă refer la procesul prin care acesta este preluat de un obiect specializat și scris într-un fișier) și clasa care face scrierea în fișier se află în două proiecte diferite. Proiectul ce conține clasa care se ocupă cu logul mesajelor este proiectul care conține funcționalități de bază în aplicație și este referențiat de celelalte proiecte astfel că acest proiect nu poate referenția alte proiecte. Proiectul care conține clasa ce se ocupă de scrierea în fișier este un proiect de calcule și referențiază proiectul de utilități amintit anterior. În cadrul proiectului de utilități a fost definită o interfață care a fost implementată mai apoi de către o clasă din cadrul proiectului de calcule. Această clasă a fost mai apoi înregistrată în containerul Unity. Clasa care se ocupă de log cere și primește din containerul Unity un obiect de tipul interfeței menționate. Folosind acest obiect el apelează mai apoi metoda care se ocupă de scrierea mesajelor. Chiar dacă obiectul primit a fost o instanță a clasei din proiectul de calcule clasa care se ocupă cu logul mesajelor folosește ceva ce implementează interfața definită anterior și are acces doar la comportamentul definit în cadrul interfeței.

Una dintre cerințele care s-au formulat pentru această aplicație a fost ca ea să poată să ruleze în aproapre orice situație și să poată să depășească orice situație excepțională care poate să intervină pe parcursul rulării aplicației. Prin situații excepționale mă refer atât la erori care apar datorită lipsei unor argumente sau a operațiilor ilegale pe care procesorul le poate executa dar și la erorile care se datorează introducerii de date eronate de către operatorul uman. Toate aceste excepții trebuie rezolvate de către dezvoltator. Însă în cele mai multe situații operatorul nu este conștient de faptul că un asemenea eveniment s-a întâmplat sau nu poate să redea exact pașii pe care i-a urmat pentru a obține acea excepție. Pentru a evita aceste situații a fost construit un sistem de log a mesajelor excepțiilor care pot interveni pe parcursul rulării aplicației.

În oricare dintre componentele aplicației pot să apară astfel de situații excepționale ca și cele amintite anterior. Fiecare mesaj pe care sistemul de log îl primește este format din trei ccomponente: timpul la care a apărut situația în cauză, nivelul utilizatorului pentru care este destinat mesajul și mesajul propriu-zis. Pentru un un mesaj se poate seta unul dintre cele trei nivele ale utilizatorului: None, User și Developer. În funcție de nivelul pe care îl are atunci când ajunge la sistemul de log mesajul este trimis pentru a fi afișat sau pentru a fi scris întru-un fișier. Pentru nivelul None mesajul este trimis pentru a fi scris în consolă și este folosit pentru mesajele de notificare asupra scurgerii intervalului de timp necesar pentru achiziționarea și prelcrarea datelor. Mesajele care au setat nivelul pe User sunt de obicei mesaje care sunt importante pentru operator. Spre exemplu atunci când unul dintre module este inițializat cu setările de bază userul este anunțat de acest lucru întrucât este important să aibă cunoștiință de faptul că acel modul va lucra cu valorile de bază și nu cu valori pe care acesta le-a setat. Toate mesajele pentru care este setat nivelul User vor fi trimise de sistemul de log către modulul de alarme unde vor fi afișate în lista de mesaje a modulului. Singurele mesaje care sunt trimise către de către sistemul de log pentru a fi scrise sunt mesajele care au setat nivelul Developer. Aceste mesaje trebuie ca să ajungă la dezvoltator întrucât, de cele mai multe ori, nimeni nu sesizează faptul că s-a produs o neregulă în timp ce aplicația rulează. Pentru fiecare zi este creat un fișier în care vor fi scrise toate mesajele destinate dezvoltatorului. Fișierul este schimbat la ora 00:00 pentru cazul în care aplicația rulează mai mult timp sau este creat/deschis atunci când aplicația pornește.

Clasa care se ocupă cu log-ul mesajelor este înregistrată în containerul Unity fiind astfel accesibilă orice modul. Atunci când este nevoie de un obiect al acestei clase el este injectat folosind Resolve. Metoda prin intermediul căreia se face log-ul unui mesaj primește ca si parametrii nivelul mesajului, timpul la care acesta a apărut și mesajul. Mai apoi este evaluat în funcție de nivel și trimis mai departe pentru a putea fi afișat sau scris în fișier.

Scrierea în fișier este realizată de către o clasă aflată în procesul de calcule. Întrucât sunt multe instanțe ale clasei de log prezente în același timp în aplicație scrierea în fișier nu poate fi făcută în cadrul acestei clase deoarece întrucât se vor apărea sitații în care mai multe obiecte vor deschide fișierul pentru a scrie iar acesta să fie deschis creindu-se astfel un blocaj al aplicației. Astfel toate mesajele care sunt destinate dezvoltatorului sunt trimise spre un obiect al clasei procesului de calcule iar acest obiect se va ocupa cu scrierea mesajelor în fișier eliminând astfel blocajele care ar putea să apară datorită încercărilor de a deschide un fișier deja deschis.

Anterior menționam faptul că modulele aplicației comunică între ele folosind evenimente. Toate aceste evenimente sunt coordonate de o clasa pe care o vom numi centru de evenimente. Această clasă este responsabilă de coordonarea tuturor evenimentelor pe care modulele aplicației doresc să le trimită în cadrul aplicației.

Întrucât toate modulele au nevoie de un același coordonator pentru evenimente centrul de evenimente este înregistrat ca și obiect de tip single tone în containerul pe care Unity îl pune la dispoziție. Apoi, pentru fiecare modul care are nevoie de centrul de evenimente, acesta este injectat folosind metoda Resolve pe care Unity o pune la disopziție. Astfel fiecare modul poate să trimită în interiorul aplicației evenimente care vor fie coordonate de o singură entitate.

Folosind centul de evenimente se pot realiza trei acțiuni legate de un anumint eveniment. Prima acțiune este Publish. Folosind această metodă un modul trimite un eveniment în mediul aplicației. Numărul de evenimente le care un modul le poate trimite nu este limitat însă trebuie precizat faptul că evenimentele vor fi tratate în ordinea în care acestea au fost trimise în aplicație. Cea de-a doua acțiune care se poate realiza folosind centrul de evenimente este abonarea la unul sau mai multe evenimente; abonarea se face prin intermediul metodei Subscribe. Folosind metoda Subscribe un modul sau o clasă este notificată atunci când altcineva a trimis un eveniment de un anumit tip în cadrul aplicației. Tot prin intermediul metodei Subscribe se face legătura dintre evenimentul recepționat și funcționalitatea pe atașată acestui eveniment(prin funționalitate mă refer la o metodă în cadrul căreia se fac anumite prelucrări folosind date pe care obiectul evenimentului le poate conține). Cea de-a treia acțiune care se poate realiza folosind centrul de este Unsubscribe. În cazul în care, după ce pentru un anumit eveniment s-a executat funcționalitatea aferentă metodei atașate, sau, modulul/clasa nu mai are nevoie să primească notificări cu privire la un anumit eveniment se poate realiza dezabonarea de la notificările acelui eveniment folosind metoda Unsubscribe. Astfel nu se mai execută funcționalitatea atașată acelui eveniment întrucât nu se mai primesc notificările aferente acelui eveniment. La fel ca și metoda Subscribe, metoda Unsubscribe poate fi folosită pentru unul sau mai multe tipuri de evenimente. Un lucru important de precizat în legătură cu centrul de evenimente este faptul că acesta poate opera doar cu obiecte de tipul EventArgs. Astfel fiecare clasă folosită în cadrul sistemului intern de evenimente al aplicației trebuie să fie derivată din clasa EventArgs, clasă pe care .Net Framework o conține.

Un ultim aspect pe care aș dori să îl prezint legat de partea arhitecturală a aplicației este cel legat de setările modulelor. Fiecare dintre modulele aplicației are anumite opțiuni care se doresc a fi salvate. De asementea, între sesiunile de rulare a aplicației trebuie salvate și listele cu parametrii pe cate fiecare modul le conține pentru ca la următoarea rulare acestea să nu trebuiască să fie refăcute. Pentru aceste necesități existente am ales ca și modalitate de salvare a setărilor fiecărui modul serializarea în format XML, una dintre modalitățile de serializare pe care platforma .Net le suportă.

Fiecare clasă care participă la construirea setărilor unui modul are declarat, înainte de definirea clasei, atributul Serializable. Acesta specifică faptul că acea clasă poate să fie convertită la o altă formă pentru ca starea ei să fie salvată. De asemenea fiecare clasă trebuie să declare un constructor implicit în cazul în care există deja un constructor explicit, în caz contrar procesul de deserializare a datelor ar putea să fie periclitat. Acest constructor este necesare pentru crearea unui nou obiect de tipul clasei pentru care se face deserializarea.

Operațiile de salvare și recreare a obiectelor de setări corespunzătoare modulelor sunt realizate de către o clasă construită special pentru acest scop. Această clasă este înregistrată ca și single tone în containeru Unity astfel pentru toate modulele există un singur obiect care se ocupă de servirea cererilor de salvare care vin de la modulele aplicației. Pentru simplitate voi referi, în cele ce urmează, clasa care se ocupă de obiectele de setări ale modulelor ca și manager de setări. Acest manager conține o listă cu obiectul de setări specific fiecărui modul. Atât operațiile de salvare cât și operațiile de încărcare a setărilor unui modul se fac folosind aceste obiecte. De asemenea modulele lucrează cu referințe ale obiectelor pe care mangeru de setări le conține.

Atunci când aplicația pornește este necesară încărcarea setărilor tuturor modulelor. Aceste setări se găsesc în fișiere de tip XML. Aceste fișiere se găsesc în diferite locații de pe disc, locații care sunt păstrate într-un fișier aflat în folderul unde aplicația este instalată. Acel fișier este citit line cu linie iar pentru fiecare citire se crează un obiect de setări care este stocat în lista de obiecte a managerului de setări. După ce obiectul de setări este pus în listă se trimite un eveniment prin care se anunță faptul că un obiect de setări a fost încărcat, eveniment care mai conține și tipul acelui obiect. Atunci când modulul căruia i se porivește acel obiect recepționează evenimentu va folosi un obiect de tipul managerului de setări pentru a-și însuși acel obiect.

Atunci când un modul dorește să salvze anumite setări apelează, din managerul de setări, metoda de salvare pentru care specifică tipul modulului care dorește salvarea. Pe baza acestui tip managerul apelează, mai departe, metoda de serializare a datelor folosind obiectul de setări corespunzător. După procesul de salvare a datelor nu mai este necesară trimitera unui nou eveniment de încărcare deoarece modulul are deja ultimele setări pentru care a apelat salvarea.

2.3 Redis

Redis este o alternativă la bazele de date convenționale, cu sursă deschisă și cu structură asemănătoare dicționarelor din C#; Redis stochează datele în perechi de tip cheie – valoare. Redis este „in memory database”(o bază de date Redis este prezentă în memoria calculatorului) micșorând timpul necesar prelucrării unei cereri. De asemenea trebuie menționat faptul că în cadrul Redis nu există modelul relațional pe care îl regăsim în numeroase baze de date. Singura relație care se stabilește în Redis este legătura dintre cheie și valoarea pe care o reprezintă.

Pentru stocarea datelor Redis folosește perechi cheie – valoare. Fiecare dintre cheile folosite pentru a stoca anumite date este unică. Valorile aferente acestor chei pot avea, totuși, doar cinci tipuri structurale: stringuri, liste, mulțimi ordonate și mulțimi neordonate, și structuri de tip hash. Structurile de tip string sunt corespunzătoare atât șirurilor de caractere cât și numerelor întregi sau reale. Pe datele de tip string pot fi făcute operații cu care majoritatea programatorilor sunt familiari pentru tipul de date string. Pentru datele reale sau întregi operațiile permise sunt doar cele de incrementare sau decrementare a valorii. Structurile de tip mulțime neordonată sunt structuri în care lipsesc criteriile de ordonare a elementelor iar aceste structuri sunt compuse doar din structuri de tip string care sunt unice în cadrul muțimii. Mulțimile ordonate conțin, în schimb, perechi formate dintr-un string și un numar real, aceste perechi fiind ordonate după valoarea pe care o are numărul real. Operațiile principale care se pot face pe aceste două structuri sunt operațiile de adăugare, ștergere, selectare a unui element precum și operațiile matematice de intersecție, reuniune și diferență, ultimele trei operații aplicându-se doar pentru mulțimile neordonate. Structura de date de tip hash este o structură de date care conține perechi neordonate de tip cheie – valoare. Operațiile care se pot realiza pe această structură sunt operațiile de adăugare, ștergere și returnare a unui element dar și returnarea întregii structuri. Structura de tip listă este o structură în care se înlănțuiesc o serie de structuri de tip string. Operațiile aferente acestei structuri sunt opetațiile de adaugare a unui element folosind oricare dintre cele două capete ale listei, scoaterea unui element din listă, ștergerea sau căutarea unui element folosind ca și criteriu de selecție valoarea elementului. Pentru fiecare dintre aceste structuri Redis vine cu o serie de comenzi specifice, comenzi care nu vor fi prezentate în această lucrare dar care pot fi găsite în cartea folosită ca și suport pentru acest subcapitol.[6]

Datorită faptului că Redis este structurat după modelul unui dicționar accesul la date este foarte rapid. Pentru a motiva acestă afirmație voi folosi un exemplu destul de elocvent, exemplu pe care Josiah L. Carlson îl dă atunci când povestește despre prima întâlnire cu Redis. Pentu o bază de date relațională cu aproximativ 60.000 de intrări, căutarea uneia dintre aceste intrări poate dura 15 – 20 de secunde. Pentru Redis, aceeași căutare scade la ordinul milisecundelor, rezultatul căutării fiind întors în aproximativ 50 de milisecunde. Această diferență semnificativă între timpii de căutare necesari unei baze de date cu model relațional respectiv timpul de căutare necesar Redi a fost, probabil, principalul atu care a înclinat balața în favoarea folosirii în aplicație a unei baze de date de tip Redis, în detrimentul oricărei alte baze de date cu model relațional disponibilă la momentu curent în industria programării.

Uneori este necesar ca Redis să poată procesa mult mai multe cereri de citire decât poate să proceseze un singur server Redis. Pentru aceste necesități Redis are capacitatea de replicare. Acest lucru înseamnă faptul că alte servere pot primi o copie continuă a datelor care sunt scrise astfel ca aceste replici să fie capabile să managerieze cererile de citire care se fac iar între serverul care trimite datele și celelalte servere Redis se stabilește o relatie de tip mastre/slave. Atunci când are loc sincronizarea dintre un server master și unul slave toate datele pe care masterul slave le are la momentul începerii sincronizări sunt golite pentru a putea să primească datele de la serverul master.

O altă caracteristică importantă pe care Redis o are este persistența datelor. După cum am menționat la începutul acestui capitol Redis este o bază de date care se află în memoria calculatorului. Totuși datele trebuie salvate înaintea închiderii sistemului sau să fie disponibilă cea mai recentă salvare în cazul în care se întâmplă situații neprevăzute.

Unul dintre sistemele pe care Redis le pune la dispoziție pentru salvarea datelor este sistemul de snapshot. Prin intermediul acestui sistem, la anumite intervale de timp, se crează o copie a datelor conținute de către baza de date care rulează în memorie. Această copie este ținută, la rândul ei, în memorie până când se realizează următoarea copie. În momentul în care s-a mai creat încă o copie cea precedentă este scrisă într-un fișier pe disc, fișierul fiind plasat într-o locație pe care o alege programatorul. Dacă între două copii se întâmplă unele situații neprevăzute(sistemul pică din diferite motive legate de hardware sau software, are loc o pană de curent etc.) atunci ultima copie creată se pierde întrucât aceasta se afla în memoria RAM a calculatorului. În timpul creării copiei Redis oprește procesarea oricăror cereri de citire sau scriere pentur a asigura integritatea lor. Abia după ce s-a încheiat procesul de cerare a copiei se reia procesul de servire al cererilor de citire/scriere. Există diferite moduri de a începe procesul de creare a unei copii a datelor. Dintre comenzile folosite pentru începere procesului de creare a unei copiii amintesc bgsave, save precum și configurarea Redis folosind save x y, unde x reprezintă numărul de secunde care au trecut de la încheierea cu succes a reării ultimei copii iar y reprezintă numărul de operații de scriere care trebuie să fie realizate în intervalul x pentru ca Redis să înceapă un alt procecs de salvare a datelor. Un alt mod prin care Redis poate iniția procesul de salvare a datelor este primirea unui semnal de închidere din partea sistemului de operare sau primire unui semnal de terminare a procesului Redis. În aceste cazuri Redis blochează canalele pe care clienții fac cereri apoi salvează datele. După ce aceste operații au fost încheiate sistemul poate să continue executarea următoarelor sarcini sau poate continua cu procesul de închidere.

Există situații în care, în timpul procesului de scriere a datelor, se primesc mai multe cereri de citire. În urma servirii cererilor de citire datele returnate pot deveni inconsistente sau chiar eronate. Aceste situații pot fi abordate în diferite modui iar în cele ce urmează voi prezenta două dintre aceste moduri.

O primă soluție pentru aceste situații este folosirea unui semafor de numărare. Un semafor de numărare limitează numărul de procese care pot accesa concurent o resursă comună. Prin crearea unui semafor de numărare cu limita de 1 se crează un sistem de blocare pentru toate celelalte procese care încearcă să acceseze resursa. Astfel doar un singur proces are acces la acea resursă urmând ca după eliberarea semaforului un alt proces să blocheze accesul celorlalte procese la resursă până când va fi din nou eliberat semaforul. La fel ca toate celelalte tipuri be blocare, un semafor de numărare poate fi achiziționare și eliberat. În momentul în care limita de procese a fost atinsă iar un alt proces dorește să folosească resursa, înceracrea de achiziționare a semaforului va eșua deoarece numărul de procese care deja folosesc acea resursă a atins limita. Pentur acest tip de semafoare trebuie definite două metode și anume metoda care va fi apelată pentru achiziționarea semaforului și metoda de eliberare a acestuia.[7]

Cea de-a doua modalitate de evitare a returnării unor date eronate este folosirea unui semafor care se resetează la un anumit interval de timp. Odată ce acest tip de semafor a fost achiziționat de un proces el rămâne blocat până când cunata de timp expiră, resetându-se starea acestuia. De cele mai multe ori, pentru acest tip de semafor, se folosește ca și cuantificator de timp ceasul sistem. Astfel la fiecare „bătaie” a ceasului sistem starea semaforului este readusă la starea inițială.

Redis are și alte mecanisme prin care se poate asigura accesul concurent la resurse partajate pentru asigurarea corectitudinii darelor returnate. Cele două tipuri de semafoare prezentate anterior sunt două dintre modalitățile existente pentur a garanta integritatea și corectitudinea datelor.

Așa cum am mentionat anterior, în acest subcapitol, Redis este o bază de date care se află în memoria computerului. Cu cât se adaugă mai multe date în această bază de date cu atât memoria folosită crește. Pentru a evita epuizarea memoriei disponibile se folosesc o serie de metode de reducere a memoriei utilizate. În cele ce urmează voi prezenta trei dintre aceste metode folosite pentru reducerea memoriei utilizate și creșterea performanțelor.

Prima dintre tehnicile care pot fi utilizate pentru micșorarea cantității de memorie folosite este utilizarea unor structuri de date mai compacte și mai scurte. În această manieră Redis poate stoca aceste structuri întru-un mod mult mai eficient din punct de vedere al memoriei utilizate. Atunci când se folosesc structuri ca mulțimi, liste sau hash-uri Redis poate opta pentru folosirea unei structuri mai compacte numită ziplist.

O structură de tip ziplist este o versiune nestructurată a oricăriei dintre structurile amintite anterior. Ce face un ziplist să fie nestructurat este faptul că acesta este o versiune serializată pentru una din cele trei structuri amintite. Aceasta poate fi deserializată pentru fiecare operație de citire si parțial reserializată la fiecare operație de scriere.

Pentru fiecare dintre structuririle de date comune(mulțimi, liste sau hash-uri) există o anumită limitarea număruli de elemente precum și a numărului maxim de biți pe care valorile pot fi stocate pentru a putea face conversia de la o structură la ziplist. Acestea au următoarele valori: pentru structurile de tip listă numărul maxim de elemente este 512 iar fiecare element poate avea valoarea stocată pe maxim 64 de biți; structurile de tip hash au aceleași limitări ca și cele de tip listă iar pentru structurile de tip mulțime(pentru structurile de tip mulțime se iau în considerare doar mulțimile ordonate) numărul maxim de elemente este de 128 în timp ce limita pentru valoare rămâne neschimbată. Aceste limite sunt cele pe care Redis le setează în mod implicit și sunt configurabile.

Mulțimile neordonate sunt convertite într-o altă strcutură pentru a putea salva memoria și anume într-un vector. Acest lucru este aplicat, de obicei, pentru mulțimi de numere reprezentate în baza 10. La fel ca și în cazul structurii ziplist acești vectori sunt utilizați pentru mulțimi care nu conțin mai mult de 512 elemente(ca și în cazul anterior limita este configurabilă). De asemenea, trebuie precizat faptul că vectorul este un vector ordonat facilitând astfel rapiditatea aplicării operațiilor specifice mulțimilor.

În cazul în care se depășeste limita de elemente care a fost impusă structurile compacte sunt convertite la tipul de bază. Acest lucru se întâmplă deoarece după un anumit număr de elemente viteza de acces pentru structurile compacte este mult mai scăzută decât pentru structurile normale de stocate.

Cea de-a doua metodă de micșorare a memoriei folosite de Redis este procesul de sharding. Prin intermediul acestui proces datele sunt împărținte între mai multe pachete și sunt trimise în mai multe locații în funcție de locația care a fost asignată acelui pachet. Pentru fiecare dintre structurile suportate de Redis există o abordare diferită pentru a realiza acest proces de împărțire a datelor în pachete și asignarea acestor pachete în locațiile destinate lor. Detaliile legate de aceste implementări nu vor fi incluse în cadrul acestei lucrări întrucât nu fac parte din subiectul tratat în acestă lucrare.[7]

Cea de-a treia modalitate de a micșora cantitatea de memorie folosită este aceea de a stoca informație care a fost, în prealabil, compactată. Această metodă de micșorare a cantității de memorie folosite este utilizată în aplicațiile care păstrează o evidență a utilizatorilor precum și anumite informații despre aceștia. Aceste aplicații au nevoie ca în baza de date să păstreze o listă cu țările în care este disponibilă aplicația, regiunile acesor țări și alte informații legate de utilizatorii aplicației. Astfel cantitatea de date pe care baza de date trebuie să o păstreze crește consiferabil. În acest caz datele vor fi întâi condensate iar după ce procesul de condensare s-a încheiat datele vor fi memorate.

Capitolul 3. Prezentarea aplicației

În capitolul anterior am prezentat tehnologiile folosite pentru dezvoltatea aplicației. De asemenea am făcut și o prezentare generală a Redis, baza de date pe care aplicația o folosește. După toate aceste aspecte teoretice a sosit timpul pentru prezentarea aplicației. În acest capitol voi prezenta aplicația din trei puncte de vedere diferite: în primul subcapitol voi face o prezentare a aplicației din punct de vedere al utilizatorului, în cel de-al doilea subcapitol voi face o prezentare a bazei de date iar în cel de-al treilea subcapitol voi prezenata aspecte tehnice ale aplicației.

3.1 Prezentarea aplicației din punct de vedere al utilizatorului

Principalul judecător al unei aplicații este utilizatorul. El este acela care, intrând în interacțiune cu produsul software, îi determină utilitatea valoarea și utilitatea. Pentru această aplicație am utilizat un design cat mai simplu fără elemente care să obosească ochiul iar navigarea între module să fie cat mai simplu de realizat. Să începem, așa dar cu prezentarea aplicației.

Figura 3.1.1. Procese

Prima interacțiune a utilizatorului cu aplicația este prin intermediul modulului de setări. Figura 3.1.1 prezintă pagina de setări în care sunt monitorizate procesele aplicației. Pentru ca aplicația să înceapă rulatea trebuie ca procesele aplicației să fie pornite. Pentru acest lucru există buton de pornire sau oprire a tuturor proceselor și, de asemenea, fiecare proces în parte are un buton de unde poate să fie pornit individual, separat de restul proceselor. Pentru fiecare proces se precizează și faptul că este pornit sau oprit.

Figura 3.1.2. Fișierele XML

În Figura 3.1.2 este prezentată cea de-a doua pagină a modulului de setări. Această pagină este folosită pentru setarea căilor pe care aplicația le va folosi atunci când va salva un fișier de setări pentru un modul. Implicit aplicația salvează aceste fișiere într-un folder pe care îl crează pe partiția C a sistemului de operare. Interacținea utilizatorului cu această pagină este foarte redusă întrucât se preferă setările aplicației. Totuși există cazuri excepționale în care se dorește salvarea uneu anumite configurații a unui sau mai multor module într-o anumită locație. În acest caz se vor schimba căile pentru fișierele corespunzătoare apoi se va apăsa butonul Save din colțul stânga, sus al ferestrei pentru ca salvarea setărilor în noua locație să aibă loc.

Figura 3.1.3. Cromatografia

În Figura 3.1.3 este prezentată interfața modulului de cromatografie. Cu ajutorul acestui modul operatorul poate să urmărească concentrația de gaze dintr-o monstră pe care o analizează. O monstră este, de obicei, formată din hidrogen, oxigen, hidrogen sulfurat, metan și alte gaze care rezultă din procesul de forare. Monstra este amestecată cu un gaz de procesare iar apoi, în urma descompunerii în comonente, valorile pentru fiecare dintre componentele generale ale monstrei vor fi citite de un senzor. Rezultatuele vor fi afișate sub forma unui grafic pe o durată de 40 de secunde. După încheierea celor 40 de secunde graficul precedent este păstrat și i se setrază o nuanță de gri deschis iar noul grafic este desenat. Această decizie de a păstra graficul precedent a fost luată pentru ca operatorul să aibă un reper pentru viitoarea afișare. Astfel se poate observa mult mai repede când au loc schimbări în compoziția monstrelor analizate.

Figura 3.1.4. Hook position

Următorul modul pe care aș dori să îl prezint este modulul HookPos. Acest modul este prezentat în Figura 3.1.4 Datoria principală a acestui modul este să monitorizeze poziția pe care o are cârligul macaralei sondei. Monitorizarea constantă a cârligului macaralei este necesară pentru ca un alt modul, și anume modulul Depth să funcționeze corect. De asemenea monitorizarea cârligului este importantă pentru siguranța celor care sunt lângă macaraua sondei. Folosind puncte de calibrare predefinite, monitorizarea cârilgului se face cu date controlate dinaint. Orice deviere de la aceste date poate însemna o problemă gravă. Punctele de calibrare pot fi adăugate folosind butonul Add new point; va fi adăugat un punct de coorodnate 0. Apoi operatorul va schimba valorile inițiale cu valorile pe care le dorește. Abia după ce valorile inițiale vor fi schimbare punctul nou adăugat va fi introdu în grafic și în procesul de calcule. Poziția cârligului este reprezentată de elipsa roșie care se află la baza suprafeței de desenare. Coorodnatele acestei elipse sunt preluate din procesul de calcule, după ce un ciclu al procesului s-a încheiat. Valorile pentru aceste coorodnate sunt mapate pentru a se potrivi suprafeței de desenare.

Figura 3.1.5. HLPT

Modulul HLPT(HookLoad, Press, Troque), prezentat în Figura 3.1.5, este dator cu monitorizarea unor parametrii legați de sapa sondei precum și de cârligul acesteia. Acești parametrii sunt monitorizați pentur a observa evoluția sapei pe perioada forării. La fiecare 27 de metrii se adaugă o nouă secțiune la sapa sondei. Astfel, cu înaintarea forării, numărul de secțiuni crește și astfel încărcătura pe care cârligul o susține se mărește(HookLoad). Dacă încărcătura din cârlig este prea mică atunci există posibilitatea ca sapa să fie blocată sau ruptă. Dacă încărcătura este prea mare atunci sonda se poate probuși. Presiunea este monitorizată pentru a vedea dacă fluidul din interiorul sapei și a găurii de foraj nu ai circulă. O presiune mare în interior poate semnifica înfundarea sapei iar o presiune mare în exterior poate să producă o erupție. Ultimul parametru monitorizează numărul de rotații ale sapei într-un interfal de timp stabilit. Cu ajutorul acestui parametru se poate determina tipul stratului prin care se sapă. Toți acești parametrii au valori configurabile iar modificările sunt preluate de procesul de calcule pentru următorul ciclu de calcul.

Figura 3.1.6. Rezervoare

Modulul de rezervoare prezentat în Figura 3.6 este foarte asemănător cu cel precedent. Singura diferență între aceste două module o reprezintă numărul parametrilor monitorizați. Modulul monitorizează nivelul de fluid de forare care există în rezervoarele sondei. Cu toate că sunt 16 rezervoare, o sondă nu este nevoită să aibă acest număr de rezervoare instalate. De obicei se folosește doar un singur rezervor uriaș împreună cu un senzorul este calibrat corespunzător. De asemenea, acest modul calculează un total pentru parametrii monitorizați total al cantității de fluid pe care rezervoarele sondei îl conțin. Folosind această valoare se poate determina cantitatea totală de fluid din sistemul de forare și se poate suplimenta când aceasta scade.

Figura 3.1.7. Pompe

Fluidul de foraj este introdus în puțul de foraj prin intermediul sapei. De realizarea circuitului pentru fluidul de forare se ocupă o serie de pompe care împing fluidul de foraj, prin sapă, în interiorul puțului de forare. Modulul de pompe este prezentat în Figura 3.1.7. Pentru pompe este urmărită eficiența lor(cantitatea de fluid de forare care este introdusă în puțul de forare) dar și cantitatea de fluid care este scoasă din puțul de forare. Un alt parametru pe care acest modul îl monitorizează este numărul de curse pe care îl fac pompele într-un minut. Cu cât rotația sapei este mai mare cu atât numărul de curse al pompelor trebuie să fie mai mare pentru a putea suplini nevoile pe care procesul de foraj le are. Dacă o singură pompă nu poate să suplinească aceste nevoi o alta va fi pornită pentu ca procesul de forare să nu aibă de suferit.

Figura 3.1.8. Setări pompe

Fiecare modul care are setări mai complexe are câte o pagină pentru aceste setări. O pagină de tip popup se deschide atunci când este apăsat meniul de setări din colțul stânga sus al modulului. O asemenea pagină este prezentată în Figura 3.1.8; această figură prezintă pagina de setări a modulului de pompe. În această pagină operatorul poate selecta sau deselecta opțiuni pe care modulul le are și care vor modifica valorile rezultate în urma calculelor sau poate schimba valori existente pentru modul. Aceste setări vor fi salvate și apoi trimise procesului de calcule doar atunci când popup-ul de setări este închis prin intermediul butonului Ok. În oricare alt caz modificările nu vor fi salvate iar modulul va continua să lucreze cu valorile pe care le-a avut.

Figura 3.1.9. Calcimetria

Unul dintre componentele fluidului de forare este carbonatul de calciu. Periodic, monstre de sol sunt prelevate din fluidul de forare și sunt analizate. Pentru aceste monstre se urmărește nivelul de carbonat de calciu pe care îl conțin. În Figura 3.1.9 este prezentată interfața modulului de calcimetrie. Valoarea pentru nivelul carbonatului de calciu provenită de la procesul de calcule este mai apoi folosită în desenarea unui grafic pe care operatorul să poată detecta ușor schimbările de concentrație. Acest modul are setări destul de simpliste, setări care au putut să fie intregrate cu succes în interfața modulului astfel că nu a mai fost nevoie de o pagină separată pentru setări. O facilitate pe care acest modul o are este aceea de salvare a graficului desenat. Folosind butonul Save image se crează o imagine bitmap care este mai apoi salvată în folderul în care este instalată aplicația. Butonul Start/Stop este resopnsabil pentru pornire sau oprirea pricesului de analiza pentru monstrele colectate. Parametrul utilizat pentru acest modul este un parametru comun, folosit și în modulul HLPT sau modulul de rezervoare.

Figura 3.1.10. Fluidul de forare

Fluidul de foraj este o componentă foarte importantă pentru întregul proces de foraj. Fără această componentă ar fi imposibil de finalizat forarea întrucât frecarea crește cu cât dimensiunea puțului crește iar datorită frecării sapa poate să nu mai funcționeze sau chiar să se rupă. Acest fluid trebuie să îndeplinească anumite standarde atunci când este introdus în puț. Figura 3.10 prezintă modulul dedicat fluidului de forare. Pentru fiecare dintre aceste standarde există doi parametrii care monitorizează calitatea fluidului, unul pentru momentul când fluidul este introdus în puțul de forare iar celălatl parametru pentru momentul când fluidul iasă din puțul de forare. Toți acești parametrii sunt parametrii comuni, folosiți și pentru alte module. Pentru fiecare dintre parametrii pe care acest modul îi monitorizează se desenează un grafic atunci când operatorul selectează un parametru din lista existentă. Astfel operatorul poate monitoriza valorile pe care parametrul le are atât prin cifre cât și în mod grafic.

Figura 3.1.11. Parametrii liniari

Înafara parametrilor pe care aplicația îi are definiți inițial se pot adăuga și alți parametrii. În Figura 3.1.11 este prezentat modulul parametrilor liniari, modul care are sarcina de a facilita adăugarea parametrilor „custom” doriți de operator. Adăugarea unui nou parametru se realizează apăsând butonul din colțul dreapta, jos, al interfeței modulului de parametrii. Parametrul nou adăugat va avea setările inițiale și nu va fi introdus în sistemul de calcule. Abia după ce operatorul va face ajustările necesare, valorile pentru parametrul nou adăugat vor fi calculate și afișate în cadrul acestui modul.

Aplicația folosește foarte mulți parametrii de acest tip(rezervoarele, parametrii pentru fluidul de forare, parametrul de calcimetrie, parametrii pentru sapa și cârligul sondei). Diferența fundamentală între prametrii acestui modul și restul parametrilor folosiți în aplicație este aceea că parametrii pe care acest modul îi conține pot să fie șterși. În momentul în care operatorul consieră că un parametru adăugat nu mai este relevant acesa poate să fie șters folosind butonul Delete care se află în colțul dreapta, jos, al interfeței parametrului. Spre deosebire de parametrii acestui modul parametrii folosiți în aplicație nu au opțiunea de ștergere.

Figura 3.1.12. ECD

Figura 3.1.12 prezintă modulul ECD. ECD este un modul puțin diferit de celelalte module. Acest lucru deoarece acest modul nu este construit folosind WPF ci este o pagină WinForms. WPF oferă posibilitatea de a porta pagini construite în WinForms în cadrul unei pagini construite cu XAML. Acest modul este folosit pentru a compara datele teoretice care se află într-un fișier Excel cu datele reale care se obțin pe parcursul procesului de forare.

Figura 3.1.13. Adâncime

Procesul de forare nu are nici un sens dacă nu există informații despre adâncimea la care s-a ajuns. Modulul de adâncime, a cărui interfață este prezentată în Figura 3.1.13, se ocupă cu afișarea informațiilor legate de adâncime și totodată aici se fac ajustările necesare pentru ca totul să se încadreze în parametrii normali.

În prima parte a acestui modul se află informații legate de sapa sondei și de adâncimea la care s-a ajuns cu forarea. Informațiile pe care secțiunea Drill le curpinde sunt: poziția sapei, adâncimea de forare, poziția cârligului, greutatea cu care se apasă pe sapă, greutatea care este ținută în cârlig, numărul de prăjine care se află în puțul de forare.

Secțiunea Trip se ocupă cu monitorizarea procesului de scoatere a prăjinilor din puțul de forare sau introducerea acestora în puț după ce au fost scoase. Aceste operații sunt necesare pentru schimbarea unor componente defecte. Informațiile pe care această secțiune le oferă sunt viteza cu care scoase sau introduse prăjinile, numărul de prăjini care la care s-a ajuns(numărul de prăjini scoase sau adăugate).

Secțiunea Rop oferă informații despre modul în care decurge forarea. Pentru această secțiune informațiile inportante sunt viteza cu care se înaintează și viteza teoretică cu care se poat înainta.

Ultima secțiune, secțiunea Lag, oferă informații atât despre adâncimea la care s-a ajuns cât și despre timpul necesar fluidului de forare și gazelor să ajungă la suprafață. De asemenea, tot aici, se poate vedea adâncimea de la care s-a luat monstra de sol, volumul pe care îl are puțul de forare dar și adâncimea la care se preconizează a se găsi gaz.

Figura 3.1.14. Alarme

Ultimul modul al acestei aplicații este modulul de alarme prezentat în Figura 3.1.14 Întrucât aplicația prelucrează mulți parametrii, parametrii aflați în module diferite, operatorul nu este capabil să observe concomitent schimbări ale valorilor parametrilor din diferite module, schimbări care pot afecta buna funcționare a instalației de forare. Pentru această situație a fost construit acest modul de alarme care este responsabil de avertizarea operatorului atunci când unul sau mai mulți parametrii pentru care au fost setate alarme au schimbări ale valorilor care depășesc anumite limite impuse de operator.

Adăugarea unei noi alarme se face folosind butonul de meniu Add new… iar apoi se selectează tipul alarmei dorite. Alarma va începe să fie monitorizată odată ce se va seta un parametru, valoarea sau valorile limită, tipul și nivelul alarmei. În momentul în care o alarmă pornește va fi marcată printr-un pătrat de culoare roșie și de un semnal sonor. De asemenea un mesaj de atenționare va fi afișat pentru operator, pentru ca în cazul în care acesta este plecat să fie înștiințat de faptul că o alarmă a fost pornită cât timp el a lipsit.

Asemenea parametrilor din modulul de paramerii liniari alarmele se pot șterge prin apăsaea butonului Delete prezent în interfața fiecărui obiect de alarmă pe care utilizatorul îl adaugă. Totuși acest buton nu este prezent pentru alarmele clasificate ca fiind permanente. În cazul de față, singura alarmă permanentă este alarma care monitorizează achiziția datelor.

3.2 Prezentarea bazei de date

Baza de date pe care aplicația o folosește este o bază de date de tip Redis. Redis a fost prezentat în ultimul subcapitol al capitolului doi. Această bază de date este strucutrată după modelul dicționarelor folosite în C# și anume pentru fiecare valoare prezentă în baza de date există o cheie unică după care este identificată.

Aplicația folosește baza de date pentru a stoca rezultatele provenite în urma procesului de calcule. Mai apoi aceste valori sunt preluate din baza de date și sunt utilizate pentru trasarea graficelor sau sunt afișate utilizatorului. Un neajuns al acestei baze de date este faptul că datele sunt suprascrise, astfel că la fiecare nou ciclu de achiziție a datelor în baza de date există doar datele de la ultimul ciclu de achiziție. Acest lucru se datorează faptului că sunt utilizate chei statice în cadrul aplicației.

Fiecare dintre module conține o clasă cu date care vor fi salvate în baza de date. Pentru fiecare astfel de clasă s-a atașat un atribut care are rolul de a preciza faptul că acea clasă va fi translatată pentru Redis. Acest mod de definire a claselor este foarte asemănător cu pașii care se fac pentru a serializa obiecte în C#. În baza de date se va salva perechea formată din tipul clasei ca și cheie iar pentru valoare se va lua instanța curentă a clasei respective. La fiecare ciclu se va folosi cheia definită în cod pentru a stoca datele. Întrucât acea cheie există în baza de date valoarea pe care acea cheie o reprezintă va fi schimbată și se va atribui o nouă valoare, valoare care a fost rezultată la ciclul actual de achiziție de date. Singurul moment când această valoare nu va fi suprascrisă este momentul în care se crează baza de date. La primul ciclu vor fi adăugate cheile și valorile în baza de date urmând ca de la cel de-al doilea ciclu să se utilizeze cheile deja existente și datele să fie suprascrise.

Toate clasele care sunt utilizate ca modele pentru baza de date sunt compuse din proprietăți. La rândul lor aceste proprietăți trebuie să fie marcate de un atribut care să specifice tipul proprietății respective, în caz contrar acea proprietate nu va fi salvată în baza de date. Pentru proprietăți sunt disaponibile doua atribute: KVValue și KVObject. Primul atribut este folosit pentru proprietățile al căror tip este un tip de date primar( integer, string, float, etc. ). Cel de-al doilea atribut este folosit pentru obiectele care compun clasa model a bazei de date. Ambele atribute au rolul de a marca proprietățile care vor fi translatate pentru Redis.

Fiecare model pentru baza de date are două metode prin intermediul cărora se realizează interacțiunea cu baza de date Redis: SaveToDb și LoadFromDb. Prin intermediul metodei SaveToDb obiectul este transformat înt-un hash apoi este trimis bazei de date care îl stochează împreună cu cheia(atributul) pe care îl are definit. Cea de-a doua metodă preia hash-ul din baza de date pe baza cheii pe care obiectul o furnizează iar apoi hash-ul este translatat într-o instanță de obiect care va putea să fie folosită în aplicație.

Redis este o bază de date rezidentă în memoria RAM a calculatorului. Adăugarea de date noi la cele existente deja duce la creșterea memoriei necesare stocării datelor. În unele cazuri Redis poate să utilizeze peste 70% din totalul memoriei de care dispune computerul. Pentru această aplicație cantitatea de memeorie utilizată de baza de date rămâne constantă. Acest lucru se datorează faptului că sunt utilizate chei statice. La fiecare ciclu de achiziție de date obiectele care constituie modelele bazei de date sunt salvate cu aceleași chei. Acest lucru duce la o suprascriere a datelor existente deja în baza de date cu datele obținute în urma prelucrării valorilor brute obținute de la placa de achiziție.

Persistența datelor în cazul bazei de date folosite de aplicație se realizează diferit față de bazele de date obijnuite. La fiecare șaizeci de secunde se verifică numărul de scrieri care s-au făcut în baza de date. Dacă acest număr depășește 10.000 de scrieri atunci se inițiază procesul de scriere a datelor într-un fișier. Acest fișier este creat automat de procesul pentru baza de date și are ca locație pe disc folderul de instalare al aplicației. Dacă în cele șaizeci de secunde nu s-a ajuns la numărul minim de scriei pentru care să se înceapă salvarea datelor pe disc atunci se amână începerea procesului de salvare până când, în urma verificării făcute, numărul de scrieri în baza de date este de cel puțin 10.000.

3.3 Aspecte tehnice ale aplicației

Fiecare aplicație folosită pe orice computer din întreaga lume are câteva aspecte tehnice. Aceste aspecte tehnice ar trebui amintite sau prezentate în funcție de importanța pe care o au în procesul de dezvoltare al aplicației. Aplicația construită pentru locul de muncă și pe care o folosesc pentru această lucrare nu se sustrage acestor aspecte tehnice. În continuare voi prezenta aspectele tehnice principale ale aplicației.

Aplicația este structurată pe proiecte. Fiecare dintre aceste proiecte este responsabil cu o alăt funcționalitate: proiectul de interfață este responsabil cu interfața aplicației, proiectul de achiziție este responsabil cu achiziția datelor etc. Între aceste proiecte este inevitabil să se creeze dependințe; într-un proiect este nevoie de funcționalitatea existentă într-o clasă care aparține altui proiect. În Figura 3.3.1 este ilustrat graful de dependințe între proiectele aplicației.

Figura 3.3.1. Graf de dependințe între proiectele aplicației

Complexitatea aplicației este destul de ridicată. Pentru un singur programator sarcina scrierii codului pentru întreaga aplicație ar fi mult prea mare. Echipa din care fac parte are doi membrii. Pentur ca dezvoltarea aplicației să decurcă fără incidente am folosit sistemul de source control. Acest sistem presupune urmărirea modificărilor care au loc în fișierele soluției și apoi acestea sunt adăugate în fișierele păstrate pe server. Atât eu cât și colegul meu lucrăm pe versiuni locale ale aplicației. Acest lucru facilitează o dezvoltare mai rapidă a aplicației întrucât fiecare dintre noi se ocupă de altă parte a aplicației fără să îi provoace celuilalt neplăceri. În final toate părțile sunt aduse la un loc prin intermediul tool-ului care se ocupă cu monitorizarea codului aplicației.

Înafara framework-ului .Net, framework de bază pentru aplicație, am mai utilizat un alt framework în construcția serverului web și anume framework-ul AngularJS. Prin intermediul acestui framework am fost capabil să transform o pagină web cu elemente statice într-o pagină web cu elemente dinamice. Construirea paginii web a fost necesară deoarece unele dispozitive pe care se dorește vizualizarea parametrilor nu sunt destinate instalării unor aplicații de tip desktop. Pagina web conține informații despre fiecare parametru și este actualizată la un interval de timp configurabil, interval de timp setat în mod implicit la o secundă.

Un alt aspect tehnic pe care aș dori să îl prezint în cadrul acestui subcapitol este folosirea unor pachete suplimentare. C# și .Net Framework nu pot să trateze toate situațiile în care este nevoie de o funcționalitate specială întrucât nu acesta este scopul lor. Totuși aceste funcționalități speciale sunt definite în cadrul unor pachete și pot fi adăugate aplicațiilor, permitând astfel utilizarea acelei funcționalități. În prezent aplicația folosește un număr de aproximativ doisprezece pachete suplimentare dintre care amintesc Unity, Bootstrap și Microsoft ASP.NET Web API. Primul pachet este containerul Unity cu ajutorul căruia se realizează injectarea componentelor acolo unde este nevoie de ele iar cel de-al doilea pachet a fost adăugat ca suport pentru pagina web. Cel de-al treilea pachet permite folosirea apelurilor de tip API în cadrul proiectelor care nu sunt de tip ASP.NET; un asemenea proiect este serverul web care a fost dezvoltat ca și o aplicație consolă însă care folosește apeluri API prin intermediul ultimului pachet amintit. Aceste pachete pot fi oricând scoase din cadrul aplicației însă odată cu îndepărtarea lor anumite funcționalități nu vor mai fi disponibile iar aplicația nu va mai putea rula.

Dependența de .Net Framework este un aspect tehnic cu un aspect negativ

Capitolul 4. Concluzii și posibile dezvoltări ulterioare

Bibliografie

Andrew Troelsen, Pro C# 5.0 and the .NET 4.5 Framework, Apress, 2012

Svetlin Nakov, Fundamentals of Computer Programming with C#, Faber Publishing, Bulgaria, 2013

Svetlin Nakov, Fundamentals of Computer Programming with C#, Faber Publishing, Bulgaria, Chapter 22: 920-930, 2013

Dominic Betts Grigori Melnik Fernando Simonazzi Mani Subramanian, Dependency Injection with Unity, Microsoft, 2013

Josiah L. Carlson, Redis in Action, Manning, New York, 2013

Josiah L. Carlson, Redis in Action, Manning, New York, Chapter 6: 127-129, 2013

Josiah L. Carlson, Redis in Action, Manning, New York, Chapter 9: 219, 2013

Data acquisition, http://www.ni.com/data-acquisition/what-is/, (data accesării: 12.04.2015).

USB-2533, http://www.mccdaq.com/usb-data-acquisition/USB-2533.aspx#qahash, (data accesării: 15.04.2015)

USB-FS1028, http://www.mccdaq.com/usb-data-acquisition/USB-1208LS.aspx, (data accesării: 15.04.2015)

Hidrogen sulfurat, http://ro.wikipedia.org/wiki/Hidrogen_sulfurat, (data accesării: 15.04.2015)

API, http://cplus.about.com/od/introductiontoprogramming/g/apidefn.htm, (data accesării: 17.04.2015)

Event, https://msdn.microsoft.com/en-us/library/awbftdfh.aspx, (data accesării: 15.04.2015)

Behavior, http://www.wpftutorial.net/Behaviors.html, (data accesării: 20.04.2015)

Inversion of Control Containers and the Dependency Injection pattern, http://martinfowler.com/articles/injection.html, (data accesării: 22.04.2015)

Inversion of Control, http://joelabrahamsson.com/inversion-of-control-an-introduction-with-examples-in-net/, (data accesării: 22.04.2015)

asd

Anexe

Anexa 1 – LINQ & lambda expression

IEnumerable<LinearParamViewModel> vmParams =

_mudSettings.Parameters.Select(mudParam => new LinearParamViewModel(mudParam,

DbModel.MudParameters.FirstOrDefault(

paramModel => paramModel.ParameterName == mudParam.ParameterName)));

Anexa 2 – Inserarea view-ului modulului de setări în containerul de interfețe

ApplicationContext.Container.RegisterInstance<IAlarmsViewModel>(new AlarmsViewModel());

public partial class AlarmsView

{

public AlarmsView()

{

InitializeComponent();

DataContext = ApplicationContext.Container.Resolve<IAlarmsViewModel>();

Tag = ModulesEnum.Alarms;

}

}

Anexa 3 – Convertor între nivelul unei alarme și culoare

public class AlarmLevelToBorderBrushConverter : IValueConverter

{

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)

{

var alarmLevel = value as AlarmLevel?;

if (alarmLevel.HasValue)

{

switch (alarmLevel.Value)

{

case AlarmLevel.High:

return Brushes.Red;

case AlarmLevel.Medium:

return Brushes.Tomato;

case AlarmLevel.Low:

return Brushes.Salmon;

case AlarmLevel.NoAlarm:

return Brushes.White;

}

}

return Brushes.White;

}

}

<Border Grid.Row="0"

Grid.Column="0"

BorderThickness="2"

BorderBrush="{Binding AlarmCurrentLevel, Converter={StaticResource AlarmLevelToColorConverter}}" >

Similar Posts