Aplicație de tip Android de măsurare și monitorizare a unor parametri medicali [301959]

Universitatea “Politehnica” [anonimizat] a unor parametri medicali

Proiect de diplomă

prezentat ca cerință parțială pentru obținerea titlului de

Inginer în domeniul Electronică și Telecomunicații

programul de studii de licență Tehnologii și Sisteme de Telecomunicații

Conducători științifici Absolvent: [anonimizat].Octavian FRATU Adrian BARCAN

Ș.l. Dr. Ing. Alexandru VULPE

2016

Anexa 5

Declarație de onestitate academică

Prin prezenta declar că lucrarea cu titlul “ Aplicație de tip Android de măsurare și monitorizare a unor parametri medicali”, [anonimizat] a Universității “Politehnica” [anonimizat] a mai fost prezentată niciodată la o facultate sau instituție de învățămînt superior din țară sau străinătate.

[anonimizat], [anonimizat]. [anonimizat], [anonimizat]. Reformularea în cuvinte proprii a textelor scrise de către alți autori face referință la sursă. Înțeleg că plagiatul constituie infracțiune și se sancționează conform legilor în vigoare.

[anonimizat], [anonimizat], experimente și măsurători. Înțeleg că falsificarea datelor și rezultatelor constituie fraudă și se sancționează conform regulamentelor în vigoare.

București,

28.06.2016

Absolvent: [anonimizat]

_________________________

(semnătura în original)

Listă figuri

Figura 1.1 Arhitectura software Android 12

Figura 1.2 Clasificare sisteme medicale de monitorizare 15

Figura 1.3 Pulsoximetru Contec CMS 50DL 16

Figura 1.4 Tensiometru Kodea KD-202F 18

Figura 1.5 Senzor de puls 18

Figura 1.6 Comparația formei de undă dintre electrocardiograma și fotopletismogramă 19

Figura 1.7 Placa de expansiunea e-Health Sensor Shield 20

Figura 1.8 Placa de expansiune conectată la Arduino Uno 20

Figura 1.9 Arduino Uno 21

Figura 1.10 Modul Bluetooth HC-05 22

Figura 2.1 Arduino IDE 23

Figura 2.2 Conectare modul Bluetooth la Arduino Uno 25

Figura 2.3 Incisura aortică și semnalul presiunii aortice 28

Figura 2.4 Clasa android.view.View 35

Figura 2.5 Utilitar vizual pentru interfețe grafice Android 36

Figura 2.6 Diagrama UML a aplicației Android 39

Figura 2.7 Fereastră de dialog pentru pornire Bluetooth 42

Figura 2.8 Schema conceptuală a sistemului 44

Figura 2.9 Sistemul de măsurare a pulsului în timp real 45

Figura 2.10 Sistem de măsurare a nivelului de oxigen și tensiune arterială 45

Figura 2.11 Reprezentare valori puls 46

Figura 3.1 Măsurare puls în timp real 48

Figura 3.2 Icoana aplicației din meniul dispozitivului Android 49

Figura 3.3 Fereastra principală și fereastra de dialog Bluetooth 49

Figura 3.4 Pornire Bluetooth și afișare dispozitive asociate 50

Figura 3.5 Conectarea la modulul Bluetooth 50

Figura 3.6 Mesaj de eroare conexiune Bluetooth 51

Figura 3.7 Afișare date de la senzorul de puls 51

Figura 3.8 Fereastra cu setări din meniul aplicației 52

Figura 3.9 Modificare orientării ecranului 53

Figura 3.10 Fereastra de selectare a setului de date 53

Figura 3.11 Deschidere date din memorie 54

Listă tabele

Tabel 1 Clase Bluetooth 13

Tabel 2 Valori normale ale pulsului 15

Tabel 3 Valorile tensiunii arteriale pentru persoanele peste 18 ani 17

Tabel 4 Distribuție Android în funcție de versiune 31

Tabel 5 Valori măsurate pentru puls 46

Tabel 6 Valori măsurate pentru tensiunea arterială 46

Tabel 7 Saturația de oxigen din sânge 47

Listă acronime

API Application Programming Interface / Interfață pentru programarea de aplicații

APK Android Application Package / Pachetul aplicației Android

BPM Beats Per Minute / Bătăi pe minut

CSV Comma-Separated Values / Valori separate prin virgulă

EEPROM Electrically Erasable Programmable Read-Only Memory

IBI Interbeat Interval /Interval între bătăi

IDE Integrated Development Environment

SDK Software Development Kit / Mediu de dezvoltare

SPO2 Oxygen Saturation/ Saturația de oxigen

SPP Serial Port Profile / Profil Port Serial

UUID Universally Unique Identifier / Identificator Unic Universal

Introducere

Această lucrare s-a născut din dorința de a crea o aplicație, utilă și ușor de folosit, care să măsoare și să monitorizeze parametri medicali precum pulsul, nivelul de oxigen din sânge și tensiunea arterială.

În zilele noastre, din ce în ce mai multe persoane suferă de probleme de inimă. Există o nevoie tot mai mare de a avea dispozitive ușor de folosit care să faciliteze ținerea sub observație a acestor probleme. Așadar apare necesitatea dezvoltării unor sisteme de măsură și monitorizare care să ofere rapid informații legate de acești parametri medicali. Cu ajutorul unei astfel de aplicații, utilizatorul poate verifica de acasă pulsul și nivelul de oxigen din sânge apelând la doctor doar atunci când unul din acești parametri este în afara limitelor normale.

Obiectivele generale ale lucrării sunt utilizarea unor senzori medicali pentru măsurarea unor parametri precum pulsul, nivelul de oxigen și tensiune arterială, și dezvoltarea unei aplicații Android care să afișeze pulsul în timp real și să afișeze parametrii salvați într-o bază de date.

Pentru realizarea acestei lucrări, la partea de măsurători, am utilizat un senzor de puls, un puls-oximetru și aparat pentru măsurarea tensiunii arteriale, care sunt conectați la un microcontroler. Senzorul de puls este folosit pentru determinarea pulsului în timp real și afișarea unei medii a ultimilor 15 valori măsurate. Valorile sunt trimise aplicației Android printr-o comunicației fără fir, de tip Bluetooth. Datele măsurate cu ajutorul puls-oximetrului și a dispozitivului pentru măsurarea tensiunii arteriale sunt salvate în fișiere de tip .csv care pot fi deschise de aplicația Android în orice moment de timp.

Această lucrare este structurată în trei capitole. În primul capitol sunt prezentate câteva noțiuni legate de platforma Android, mediul de dezvoltare Arduino IDE și sisteme medicale de monitorizare. În cel de-al doilea capitol este prezentată partea de implementare a aplicației Android, programarea plăcuței Arduino și realizarea montajelor. În încheierea acestui capitol, sunt prezentate rezultatele măsurătorilor parametrilor medicali. În capitolul 3 este descris modul de funcționare al aplicației Android.

Noțiuni teoretice

1.1 Platforma Android

1.1.1 Scurt Istoric

Proiectul ce avea ca scop dezvoltarea unui software pentru dispozitive mobile inteligente a apărut, în anul 2003, într-o companie mică numită Android Inc. Acest proiect își propunea să ofere un produs software pentru telefoane care să țină cont de atât de preferințele utilizatorului, cât și de locația acestuia. În anul 2005, gigantul Google cumpără această companie, continuând să lucreze la acest proiect. În anul 2007 este înființat Open Handset Alliance, un consorțiu ce include mai multe companii precum Google, HTC, Intel, LG, Motorola, Nvidia, Samsung Electronics, T-Mobile și altele. Scopul acestui consorțiu era să dezvolte standardele deschise pentru dispozitive mobile.

Începând cu data de 21 octombrie 2008, Android este licențiat ca software cu sursă deschisa (open source). Majoritatea componentelor sistemului se află sub licență Apache, iar câteva aflându-se sub licență GPL. Producătorii sunt astfel liberi să adauge extensii proprietare, fără a le face disponibile comunității .

1.1.2 Arhitectura platformei Android

Android este un sistem de operare dezvoltat de Google, bazat pe nucleul Linux și creat pentru dispozitive mobile cu ecran tactil precum telefoanele inteligente (smartphones) sau tablete.

Arhitectura Android presupune existența a patru niveluri, cel de la baza fiind nucleul Linux. Al doilea nivel, cel de middleware, conține biblioteci de C. Acesta este cod nativ, rulând direct peste cel de Kernel. Următorul nivel este cel de application framework, care cuprinde biblioteci compatibile Java, bazate pe versiunea de Java Apache Harmony. Dalvik Virtual Machine este mașina virtuală care face translația din cod Java în byte-code, și se deosebește de mașina virtuală clasică JVM prin faptul că nu este o mașină bazată pe stivă, ci una bazată pe registre.

Nivelul de application framework cuprinde servicii scrise în Java care fac legătura între aplicații și sistemul de operare, fiind separate pe diverse funcționalități: manager de resurse, manager de notificări, manager de locație și altele. Nivelul de top este cel al aplicațiilor, unde dezvoltatorii pot adăuga noi aplicații sau pot modifica aplicațiile deja existente.

Figura 1.1 Arhitectura software Android

Android SDK (Android Software Developer Kit) este un set de instrumente de instrumente de dezvoltare. Acesta include un program de depanare, biblioteci, un emulator, exemple de cod, documentație și tutoriale. APK este prescurtarea de la Android application package file, și reprezintă formatul fișierelor utilizate pentru distribuirea și instalarea aplicațiilor pe dispozitive ce folosesc sistemul de operare Android. Toate aplicațiile Android prezintă extensia .apk. Uneltele din SDK compilează codul sursă împreună cu fișierele în care sunt memorate resursele în arhive ce au extensia .apk.

Sistemul de operare Android lucrează ca un sistem Linux multi-user, în care fiecare aplicație reprezintă un utilizator diferit. Fiecare aplicație are asociat un ID de utilizator unic ce poate accesa permisiunile și datele asociate aplicației. Aplicația trebuie să ceară permisiunea pentru a folosi date sau resurse de pe telefon. Aceste permisiuni sunt acordate de utilizator la instalarea aplicației.

Fiecare aplicație Android rulează în propriul său proces și are propria sa instanță în cadrul mașinii virtuale Dalvik. Mașina virtuală Dalvik rulează fișiere cu extensia .dex, ce sunt optimizate pentru a folosi cat mai puțin memoria. Mașina virtuală Dalvik se bazează pe nucleul Linux pentru a gestiona firele de execuție și pentru a gestiona memoria la nivel hardware .

Platforma Android este bazată pe nucleul Linux, versiunea 3.4, pentru accesul la servicii de bază precum gestionarea memoriei, gestionarea proceselor, securitate, stiva de rețea și driverele. Nucleul linux acționează ca un intermediar între hardware și restul stivei software.

1.2 Bluetooth

Ideea ce a dat naștere tehnologiei fără fir Bluetooth a apărut în 1994 când compania Ericsson Mobile Communications a decis investigarea fezabilității unei interfețe radio de mică putere și cost redus între telefoanele mobile și accesoriile acestora. Tehnologia wireless Bluetooth reprezintă una dintre cele mai avansate și răspândite tehnologii de comunicație fără fir.

Numele Bluetooth vine de la Harald I Blåtand ("the blue-toothed"), regele Viking al Danemarcei între anii 940 și 981. Unul dintre scopurile sale era să determine oamenii să comunice între ei și în timpul domniei sale Danemarca și Norvegia au fost unite. Astăzi tehnologia wireless Bluetooth îngăduie oamenilor să comunice între ei, dar de această dată prin intermediul unei legături radio de cost redus și pe domenii restrânse .

Specificațiile Bluetooth definesc capabilități de legături radio pe distanțe scurte (aproximativ 10m) sau opțional pe distanțe medii (aproximativ 20m) pentru transmisii vocale sau de date. Gama de frecvențe de operare o constituie banda nelicențiată industrială, științifică și medicală (ISM) de la 2.4GHz la 2.48GHz.

În funcție de puterea maximă emisă s-au standardizat 3 clase principale în care se pot fabrica dispozitivele Bluetooth, acestora corespunzându-le și diferite acoperiri (raze de acțiune) maxime.

Tabel 1 Clase Bluetooth

1.3 Sisteme de monitorizare

Scopul unui sistem medical de monitorizare la distanță este de a ține sub supraveghere anumiți parametri medicali fără a fi nevoie ca pacientul să fie internat într-un spital. Sistemele de monitorizare a parametrilor medicali oferă pacienților posibilitatea de a observa semnele vitale fără asistența unui personal medical. Astfel, pacienții care au trecut printr-o operație sau prezintă anumite probleme medicale și trebuie ținuți sub observație, pot să-și verifice pulsul, tensiunea arterială sau nivelul de oxigen din sânge fără a merge până la un cabinet medical. Așadar, în cazul în care aceștia descoperă probleme legate de un anumit parametru, pot merge la un doctor pentru un control mai amănunțit.

Sistemele medicale de monitorizare a pacienților se împart în mai multe categorii în funcție de operațiile pe care le efectuează. Sistemele medicale de monitorizare la distanță (RHMS) fac referire la sistemele care colectează parametri medicali, la domiciliul pacientului, și îi încarcă într-o bază de date online, la care personalul medical are acces. Funcțiile unor astfel de sisteme pot varia foarte mult, de la măsurarea unui singur parametru până la o mulțime de simptome și semne fizice ale organismului. Sistemele medicale mobile de monitorizare (MHMS) fac referire, în general, la telefoanele inteligente și asistenți digitali personali (PDA). Sistemele medicale de monitorizare ușor de purtat (WHMS) fac referire la dispozitivele de tip brățară sau ceas echipate cu diferiți senzori medicali. Semnele vitale măsurate de aceste dispozitive sunt: pulsul (HR), tensiunea arterială (BP), electrocardiograma (ECG), saturația de oxigen din sânge (SpO2), temperatura corpului și rata respiratorie (RR) .

Figura 1.2 Clasificare sisteme medicale de monitorizare

De-a lungul ultimilor ani a avut loc o creștere foarte mare a numărului de sisteme medicale de monitorizare, a tehnicilor și metodelor de măsurare a parametrilor medicali. În categoria acestor sisteme de monitorizare a parametrilor medicali întră și pulsoximetrul, tensiometrul digital și brățările inteligente care măsoară diverși parametri medicali.

1.3.1 Pulsoximetrul

În anul 1935, în urma unor studii asupra transmiterii luminii prin piele, s-a descoperit posibilitatea măsurării nivelului de oxigen din sânge. Oximetria reprezintă o metodă de fotodiagnostic utilizată pentru monitorizarea arterială a saturației de oxigen din sânge. Această metodă este utilizată pentru monitorizarea nivelului de oxigen al pacientului în perioada perioperatorie, in timpul ventilației mecanice, în cadrul programelor de reabilitare pulmonară și a testului de stres dar și pentru evaluarea a numeroase boli care pot afecta plămânii sau inima

Pulsoximetrul este un dispozitiv neinvaziv de măsurare a nivelului de oxigen din sânge (SpO2) și a pulsului. Acesta poate fi folosit atât în spitale, cât și la domiciliu pentru o testare rapidă și sigură, oferind o mare capacitate de monitorizare a semnelor vitale.

Figura 1.3 Pulsoximetru Contec CMS 50DL

Pulsul reprezintă expansiunea ritmica a arterelor sanguine care se comprimă pe un plan osos și reflectă numărul de bătăi ale inimii care au loc pe parcursul unui minut. Pulsul diferă de la o persoană la alta, în funcție de vârstă, gen sau stare generală de sănătate. Modificările ritmului inimii, un puls slab sau o rigiditatea crescută a vaselor de sânge pot fi cauzate de boli cardiovasculare.

Sângele este un lichid foarte opac, penetrarea luminii fiind extrem de scăzută în sânge. Volumul sângelui în zonele vasculare periferice (capilare, arteriole și venule) produce o schimbare corespunzătoare ciclului cardiac, datorită fluctuației pulsului. Senzorul optic de volum poate detecta această schimbare. Folosind această metodă pentru a obține volumul de sânge se numește fotopletismografie. Volumul de sânge detectat fotopletismografic poate asigura o bună stabilitate și repetabilitate. Din moment ce măsurătoare are loc la nivelul vârfului degetului, fără restricții asupra activității pacientului, se poate realiza și o monitorizare pentru o perioadă de timp mai îndelungată.

În următorul tabel sunt prezentate valorile normale ale pulsului pentru o persoană în stare de repaus.

Tabel 2 Valori normale ale pulsului

Un pulsoximetru folosește o rază de lumină roșie și infraroșie. Oximetrul funcționează pe principiul ca sângele oxigenat are o culoare roșie mai intensă decât sângele neoxigenat, care are o culoare albastră-purpurie. Saturația normală a oxigenului în sânge este de minim 95%. Determinarea cu precizie a cantității de oxigen din sânge poate fi împiedicată de modificări ale fluxului sanguin în zona în care este atașat dispozitivul, dacă pacientul suferă de anemie, sau daca se constată prezența unor hemoglobine anormale

Globulele roșii din sânge conțin o proteină numită hemoglobină. Oxigenul reacționează la contactul cu această proteină, se atașează de ea și rezultă oxihemoglobina (HbO2). Celulele roșii cu hemoglobina oxigenată circulă prin tot corpul irigând țesuturile. La nivel celular, oxigenul este eliberat de hemoglobină și rezultă hemoglobină neoxigenată (Hb), apoi sângele fără oxigen se întoarce în atriul drept a inimii pentru a repeta întregul proces.

Principiul de funcționare al pulsoximetrelor constă în măsurarea absorbției radiațiilor de lumină roșie și infraroșie ce trec prin degetul pacientului, utilizând un senzor de lumină. Hemoglobina ce transportă oxigenul (oxihemoglobina) absoarbe radiații infraroșii (800 – 940 nm) iar hemoglobina fără oxigen absoarbe lumină roșie din spectrul vizibil (600 – 700 nm). Componentele staționare reprezentate de țesuturi, oase, sângele venos și componenta staționară a sângelui arterial sunt excluse din calcule prin monitorizarea componentelor staționare de absorbție. Ca și sursă de lumină se utilizează LED-uri care sunt comandate în impulsuri și aprinse secvențial. La o bătaie a inimii, volumul de sânge crește, iar componenta alternativă a curentului pe fotodetector este utilizată pentru calculul absorbțiilor corespunzătoare hemoglobinei oxigenate și neoxigenate.

Limitele normale acceptate pentru nivelul de oxigen din sânge sunt între 95 și 98%. Valorile de 100% pot indica intoxicații cu dioxid de carbon

1.3.2 Tensiunea arterială

Tensiunea arterială reprezintă presiunea exercitată de coloana de sânge asupra peretelui vascular în timpul contracției și relaxării ritmice a inimii.

Există două componente ale presiunii sanguine: presiunea sistolică și presiunea diastolică. Presiunea sistolică reprezintă presiunea exercitată asupra pereților arteriali când inima se contractă. Valorile normale sunt între 100 și 120 mm coloana de mercur (Hg). Presiunea diastolică este presiunea exercitată asupra pereților arteriali când inima se relaxează între două contracții. Valorile normale pentru presiunea diastolică sunt sub 80 mmHg.

Tensiunea arterială crește, după efectuarea unui efort fizic, din cauza stresului sau a oboselii, ca un mecanism de adaptare a organismului, după care revine la valorile normale.

Tabel 3 Valorile tensiunii arteriale pentru persoanele peste 18 ani

Măsurarea neinvazivă se poate face foarte ușor cu ajutorul unui tensiometru (digital sau mecanic). Tensiometrul digital are un microprocesor ce, prin intermediul unui senzor de presiune, măsoară vibrațiile rezultate din umflarea și dezumflarea pernei de aer pe arteră.

Figura 1.4 Tensiometru Kodea KD-202F

Există un număr mare de factori ce pot influența valoarea presiunii arteriale. Măsurătorile pot fi afectate serios de stres, teamă, efor fizic intens sau chiar și de momentul zilei în care sunt efectuate măsurătorile. Aceste fluctuații sunt mai accentuate in cazul persoanelor care suferă de hipertensiune. Presiunea arteriala este de obicei la valorile maxime în timpul eforului fizic iar la nivelul cel mai scăzut în timpul orelor de somn. Greutatea corporală are, de asemenea, o influența marcantă asupra tensiunii arteriale. Creșterea în greutate este însoțită de o frecvența crescută a hipertensiunii arteriale.

1.3.3 Senzorul de puls

Este un senzor de puls ușor de utilizat care se conectează la un microcontroler cu ajutorul celor 3 conectori. Senzorul poate fi prins pe vârful degetului sau poate fi atașat de lobul urechii cu ajutorul unei cleme.

Figura 1.5 Senzor de puls

Partea cu logoul în formă de inimă este cea care intră în contact cu pielea. Aici se poate observa o deschizătură rotundă in care se află LED-ul , iar sub aceasta se află un senzor de lumină ambientală. Acest senzor este asemănător cu cel folosit la telefoane mobile, tablete și laptopuri pentru ajustarea automată a luminozității ecranului. Senzorul este foarte sensibil la lumină, așadar, trebuie evitată lumina din exterior la efectuarea măsurătorilor.

La acest senzor de puls, lumina emisă de LED este reflectată înapoi la senzor care detectează o modificarea a intensității luminii, datorată variației volumului de sânge în interiorul țesutului, la fiecare bătaie a inimii. Semnalul de impulsuri, care reprezintă măsurătoarea de la senzor, este format dintr-o fluctuație analogică în tensiune, și are forma de undă de mai jos.

Figura 1.6 Comparația formei de undă dintre electrocardiograma și fotopletismogramă

PTT reprezintă timpul de transmisie a pulsului. În general, punctul de mijloc al formei de undă este luat ca indice pentru sosirea undei de puls. Considerăm punctul de mijloc al formei de undă ca o bătaie a inimii, iar timpul între doua bătăi succesive este numit IBI. Valoarea BPM iese dintr-o medie de 10 timpi IBI.

Cei 3 conectori pentru conectarea la placa Arduino sunt:

– Firul (violet) pe care se transmit datele de la senzor.

– Firul (roșu) de alimentare care se conectează la portul Vin de 5V.

– Firul (negru) de masă se conectează la portul GND.

1.3.4 Placa de expansiune e-Health Sensor Shield

Placa de expansiune e-Health Sensor Shield permite conectarea la placa Arduino a diferitor senzori: puls, nivel de oxigen în sânge (SpO2), flux de aer (respirație), temperatură, electrocardiogramă (ECG), glucometru, presiune arterială, poziția corpului și senzor pentru monitorizarea activității musculare. Această placă poate fi folosită pentru a monitoriza în timp real starea unui pacient, sau pentru a accesa datele salvate de acești senzori și afișarea pe un calculator. Placa de expansiune poate fi alimentată de la un calculator sau de la o sursă de tensiune externă de 12V.

Figura 1.7 Placa de expansiunea e-Health Sensor Shield

Figura 1.8 Placa de expansiune conectată la Arduino Uno

Placa de expansiune are creată și o bibliotecă de funcții pentru o utilizare cât mai ușoară. Această bibliotecă include toate fișierele necesare utilizării, separate în două directoare, "eHealth" și "PinChangeInt". Biblioteca "PinChangeInt" trebuie importată atunci când se conectează pulsoximetrul. Pentru utilizarea acestor biblioteci este necesară copierea fișierelor în directorul "libraries" în locația în care este instalat Arduino IDE.

1.3.5 Arduino Uno

Arduino Uno este o placă de dezvoltare open-source bazată pe microcontrolerul ATmega 328. Placa de dezvoltare Arduino poate fi folosită, de exemplu, pentru citirea datelor de la un senzor, controlul unor motoare sau a unor leduri.

Arduino Uno are 14 intrări digitale/pini de ieșire (din care 6 pot fi utilizate ca ieșiri PWM), 6 intrări analogice, un oscilator cu cuarț de 16 MHz, o conexiune USB, o mufă de alimentare, o mufă ICSP și un buton de resetare. Alimentarea se poate realiza prin conectarea la un computer printr-un cablu USB sau cu o sursă de alimentare externă.

Pinii de intrare digitali prezintă două stări posibile, pornit și oprit. Intrările digitale pot fi folosite în numeroase protocoale de comunicații digitale.

Figura 1.9 Arduino Uno

Comunicarea cu calculatorul, altă placă Arduino sau alte microcontrolere se poate realiza fie prin portul USB (și este văzut ca un port standard serial COMx), fie prin pinii 0 și 1 (RX și TX) care facilitează comunicarea serială UART TTL (5V). Folosind librăria SoftwareSerial se pot realiza comunicații seriale folosind oricare din pinii digitali.

Monitorul serial permite microcontrolerului să realizeze o comunicație serială cu calculatorul. Prin intermediul acestei conexiuni Arduino afișează datele primite de la senzor pe ecranul calculatorului în timp real.

Microcontrolerele Arduino prezintă o memorie volatila și una nevolatilă. La oprirea alimentării microcontrolerului, datele salvate în memoria volatilă se pierd. Datele din memoria nevolatilă rămân în memorie chiar și după oprirea alimentării, și pot fi folosite în continuare după ce plăcuța este alimentată.

ATmega328 are o memorie de 32 KB (cu 0,5 KB utilizați de bootloader). Acesta mai are, de asemenea, 2 KB SRAM si 1 KB memorie EEPROM. Memoria EEPROM are o rezistență de scriere de cel puțin 100000 de cicluri. La fiecare scriere, memoria este stresată, iar în timp ea devine mai puțin fiabilă. Cele 100000 de scrieri se aplică pentru fiecare adresă de memorie în parte. Citirea din memoria EEPROM nu solicită memoria.

1.3.6 Modulul Bluetooth HC-05

Modulul HC-05 este un modul Bluetooth SPP (Serial Port Protocol) ușor de utilizat, conceput pentru configurarea conexiunii seriale fără fir. Distanța între modul și dispozitivul conectat este de până la 10 metri. Tensiunea de alimentare este între 3.6 și 6V. Suportă rata de transfer standard, de la 4800 bps ~ 1382400 bps.

Figura 1.10 Modul Bluetooth HC-05

Implementare

2.1 Arduino

2.1.1 Arduino IDE

Arduino oferă un mediu integrat de dezvoltare (IDE), o aplicație multiplatformă (cross-platform), scrisă in Java. Include un editor de cod cu funcții ca evidențierea sintaxelor, potrivirea acoladelor și spațierea automată, și oferă mecanisme simple pentru a compila și încărca programele în plăcuța Arduino. Un program scris în Arduino IDE se numește sketch. Software-ul Arduino vine cu un set de sketch-uri preîncărcate (De exemplu, programarea unui LED să clipească intermitent).[ http://playground.arduino.cc/Interfacing/Java] Un sketch este format din două funcții:

– setup(): este o funcție rulată o singură dată la începutul programului, când se inițializează setările.

– loop(): este o funcție apelată în mod repetat până la oprirea alimentării cu energie a plăcuței.

Figura 2.1 Arduino IDE

2.1.2 Comunicația Serială

Plăcuța Arduino are suport încorporat pentru comunicația serială. Pinii 1 și 0. Comunicarea serială este bidirecțională (Arduino poate trimite și recepționa mesaje) și este folosită în general pentru diagnosticarea programelor și interacțiunea cu diverse periferice. Pentru realizarea unei comunicări seriale este nevoie de 2 pini: Tx (transmitere) și Rx (recepție). Pe placa de dezvoltare acești pini sunt identificați prin ieșirea digitală 0 și 1, iar starea lor (1,0) este semnalizată de cele două leduri asociate. Funcția begin() este folosită pentru a stabili rata de transfer in biți pe secundă. Pentru comunicarea cu un calculator pot fi folosite următoarele rate de transfer: 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, 115200. Cu toate acestea, se poate seta și o altă rată de transfer dacă dispozitivul conectat la Arduino necesită acest lucru. Dacă porturile seriale ale plăcuței sunt ocupate, se poate folosi Serial.begin(rata) pentru stabilirea unei rate de transfer diferite. Deoarece pinii serial (Rx și Tx) sunt conectați la calculator, prin cablul USB, se pot vizualiza datele transmise prin conexiunea serială cu ajutorul monitorului serial inclus în mediul de dezvoltare Arduino. Pe lângă cei doi pini de comunicare în mod serial speciali, Arduino suportă comunicație serială pe restul de pini digitali utilizând librăria SoftwareSerial. În această lucrare pinii Rx și Tx de pe placa Arduino sunt folosiți pentru comunicația serială. Pinul Rx este conectat la pinul TXD al modulului Bluetooth, iar pinul Tx este conectat la pinul RXD. Pinul VCC al modulului Bluetooth este conectat la pinul de 3.3V, iar GND la pinul GND al plăcuței Arduino.

2.1.3 Întreruperi

Întreruperea reprezintă suspendarea procesului normal de execuție a programului pentru rezolvarea unei probleme prioritare. Întreruperea este generată ca răspuns la un efect fizic intern sau extern al unui modul periferic. Modulele periferice pot genera una sau mai multe întreruperi.

Mediul Arduino IDE pune la dispoziție funcția attachInterrupt (întrerupere, isr_asociat, mod) care permite manipularea celor două întreruperi externe INT0 și INT1 ale microcontrolerului. Parametrii sunt: întrerupere – 0 pentru INT0 și 1 pentru INT1, isr_asociat – numele unei proceduri care să trateze apariția întreruperii și mod – specifică evenimentul extern la care să se declanșeze întreruperea. Modurile sunt: LOW (pinul devine "0" logic), FALLING (pinul trece din "1" în "0"), RISING (pinul trece din "0" în "1") și CHANGE (orice tip de tranziție). Funcția pereche detachInterrupt permite dezactivarea întreruperii.

2.1.4 Configurarea modulului Bluetooth

Modulul Bluetooth HC-05 este alimentat de placa de dezvoltare Arduino Uno. Odată conectat la Arduino se poate realiza conectarea wireless a unui dispozitiv cu Bluetooth ce suportă SPP la acest modul. Odată ce cele două dispozitive sunt asociate, modulul Bluetooth poate fi configurat să lucreze la rata de transfer dorită. Rata de transfer a modulului HC-05, în mod implicit, este 9600.

Figura 2.2 Conectare modul Bluetooth la Arduino Uno

Modulul are două moduri de operare, Command Mode (Modul de comandă) și Data Mode (Modul de date). Modul de comandă este folosit pentru a configura modulul Bluetooth atunci când este nevoie de o schimbare a numelui, codului PIN sau a ratei de transfer. În modul de comandă, modulul poate căuta și se poate conecta la dispozitive din raza sa de acoperire. În modul de date, modulul este folosit doar pentru a trimite si recepționa date. În mod implicit, numele modulului Bluetooth HC-05, este HC-05, iar codul PIN este 1234.

2.1.5 Citirea datelor de la pulsoximetru

Platforma e-Health , cu ajutorul căreia se realizează măsurarea parametrilor puls, nivel de oxigen și presiune arterială, prezintă un set de biblioteci C++ cu care se face citirea și transmiterea datelor către calculator. Pentru utilizarea acestor biblioteci este necesară copierea directoarelor eHealth și PinChange în directorul libraries, în locația în care este instalat Arduino IDE.

Pentru folosirea bibliotecilor într-un program este necesară importarea acestora folosind meniul Sketch -> Import Library.

#include <PinChangeInt.h>

#include <PinChangeIntConfig.h>

#include <eHealth.h>

#include <eHealthDisplay.h>

Funcțiile folosite din aceste biblioteci sunt:

initPulsioximeter() : inițializează senzorul pulsoximetru.

readPulsioximeter() : citește valorile de la pulsoximetru.

getBPM() : returnează pulsul în bpm (bătăi pe minut).

getOxygenSaturation() : returnează nivelul de oxigen din sânge exprimat în procente.

Întreruperile se adaugă în sketch folosind următoarea linie de cod:

PCintPort::attachInterrupt(6, readPulsioximeter, RISING);

Senzorul va întrerupe procesul pentru a actualiza datele salvate în variabilele private. Pinul digital 6 de pe Arduino este pinul pe care senzorul trimite întreruperea, realizându-se astfel execuția funcției readPulsioximeter().

void readPulsioximeter(){

cont ++;

if (cont == 50) {

eHealth.readPulsioximeter();

cont = 0;

}

Pentru obținerea datelor de la senzor se vor folosi următoarele funcții: eHealth.getOxygenSaturation() și eHealth.getBPM().

void loop() {

Serial.print(eHealth.getBPM());

Serial.print(",");

Serial.print(eHealth.getOxygenSaturation());

Serial.print("\n");

delay(3000);

}

2.1.6 Citirea datelor de la tensiometrul digital

Pentru utilizarea senzorului de tensiune trebuie realizată inițializare unor parametri. Acest lucru se face prin utilizarea funcției readBloodPressureSensor() din biblioteca eHealth. Se setează rata de transfer la 115200 bps.

void setup() {

eHealth.readBloodPressureSensor();

Serial.begin(115200);

delay(100);

}

Funcțiile eHealth.getSystolicPressure(i) și eHealth.getDiastolicPressure(i) returnează valorile presiunii sistolică și diastolică măsurată de senzor și le salvează în variabilele systolic și diastolic. Argumentul i al celor două funcții reprezintă numărul măsurătorii din memoria dispozitivului cu care se s-au realizat măsurătorile, Kodea KD-202F. Numărul de măsurători este salvat într-o variabilă de tip unsigned char (uint8_t). Cu ajutorul acestei variabile se face citirea datelor.

uint8_t numberOfData = eHealth.getBloodPressureLength();

for (int i = 0; i<numberOfData; i++) {

// The protocol sends data in this order

Serial.print(eHealth.bloodPressureDataVector[i].day);

Serial.print(F("."));

Serial.print(eHealth.numberToMonth(eHealth.bloodPressureDataVector[i].month));

Serial.print(F("."));

Serial.print(2000 + eHealth.bloodPressureDataVector[i].year);

Serial.print(F(" "));

if (eHealth.bloodPressureDataVector[i].hour < 10) {

Serial.print(0); // Only for best representation.

}

Serial.print(eHealth.bloodPressureDataVector[i].hour);

Serial.print(F(":"));

if (eHealth.bloodPressureDataVector[i].minutes < 10) {

Serial.print(0);// Only for best representation.

}

Serial.print(eHealth.bloodPressureDataVector[i].minutes);

Serial.print(",");

Serial.print(30+eHealth.bloodPressureDataVector[i].systolic);

Serial.print(",");

Serial.print(eHealth.bloodPressureDataVector[i].diastolic);

Serial.print(",");

Serial.print(eHealth.bloodPressureDataVector[i].pulse);

Serial.println();

}

Datele salvate sunt de forma : 13.June.2016 13:46,121,85,86. Prima valoare de după dată reprezintă tensiunea sistolică, a doua este tensiunea diastolică, iar a treia este pulsul.

2.1.7 Măsurarea datelor în timp real

Se calculează valoare pulsului în bătăi pe minut prin medierea a 10 valori IBI. Când semnalul crește peste punctul de mijloc a formei de undă, se calculează timpul de la ultima bătaie pentru a determina IBI. Cu valoarea IBI determinată, aceasta este adăugată într-un șir de caractere. Cu cele 10 valori din acest sir de caractere se calculează valoare pulsului.

Se setează un timer și se apelează întreruperea la fiecare 2 ms pentru a măsura semnalul de la senzorul de puls. Astfel rata de eșantionare este de 500 Hz. Semnalul analogic este transformat în semnal digital cu ajutorul ADC. Pentru a evita interferența produsă de valul dicrotic, se urmărește creșterea pulsului după 0.6 din IBI.

Figura 2.3 Incisura aortică și semnalul presiunii aortice

Memoria nevolatilă a unui microcontroler este numită EEPROM. Pentru a putea scrie și citi memoria EEPROM este necesar să se adauge la începutul sketch-ului biblioteca EEPOROM.h, prin scrierea liniei #include <EEPROM.h> înaintea funcțiilor programului, sau cu ajutorul meniului IDE Sketch > Import Library….

O funcție importantă a bibliotecii EEPROM este EEPROM.read(). Funcția are un singur argument, adresa octetului din memoria EEPROM care urmează a fi citit. Numerotarea adreselor octeților din memoria EEPROM începe de la 0 și ajunge până la 512. Funcția EEPROM.write() realizează scrierea în memoria EEPROM și prezintă doua argumente. Primul argument reprezintă adresa octetului în care se face scrierea, iar al doilea argument este valoarea care se dorește scrisă.

Biblioteca Time pentru Arduino adaugă funcționalitatea de a înregistra timpul fără a necesita conectarea unui senzor. Această bibliotecă permite sketch-ului să obțină momentul de timp și data sub forma: secunde, minute, oră, zi, lună, an. Aceasta asigură, de asemenea, un timp sub forma standard C time_t, cu care pot fi calculate valorile timpului între platforme diferite.

Ora și data sunt setate cu ajutorul funcției setTime(hour,min,sec,day,month,year). Pentru aflarea momentului de timp sketch-ul Arduino folosește un mesaj de sincronizare pe portul serial. Momentul de timp, sub forma: oră, minute, secunde, zi, lună, an, este salvat într-un char și convertit mai apoi în integer.

void loop(){

char x;

if(Serial.available()){

x = Serial.read();

if(x == '\n'){

if(strstr(command, "sync") != NULL ){

char strTemp[3];

strTemp[0] = command[5];

strTemp[1] = command[6];

int hour = atoi(strTemp); //conversie string – integer

strTemp[0] = command[8];

strTemp[1] = command[9];

int minutes = atoi(strTemp);

strTemp[0] = command[11];

strTemp[1] = command[12];

int sec = atoi(strTemp);

strTemp[0] = command[14];

strTemp[1] = command[15];

int day = atoi(strTemp);

strTemp[0] = command[17];

strTemp[1] = command[18];

int month = atoi(strTemp);

strTemp[0] = command[20];

strTemp[1] = command[21];

int year = atoi(strTemp);

setTime(hour, minutes, sec, day, month, year);

Pentru valoarea pulsului se face o medie a cel puțin 15 valori detectate ale pulsului și se memorează media acestora în memoria EEPROM a plăcuței Arduino împreună cu momentul de timp.

if (QS == true){ // flag true cand se detecteaza puls

countTime++;

a[countTime] = BPM;

Serial.println(countTime);

Serial.println(BPM);

if(countTime >= maxCount){

average = 0;

average_err = 100;

while(average_err > 3){

int i = 0;

average = 0;

int count_temp = 0;

for(i = beat_start_calc; i <= countTime; i++){

if(a[i] != -1){

average += a[i];

count_temp++;

}

}

average /= count_temp;

Pentru a nu înregistra valori eronate, se folosește o variabilă ce conține eroarea medie dintre parametrii măsurați. Eroarea medie se calculează ca media diferențelor dintre valoarea pulsului măsurată și ultima valoare medie a pulsului. Dacă diferența dintre valoarea pulsului măsurată la un moment de timp și valoarea medie a pulsului este mai mare decât eroarea medie a pulsului, variabila a[i] în care avem valoarea ratei pulsului este inițializaată cu -1 și se exclude de la calculul medie pulsului.

average_err = 0;

int count_err = 0;

for(i = beat_start_calc; i <= countTime; i++){

if(a[i] != -1){

b[i] = abs(a[i] – average);

average_err += b[i];

count_err++;

}

}

average_err = average_err / count_err;

for(i = beat_start_calc; i <= countTime; i++)

if(b[i] > average_err)

a[i] = -1;

if(count_err == 2)

break;

După ce se calculează mediu valorilor pulsului, se scrie valoarea în memorie cu ajutorul funcției EEPROM.write() împreună cu momentul de timp. Variabila iEEPROM conține numărul adresei octetului din memoria EEPROM.

Serial.print("average");

Serial.println(average);

if(year() >= 2015){

EEPROM.write(iEEPROM, average);

iEEPROM++;

EEPROM.write(1,iEEPROM);

//EEPROM time, date:

EEPROM.write(iEEPROM, hour());

iEEPROM++;

EEPROM.write(iEEPROM, minute());

iEEPROM++;

EEPROM.write(iEEPROM, second());

iEEPROM++;

EEPROM.write(iEEPROM, day());

iEEPROM++;

EEPROM.write(iEEPROM, month());

iEEPROM++;

EEPROM.write(iEEPROM, year()-2000);

iEEPROM++;

EEPROM.write(1,iEEPROM);

}

În final, se resetează numărătorul și flagul QS pentru a începe calculul unei noi valori medii a pulsului. Variabila countTimes memorează de câte ori flagul QS este true, atunci când se detectează pulsul.

countTime = 0;

}

QS = false; // reseteaza QS

}

2.2 Android Studio

2.2.1 Date generale

Până în luna mai a anului 2013, aplicațiile Android se puteau realiza utilizând mediul de dezvoltare Eclipse, împreună cu plugin-ul ADT (Android Development Tools) – dezvoltat de către Google. Google a propus apoi un nou IDE, Android Studio, care oferă mai multe facilități în ceea ce privește instalarea mediului de lucru, performanță și ușurința de scriere și verificare a codului. Pentru instalarea softului Android Studio este necesar să fie instalat Java Developer Kit (JDK). Primul pas în crearea unei aplicații Android este instalarea platformei și uneltelor SDK folosind managerul SDK.

Fiecare aplicație Android are un nume, un Company Domain și un Package name. Numele aplicației este cel care utilizatorilor. Company Domain identifică în mod unic aplicația. Astfel, doua aplicații pot avea același nume, dar se vor diferenția prin domeniul companiei. Acest câmp este completat automat cu un domeniu fictiv. Package name trebuie să fie unic dacă se dorește lansarea pe piață a aplicației. Acesta este format din Company Domain scris invers la care este adăugat numele aplicației. Se aleg dispozitivele pe care poate funcționa aplicația, telefon și tabletă, și versiunea minimă de Android. Versiunea poate fi schimbată din fișierul build.gradle.

Tabel 4 Distribuție Android în funcție de versiune

2.2.3 Componentele aplicației

a. Activitate (Activity)

Activitatea reprezintă o componentă a aplicației Android ce oferă o interfață grafică cu care utilizatorul poate să interacționeze. În sistemul de operare Android ferestrele se numesc activități. O aplicație Android este formată din una sau mai multe activități.

Din punct de vedere al programării, activitățile sunt clase care extind clasa Activity. Spre deosebire de alte sisteme în care un program conține mai multe ferestre afișate simultan, în Android, ecranul este mereu ocupat doar de o singură fereastră. Fereastra care apare pe ecran la pornirea aplicației este considerată fereastra principală. O activitate poate invoca o altă activitate pentru a realiza diferite sarcini, prin intermediul unei intenții. În acest moment, activitatea veche este oprită și salvată pe stivă (back stack), după care este pornită activitatea nouă. Restaurarea și reînceperea activității vechi este realizată în momentul în care activitatea nouă (curentă) este terminată. Un comportament similar are loc în momentul în care se produce un eveniment (primirea unui apel telefonic, apăsarea tastelor Home sau Back, lansarea altei aplicații). Interacțiunea cu ferestrele se realizează prin intermediul evenimentelor. Două dintre cele mai importante evenimente sunt: onCreate() și onPause().

O activitate poate fi utilizată numai dacă este definită în fișierul AndroidManifest.xml, în cadrul elementului de tip <application>. Pentru fiecare activitate trebuie creată o intrare de tip <activity> pentru care se specifică diferite atribute, dintre care obligatoriu este numai denumirea activității (android:name). Pentru fiecare activitate, este necesar să se descrie interfața grafică în cadrul unui fișier .xml. Acest fișier este plasat în directorul res/layout și conține referințe către toate obiectele care vor fi afișate în cadrul ferestrei. Așadar, componentele definitorii ale unei activități sunt clasa în care este implementat comportamentul în urma interacțiunii cu utilizatorul și fișierul .xml care descrie modelul interfeței grafice.

b. Servicii

În Android, clasa Service este utilizată pentru componente a căror funcționalitate implică procesări complexe, de lungă durată, necesitând anumite resurse, fără a fi necesar să pună la dispoziție o interfață grafică sau un mecanism de interacțiune cu utilizatorul. Prin intermediul unui serviciu, se asigură faptul că aplicația Android continuă să se găsească în execuție, chiar și atunci când interfața grafică a acesteia nu este vizibilă. Serviciul Android nu prezintă o interfață grafică. O aplicație poate porni și rula un serviciu în background chiar dacă utilizatorul intră într-o altă aplicație. Un serviciu este o clasă derivată din android.app.Service, implementând metodele:

onCreate() – realizează operațiile asociate construirii serviciului respectiv;

onBind() – asigură asocierea serviciului la o altă componentă a aplicației Android; metoda primește un parametru de tip Intent, reprezentând intenția prin intermediul căruia a fost lansat în execuție.

c. Manager de conținut (Content provider)

Managerul de conținut permite aplicațiilor să stocheze date. Dacă managerul de conținut permite, alte aplicații pot accesa și modifica aceste date. De exemplu, sistemul Android are un manager de conținut care gestionează informațiile listei de contacte ale utilizatorului. Dacă primesc permisiunile necesare, alte aplicații pot accesa și edita aceste informații.

d. Broadcast receiver

Un broadcast receiver este o componentă Android care permite identificarea diferitelor evenimente generate de sistem sau de o aplicație. Fiecare “ascultător” înregistrat pentru un anumit eveniment va fi notificat de sistem imediat ce evenimentul respectiv apare.

e. Intenții (Intent)

Conceptul de intenție în Android poate fi definit ca o acțiune având asociată o serie de informații, transmisă sistemului de operare Android pentru a fi executată sub forma unui mesaj asincron. Astfel, intenția asigură interacțiunea între toate aplicațiile instalate pe dispozitivul mobil, chiar dacă fiecare în parte are o existență autonomă. Sistemul de operare Android poate fi privit ca o colecție de componente funcționale, independente și interconectate.

O intenție poate fi utilizată pentru:

– a invoca activități din cadrul aceleiași aplicații Android;

– a invoca alte activități, din alte aplicații Android;

– a transmite mesaje cu difuzare (broadcast messages), propagate în întreg sistemul de operare Android.

O intenție reprezintă o instanță a clasei android.content.Intent. Aceasta este transmisă ca parametru unor metode( de tipul startActivity() sau startService() definite în clasa abstractă android.content.Context), pentru a invoca anumite componente(activități sau servicii).

În fișierul AndroidManifest.xml, orice activitate definește în cadrul elementului <intent-filter>, denumirea unei acțiuni care va putea fi folosită de o intenție pentru a o invoca.

<activity
android:name="com.realtimepulse.Homescreen"
android:configChanges="keyboardHidden|orientation"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

</activity>

O aplicație poate invoca, prin intermediul unei intenții, o activitate din cadrul său, sau o activitate aparținând altei aplicații. Pentru invocarea unei activități din cadrul aplicației se pot folosi următoarele linii de cod:

public void openData( View view)
{

Intent intent1= new Intent(this,DataActivity.class);
startActivity(intent1 ); }

Se observă faptul că în situația în care este pornită o activitate din cadrul aceleiași aplicații Android, obiectul de tip Intent primește ca parametru și contextul curent (this). În cazul în care este lansată în execuție o activitate din cadrul altei aplicații Android acest parametru este omis.

f. Interfețe grafice

În cadrul unei aplicații Android, o interfață grafică conține elemente care au capabilitatea de a afișa informații, în diferite formate, și de a interacționa cu utilizatorul, preluând datele necesare realizării diverselor fluxuri operaționale din cadrul aplicației.

Interfața grafică reprezintă unul dintre cele mai importante elemente ale unei aplicații. Pentru a putea diferenția interfața grafică de codul aplicației, cele două se află în fișiere diferite. De asemenea, designul interfeței este făcut într-un alt limbaj, unul descriptiv. Acesta este asemănător cu limbajul HTML, fiind alcătuit din marcaje.

Baza pentru construirea oricărei interfețe grafice dintr-o aplicație Android este reprezentată de clasa android.view.View. Ea definește o zonă rectangulară a dispozitivului de afișare (ecran), majoritatea controalelor grafice și a mecanismelor de dispunere a conținutului fiind derivate din aceasta.

Figura 2.4 Clasa android.view.View

O mare parte din elementele grafice sunt definite în pachetul android.widget, în care sunt implementate controale care implementează cele mai multe dintre funcționalitățile uzuale (etichete, câmpuri text, controale pentru redarea de conținut multimedia, butoane, elemente pentru gestiunea datei calendaristice și a timpului). Controalele pentru gestiunea mecanismului de dispunere a conținutului sunt derivate din clasa android.view.ViewGroup.

O interfață grafică poate fi construită prin definirea elementelor componente și a modului lor de dispunere în cadrul unui fișier .xml, sau programatic, prin instanțierea unor obiecte de tipul elementelor componente direct în codul sursă al activității. De regulă, se preferă ca interfața grafică să fie definită în cadrul unui fișier .xml pentru fiecare fereastră din cadrul aplicației Android. Pentru fiecare activitate se construiește un fișier .xml în directorul res/layout care va descrie conținutul interfeței grafice și modul de dispunere al elementelor componente.

Utilitarul vizual poate fi accesat prin apăsarea butonului Design , putând fi specificate (prin selecția dintr-un meniu) dispozitivul mobil pentru care se proiectează interfața grafică, orientarea ecranului, tema aplicației (stilul folosit), localizarea precum și nivelul de API.

Figura 2.5 Utilitar vizual pentru interfețe grafice Android

f. Mecanisme pentru dispunerea controalelor (layout)

Controalele Android fac parte dintr-un grup (obiect de tip android.view.ViewGroup) care definește modul în care acestea sunt dispuse în cadrul unei interfețe grafice și dimensiunile pe care acestea le pot lua. Astfel, o componentă de acest tip este referită și sub denumirea de layout. Cele mai utilizare tipuri de grupuri de componente vizuale sunt: LinearLayout, AbsoluteLayout, relativeLayout, FrameLayout, TableLayout și GridLayout.

Elementele de tip layout pot fi conținute unele în altele, putându-se proiecta astfel interfețe grafice foarte complexe. Dezvoltarea unei interfețe grafice se poate realiza atât în fișierul .xml al activității corespunzătoare cât și programatic. În ambele cazuri, va trebui precizat inițial obiectul de tip ViewGroup la care se va preciza componența, prin specificare elementelor de tip View pe care le conțin.

g. Fișierul manifest

Fiecare aplicație are un fișier manifest. Toate componentele aplicației trebuie să fie declarate în acest fișier. Fișierul manifest include informații legate de aplicație, precum nivelul minim API, bibliotecile API folosite și permisiunile necesare pentru utilizarea componentelor hardware și software ale dispozitivului.

Deoarece aplicația folosește conexiune Bluetooth pentru transmiterea datelor în timp real și realizează scrierea citirea unor fișiere din memoria telefonului, sunt necesare următoarele permisiuni în AndroidManifest.xml.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.realtimepulse"
android:versionCode="1"
android:versionName="1.0" >

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

Avem nevoie de permisiunea BLUETOOTH pentru cererea inițierii unei conexiuni, acceptarea unei conexiuni și transferul de date. Permisiunea BLUETOOTH_ADMIN este necesară pentru activarea Bluetooth și pentru obținerea listei de dispozitive asociate. Dacă este nevoie de permisiunea BLUETOOTH_ADMIN este obligatoriu să se adauge și permisiunea BLUETOOTH.

În fișierul Manifest sunt folosite <activity>, <service>, <receiver> și <provider> pentru a declara activități, servicii, broadcast receiver și managerul de conținut. Fișierul AndroidManifest.xml poate conține un singur nod <application> în care sunt setate caracteristici ale aplicației precum tema, icoana aplicației și numele. Pe lângă acestea, nodul <application> acționează ca un container pentru nodurile <activity>, <service>, <provider> și <receiver>.

Pentru fiecare activitate a aplicației Android este necesară adăugarea unui nod activity în interiorul nodului application. Specificarea numelui clasei asociate nodului activity se face folosind atributul android:name. Nodul actvity permite utilizarea unui intent-filter pentru a specifica intenția (intent) care pornește activitatea. Elementul <action> declară acțiunea realizată de către intent. Elementul <category> definește categoria intenției, iar dacă această categorie este LAUNCHER, activitatea reprezintă activitatea principală a aplicației.

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:name="com.realtimepulse.MyApplication"
android:theme="@style/Theme.AppCompat" >

<activity
android:name="com.realtimepulse.Homescreen"
android:configChanges="keyboardHidden|orientation"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".DataActivity"/>
<activity android:name=".POData"/>
<activity android:name="PreferencesActivity"/>
<activity android:name=".TensData"/>
</application>

În această lucrare, după cum se poate observa și din nodul activity, activitatea principală care pornește când lansăm aplicațiea este Homescreen.

h. Gradle

Android Studio folosește Gradle pentru a compila și construi aplicația. Există câte un fișier build.gradle pentru fiecare modul al proiectului și unul pentru întreg proiectul.

android {
compileSdkVersion 23
buildToolsVersion "23.0.3"

defaultConfig {
applicationId "com.realtimepulse"
minSdkVersion 11
targetSdkVersion 23
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
}

Fișierul build.gradle al aplicației conține versiunea SDK pe care este compilat proiectul și versiunea minimă pe care poate funcționa aplicația. Câmpul applicationId reprezintă numele pachetului.

dependencies {
compile 'com.android.support:appcompat-v7:23.3.0'

}

Fișierul gradle conține și toate dependințele necesare aplicației.

2.2.4 Aplicația Android

Figura 2.6 Diagrama UML a aplicației Android

MainActivity extinde clasa Activity și este responsabilă de sincronizarea datelor prin intermediul modulului Bluetooth. Se folosește metoda Calendar.getInstance() pentru obținerea unui calendar cu ajutorul căruia, prin intermediul conexiunii seriale dintre telefon și microcontroler, se obține momentul de timp pentru fiecare măsurătoare. Această comunicare se realizează prin folosirea unui socket Bluetooth.

Clasa ConnectBT este responsabilă cu conexiunea Bluetooth. ConnectBT extinde clasa abstractă AsyncTask pentru realizarea operațiilor în background și afișarea acestora în intterfața grafică la final, eliminând astfel nevoia unui fir de execuție suplimentar sau a unui handler. Metodele clasei AsyncTask care necesită suprascriere sunt doInBackground() și onPostExecute(). Metoda doInBackground() este folosită pentru realizarea operațiilor în background. Metoda onPostExecute() este apelată pentru a aduce rezultatele operațiilor din doInBackground() în interfața grafică.

În clasa ReadInput este declarat un fir de execuție suplimentar cu ajutorul căruia se face citirea datelor, trimise cu modulul Bluetooth de la senzorul de puls, și afișarea lor pe ecran. În interiorul acestei clase sunt implementate și funcțiile pentru butoanele Sync și Clean.

Activitatea Homescreen extinde clasa Activity și este activitatea care pornește interfața grafică principală atunci când se deschide aplicația. În această clasă este declarat obiectul BluetoothAdapter, folosit pentru obținerea listei de dispozitive Bluetooth asociate aplicației Android. Tot aici sunt implementate funcțiile pentru butoanele Search, Connect și Data, și este implementat un meniul pentru aplicație.

Clasa MyAdapter extinde ArrayAdapter și are rolul de a obține lista de dispozitive Bluetooth asociate telefonul Android și popularea unui TextView cu numele și adresa MAC a acestora. Componentele cele mai importante ale acestei clase sunt constructorul și metoda getView() care descrie translația dintre date si View. O altă metodă folosită este getItem(), metodă cu rolul de a obține datele asociate unei anumite poziții din setul de date.

PreferencesActivity este activitatea asociată setărilor din meniul aplicațiilor. Aceasta extinde clasa abstractă PreferenceActivity, o clasă care oferă posibilitatea afișării unei ferestre de preferințe pentru utilizator. Pagina de setări prezintă doua tipuri de componente:

ListPreference – permite utilizatorului selectarea unei opțiuni dintr-o listă predefinită. În această lucrare se folosește pentru selectarea orientării ecranului.

EditTextPreference – permite utilizatorului modificarea unei preferințe de tip text.

În clasa ActivityHelper sunt implementate opțiunile pentru orientarea ecranului, folosite în PreferencesActivity.

Clasa SearchDevice realizează cătarea dispozitivelor Bluetooth asociate telefonului. Dacă se găsesc dispozitive asociate, se adaugă într-o listă. În cazul în care nu există niciun dispozitiv asociat, se întoarce un mesaj de eroare: "No paired devices found, please pair your serial BT device and try again".

În clasa DataActivity sunt implementate butoanele ce deschid datele salvate în fișierele .csv din memoria telefonului Android. Clasele POData și TensData sunt clasele în care se realizează citirea fișierului cu date din memorie și se realizează parcurgerea datelor acestuia și împărtțirea pe linii și coloane.

În clasa CSVFile se declară un flux de intrare (inputStream) și se face parcurgearea și împărțirea datelor dintr-un BufferReader și împarțirea acestora pe linii și coloane în funcție de un caracter de delimitare, caracterul „ , ” aici.

2.2.5 Configurare Bluetooth

Pentru a putea folosi funcțiile Bluetooth trebuie, mai întâi, adăugate permisiunile în fișierul AndroidManifest.xml și inclus pachetul android.bluetooth în codul Java. Totodată, trebuie să verificăm dacă dispozitivul pe care dorim să folosim aplicația suportă conexiune Bluetooth. Dacă nu se poate realiza o conexiune Bluetooth, aplicația nu va putea fi folosită. Dacă dispozitivul este dotat cu Bluetooth, trebuie verificat dacă acesta este pornit. Dacă nu este pornit, aplicația va afișa o fereastră din care se poate porni conexiunea Bluetooth fără a părăsi aplicația.

mBtnSearch.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View arg0) {
mBTAdapter = BluetoothAdapter.getDefaultAdapter();

if (mBTAdapter == null) {
Toast.makeText(getApplicationContext(), "Bluetooth not found", Toast.LENGTH_SHORT).show();
} else if (!mBTAdapter.isEnabled()) {
Intent enableBT = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBT, BT_ENABLE_REQUEST);
} else {
new SearchDevices().execute();
}
}
});

BluetoothAdapter este necesar în orice activitate care are nevoie de Bluetooth. Există un singur adaptor Bluetooth pentru întreg sistemul. În urma apelului funcției getDefaultAdapter() va returna obiectul BluetoothAdapter, iar aplicația va putea interacționa cu adaptorul Bluetooth prin acest obiect. Dacă funcția returnează null, dispozitivul nu suportă conexiune Bluetooth, iar aplicația nu se va putea conecta la modulul Bluetooth. Odată ce am obținut adaptorul Bluetooth, apelăm funcția isEnabled() pentru a verifica dacă Bluetooth este pornit. Dacă această funcție returnează false trebuie apelat startActivityForResult() cu o intenție de acțiune (action intent) ACTION_REQUEST_ENABLE. Aceasta va afișa o fereastră de dialog care îi cere utilizatorului să pornească Bluetooth.

Figura 2.7 Fereastră de dialog pentru pornire Bluetooth

2.2.6 Citirea datelor salvate în memoria telefonului

Parametrii măsurați cu ajutorul pulsoximetrului si a tensiometrului sunt salvați în memoria telefonului sub forma unor fișiere .csv. Fișierele CSV (comma-separated values) sunt fișiere text ce conțin date separate prin virgulă. Aceste fișiere pot fi deschise sub forma unor foi de calcul cu programe ca Microsoft Excel.

Am ales folosirea acestui tip de fișier deoarece este simplu de folosit și poate fi deschis ca fișier text sau ca foaie de calcul înafara aplicației.

Clasa CSVFile realizează citirea fișierului .csv utilizând clasa BufferedReader. Șirul de caractere citit din fișier, pe linii cu funcția readLine(), este împărțit pe coloane utilizând funcția split() și caracterul "," ca delimitator.

public CSVFile(InputStream inputStream){

this.inputStream = inputStream;
}

public List<String[]> read(){
List<String[]> resultList = new ArrayList<String[]>();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
String csvLine;
while ((csvLine = reader.readLine()) != null) {
String[] row = csvLine.split(",");
resultList.add(row);
}
}

Pentru citirea fișierului .csv din memoria dispozitivului folosim funcția getExternalStorageDirectory() din clasa Environment.

File file = new File(Environment.getExternalStorageDirectory(),"/PulseApp/po.csv");

Pentru a putea accesa fișierele din memoria telefonului este necesar să se adauge în fișierul AndroidManifest.xml următoarea permisiune:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

Am ales să colorez diferit rândurile din tabelul afișat pentru a ușura citirea datelor. Am folosit pentru acest scop funcția setBackgroundResource().

if( i % 2 == 0)
{
c1.setBackgroundResource(R.color.blue);
}
else{
c1.setBackgroundResource(R.color.black);
}

tr.addView(c1);

2.2.7 Fire de execuție

Componentele unei aplicații rulează într-un singur proces și, în general, nu este nevoie de procese adiționale. Atunci când o aplicație pornește și nu are deja componente care rulează, Android va crea un proces UNIX nou pentru acea aplicație care va conține un singur fir de execuție (thread). Componentele care vor porni ulterior vor rula în acel thread, denumit și "main thread". Un proces poate avea mai multe fire de execuție. Firul de execuție principal este extrem de important, fiind responsabil de interacțiunea cu utilizatorul. El se ocupă de lansarea de evenimente spre diferite componente UI și este și firul în care aplicația interacționează cu diferite componente UI.

Sistemul nu creează fire de execuție diferite pentru fiecare componentă. Fiind singurul thread responsabil de partea UI, dacă aceasta este foarte încărcată, aplicația poate avea o performanță slabă. Astfel, pentru descărcarea de pe internet, interogarea de baze de date sau pentru orice fel de operațiune care nu afectează direct interfața cu utilizatorul și care ar putea fi de durată, trebuie folosite alte fire de execuție. Orice acțiune asupra interfeței de utilizator trebuie făcută doar din main thread, nu din thread-urile secundare.

Pentru a folosi fire de execuție secundare există mai multe opțiuni, cum ar fi: fire de execuție clasice, servicii sau mecanismul de broadcast & receive. Una din aceste opțiuni este mecanismul task asincron, care va fi folosit în această lucrare pentru realizarea și oprirea conexiunii Bluetooth. Acesta este reprezentat de clasa AsyncTask, o clasă ajutătoare pentru clasele Thread și Handler. Clasa este definită de un task care rulează pe un fir secundar în fundal și care publică automat rezultatul spre firul de execuție principal.

O clasă ale cărei instanțe vor fi executate într-un fir de execuție trebuie declarata ca fiind Runnable. Această interfață conține o singură metodă, și anume metoda run. Așadar, orice clasă ce descrie fire de execuție va conține o metoda run în care este implementat codul ce va fi executat de firul de execuție. Interfața Runnable este concepută ca fiind un protocol comun pentru obiectele care doresc sa execute un cod pe durata existenței lor.

Prin urmare, o clasă care instanțiază fire de execuție prin implementarea interfeței Runnable trebuie obligatoriu să implementeze metoda run.

Formatul general al unei clase care implementează interfața Runnable este:

private class ReadInput implements Runnable {

private boolean bStop = false;

private Thread t;

public ReadInput() {

t = new Thread(this, "Input Thread");

t.start();

}

În metoda run se citesc datele din memoria microcontrolerului și se afișează valoarea medie a pulsului. Atunci când se apasă butonul SYNC datele salvate în memoria EEPROM a microcontrolerului sunt afișate în TextView-ul mTxtReceive.

2.3 Sistemul de măsurare a datelor

Figura 2.8 Schema conceptuală a sistemului

Sistemul de măsurare al parametrilor, utilizat în această lucrare, este alcătuit dintr-un microcontroler la care se conectează senzorii, senzorul de puls, pulsoximetrul și tensiometrul digital. Datele măsurate de senzorul de puls sunt transmise, prin intermediul unui modul Bluetooth, dispozitivului Android și sunt afișate în timp real. Parametrii măsurați de pulsoximetru și tensiometru sunt salvați în fișiere de tip .csv, care sunt copiate în directorul aplicației din memoria dispozitivului Android.

Figura 2.9 Sistemul de măsurare a pulsului în timp real

Parametrii măsurați de pulsoximetru și tensiometru sunt salvați în fișiere de tip .csv, care sunt copiate în directorul aplicației din memoria dispozitivului Android.

Figura 2.10 Sistem de măsurare a nivelului de oxigen și tensiune arterială

În urma măsurătorilor s-au obținut următoarele seturi de date. După cum se poate observa, valorile măsurate cu senzorul de puls sunt apropiate de cele măsurate cu ajutorul pulsoximetrului.

Tabel 5 Valori măsurate pentru puls

Figura 2.11 Reprezentare valori puls

Parametrii măsurați cu ajutorul tensiometrului digital sunt reprezentațin în tabelul de mai jos.

Tabel 6 Valori măsurate pentru tensiunea arterială

În următorul tabel sunt valorile saturației de oxigen din sânge.

Tabel 7 Saturația de oxigen din sânge

Utilizarea aplicației

Aplicația, denumită Pulse Monitor, permite utilizatorului să măsoare pulsul în timp real cu ajutorul unui senzor de puls. Datele măsurate de senzorul de puls, conectat la microcontrolerul Arduino Uno, sunt transmise, cu ajutorul unui modul Bluetooth, unui dispozitiv mobil Android. Scopul principal al aplicației este monitorizarea pulsului, în timp real.

Figura 3.1 Măsurare puls în timp real

O altă funcție a aplicației este de a afișa datele măsurate și salvate cu ajutorul unui pulsoximetru și a unui tensiometru. Parametrii măsurați cu cele două dispozitive sunt salvați în câte un fișier de tip .csv, care este copiat în directorul PulseApp din memoria telefonului Android. Utilizatorul poate vedea astfel valoarea parametrilor măsurați și data la care s-au făcut măsurătorile.

Figura 3.2 Icoana aplicației din meniul dispozitivului Android

După lansarea aplicației, pe ecranul telefonului va apărea activitatea principală în care se poate realiza afișarea dispozitivelor Bluetooth asociate terminalului mobil, prin apăsarea butonului SEARCH sau deschiderea datelor salvate în memoria telefonului, prin apăsarea butonului DATA.

Dacă la apăsarea butonului SEARCH, Bluetooth nu este pornit, se va lansa o fereastră de dialog în care utilizatorul este întrebat dacă acordă aplicației permisiunea de a-l activa. În cazul în care îi este acordată permisiunea, se va trece la popularea ecranului cu dispozitivele Bluetooth asociate. Dacă se apasă butonul No, lista cu dispozitive nu va fi afișată și nu se va putea face conexiunea între telefonul mobil și microcontroler.

Figura 3.3 Fereastra principală și fereastra de dialog Bluetooth

Figura 3.4 Pornire Bluetooth și afișare dispozitive asociate

Prin apăsarea butonului CONNECT se realizează conexiunea la modulul Bluetooth și se deschide o nouă fereastră pentru afișarea datelor de la senzorul de puls.

Figura 3.5 Conectarea la modulul Bluetooth

În cazul în care dispozitivul Bluetooth la care se face conectarea nu este un modul de tip SPP, care să realizeze o conexiune serială fără fir, se va afișa un mesaj de eroare.

Figura 3.6 Mesaj de eroare conexiune Bluetooth

După conectarea la modulul Bluetooth, dacă microcontrolerul este alimentat și senzorul de puls este prins pe vârful degetului, se poate începe vizualizarea datelor de la microcontroler.

Figura 3.7 Afișare date de la senzorul de puls

La apăsarea butonului Sync datele salvate în memoria EEPROM a microcontrolerului sunt sincronizate cu aplicația Android și sunt afișate sub valoarea curentă a pulsului. Dacă în momentul în care se încearcă sincronizarea datelor, microcontrolerul este ocupat cu scrierea unui nou set de măsurători în memorie, se va afișa un mesaj în care utilizatorul este rugat să încerce din nou sincronizarea datelor.

Butonul Clear golește zona cu date sincronizate de pe ecran. Prin bifarea căsuței Scroll, la sincronizarea datelor, se va merge automat la ultima valoare salvată. Căsuța Read are rolul de a afișa valoarea pulsului atunci când este bifată și de a opri afișarea atunci când nu este bifată căsuța CheckBox.

La apăsarea butonului Send, se transmit microcontrolerului datele legate de oră și dată pe baza cărora se obține momentul în care s-a realizat fiecare măsurătoare.

Aplicația oferă posibilitatea de a schimba codul UUID a modulului Bluetooth folosit, în cazul în care de dorește utilizarea unui modul Bluetooth diferit. Acest lucru se poate realiza prin apăsarea butonului Settings prezent în meniul aplicației. Pe lângă această opțiune, se mai poate stabili orientarea ecranului și dimensiunea Bufferului în care este specificat numărul maxim de caractere din buffer.

Figura 3.8 Fereastra cu setări din meniul aplicației

Figura 3.9 Modificare orientării ecranului

Dacă este selectată varianta Auto, aplicația va utiliza senzorul de orientare al telefonului pentru a detecta poziția acestuia.

La apăsarea butonului DATA din activitatea principală a aplicației se deschide o nouă fereastră, din care se poate selecta setul de măsurători dorit pentru vizualizare.

Figura 3.10 Fereastra de selectare a setului de date

Datele salvate de la pulsoximetru și tensiometru sunt deschise sub forma unor tabele în care se pot vedea parametrii măsurați

Figura 3.11 Deschidere date din memorie

Concluzii

Scopul acestei lucrări a fost de a implementa un sistem de monitorizare a unor parametri medicali. S-a realizat un sistem de măsură a pulsului în timp real format dintr-un senzor de puls, un microcontroler Arduino Uno, un modul Bluetooth și un telefon inteligent cu aplicația de monitorizare de tip Android instalată. Aplicația Android recepționează, prin intermediul unei conexiuni Bluetooth, datele măsurate de senzorul de puls și le afișează în timp real. Datele măsurate sunt salvate și în memoria EEPROM a microcontrolerului Arduino Uno, și pot fi sincronizare cu aplicația Android și afișate pe ecranul telefonului.

Pe lângă funcția de a monitoriza pulsul în timp real, aplicația Android afișează datele salvate în memoria internă a telefonului de la un pulsoximetru și un tensiometru digital. Aceste date, măsurate și salvate sub forma unui fișier .csv, sunt copiate de către utilizator în directorul aplicației din memoria telefonului.

Cea mai mare provocare a lucrării a fost acumularea unei mari cantități de informații noi necesare implementării codului Arduino și Android.

Aplicația Android realizată poate fi îmbunătățită prin adăugarea posibilității de a citi date în timp real direct de la un pulsoximetru sau de la un tensiometru digital și prin afișarea unor grafice în timp real cu acești parametri. Pentru a ușura și mai mult procesul de monitorizare, se mai poate adăuga suport pentru salvarea datelor pe un server la care poate avea acces personal medical. Astfel, medicul poate verifica aceste date și poate chema pacientul pentru un control mai amănunțit.

Anexa 1 Cod Arduino

PO.ino

#include <PinChangeInt.h>

#include <PinChangeIntConfig.h>

#include <eHealth.h>

#include <eHealthDisplay.h>

int cont = 0;

void setup() {

Serial.begin(115200);

eHealth.initPulsioximeter();

//Attach the inttruptions for using the pulsioximeter.

PCintPort::attachInterrupt(6, readPulsioximeter, RISING);

}

void loop() {

Serial.print(eHealth.getBPM());

Serial.print(",");

Serial.print(eHealth.getOxygenSaturation());

Serial.print("\n");

delay(3000);

}

void readPulsioximeter(){

cont ++;

if (cont == 50) {

eHealth.readPulsioximeter();

cont = 0;

}

}

HR.ino

#include <eHealth.h>

#include <eHealthDisplay.h>

void setup() {

eHealth.readBloodPressureSensor();

Serial.begin(115200);

delay(100);

}

void loop() {

uint8_t numberOfData = eHealth.getBloodPressureLength();

for (int i = 0; i<numberOfData; i++) {

// The protocol sends data in this order

Serial.print(eHealth.bloodPressureDataVector[i].day);

Serial.print(F("."));

Serial.print(eHealth.numberToMonth(eHealth.bloodPressureDataVector[i].month));

Serial.print(F("."));

Serial.print(2000 + eHealth.bloodPressureDataVector[i].year);

Serial.print(F(" "));

if (eHealth.bloodPressureDataVector[i].hour < 10) {

Serial.print(0); // Only for best representation.

}

Serial.print(eHealth.bloodPressureDataVector[i].hour);

Serial.print(F(":"));

if (eHealth.bloodPressureDataVector[i].minutes < 10) {

Serial.print(0);// Only for best representation.

}

Serial.print(eHealth.bloodPressureDataVector[i].minutes);

Serial.print(",");

Serial.print(30+eHealth.bloodPressureDataVector[i].systolic);

Serial.print(",");

Serial.print(eHealth.bloodPressureDataVector[i].diastolic);

Serial.print(",");

Serial.print(eHealth.bloodPressureDataVector[i].pulse);

Serial.println();

}

delay(20000);

}

PulseSensorAmped_1dot2

#include <EEPROM.h>

#include <Time.h>

// VARIABILE

int pulsePin = 0; // Firul mov conectat la A0

int blinkPin = 13; // ledul clipeste la fiecare bataie

int fadePin = 5; // pin care se stinge la fiecare bataie

int fadeRate = 0;

int countQS = 0;

int countTime = 0;

int sum = 0;

int average = 0; //Valoare medie a pulsului

int average_err = 0; //Eroare medie

const int beat_start_calc = 10; //Numărătoarea incepe dupa a 5-a valoare

//Timer:

int yy, mm, dd, hh, mi, ss;

int a[100];

int b[100];

int current_add_arr = 0;

const int ADD_COUNT_SET = 511;

int maxCount = 25;

char command[50];

int pos_command = 0;

int iEEPROM = 2;

// variabile volatile folosite in rutina Interrupt!

volatile int BPM; // pulsul

volatile int Signal; // date brute de intrare

volatile int IBI = 600; // timpul dintre batai

volatile boolean Pulse = false; // adevarat cand unda de puls este mare

volatile boolean QS = false; // adevarat cand se detecteaza bataie

void setup(){

pinMode(blinkPin,OUTPUT); // clipeste odata cu bataile inimii

pinMode(fadePin,OUTPUT); // se stinge

Serial.begin(9600); // rata de transfer

interruptSetup(); // citeste pulsul la fiecare 2 ms

maxCount = EEPROM.read(0);

iEEPROM = EEPROM.read(1);

EEPROM.write(1,0);

}

void loop(){

char x;

if(Serial.available()){

x = Serial.read();

if(x == '\n'){

if(strstr(command, "sync") != NULL ){

char strTemp[3];

strTemp[0] = command[5];

strTemp[1] = command[6];

int hour = atoi(strTemp); //conversie string – integer

strTemp[0] = command[8];

strTemp[1] = command[9];

int minutes = atoi(strTemp);

strTemp[0] = command[11];

strTemp[1] = command[12];

int sec = atoi(strTemp);

strTemp[0] = command[14];

strTemp[1] = command[15];

int day = atoi(strTemp);

strTemp[0] = command[17];

strTemp[1] = command[18];

int month = atoi(strTemp);

strTemp[0] = command[20];

strTemp[1] = command[21];

int year = atoi(strTemp);

setTime(hour, minutes, sec, day, month, year);

Serial.print("sync");

for(int i=2; i<iEEPROM; i++){

Serial.print(EEPROM.read(i));

Serial.print(" ");

}

pos_command = 0;

memset(&command[0], 0, sizeof(command));

}

else if(strcmp(command, "clear") == 0){

iEEPROM = 2;

EEPROM.write(1, iEEPROM);

Serial.print("clear");

pos_command = 0;

memset(&command[0], 0, sizeof(command));

}

else if(strstr(command, "times") != NULL){

char strTimes[20];

for(int i=6; i<pos_command; i++)

strTimes[i-6] = command[i];

maxCount = atoi(strTimes);

if(maxCount > 5)

EEPROM.write(0, maxCount);

else

EEPROM.write(0, 25);

Serial.print("times ");

Serial.print(maxCount);

pos_command = 0;

memset(&command[0], 0, sizeof(command));

}

else if(strstr(command, "settime") != NULL){

char strTemp[3];

strTemp[0] = command[8];

strTemp[1] = command[9];

int hour = atoi(strTemp);

strTemp[0] = command[11];

strTemp[1] = command[12];

int minutes = atoi(strTemp);

strTemp[0] = command[14];

strTemp[1] = command[15];

int sec = atoi(strTemp);

strTemp[0] = command[17];

strTemp[1] = command[18];

int day = atoi(strTemp);

strTemp[0] = command[20];

strTemp[1] = command[21];

int month = atoi(strTemp);

strTemp[0] = command[23];

strTemp[1] = command[24];

int year = atoi(strTemp);

setTime(hour, minutes, sec, day, month, year);

pos_command = 0;

memset(&command[0], 0, sizeof(command));

}

else{

Serial.println("command error");

Serial.print(hour());

Serial.print(" ");

Serial.print(minute());

Serial.print(" ");

Serial.print(second());

Serial.print(" ");

Serial.print(day());

Serial.print(" ");

Serial.print(month());

Serial.print(" ");

Serial.print(year()-2000);

Serial.print(" ");

pos_command = 0;

memset(&command[0], 0, sizeof(command));

}

}

if(x != '\n'){

command[pos_command] = x;

pos_command++;

//Serial.print(x);

}

}

//Numarul de batai de la senzor

if(QS == false)

countQS++;

else

countQS = 0;

if(countQS > 50){ //Daca nu se primeste raspuns de la senzor se resereaza

countTime = 0;

sum = 0;

current_add_arr = 0;

}

if (QS == true){ // flag true cand se detecteaza puls

countTime++;

a[countTime] = BPM;

Serial.println(countTime);

Serial.println(BPM);

if(countTime >= maxCount){

average = 0;

average_err = 100;

while(average_err > 3){

int i = 0;

average = 0;

int count_temp = 0;

for(i = beat_start_calc; i <= countTime; i++){

if(a[i] != -1){

average += a[i];

count_temp++;

}

}

average /= count_temp;

average_err = 0;

int count_err = 0;

for(i = beat_start_calc; i <= countTime; i++){

if(a[i] != -1){

b[i] = abs(a[i] – average);

average_err += b[i];

count_err++;

}

}

average_err = average_err / count_err;

for(i = beat_start_calc; i <= countTime; i++)

if(b[i] > average_err)

a[i] = -1;

if(count_err == 2)

break;

}

//stochează valoarea medie

Serial.print("average");

Serial.println(average);

if(year() >= 2015){

EEPROM.write(iEEPROM, average);

iEEPROM++;

EEPROM.write(1,iEEPROM);

//EEPROM time, date:

EEPROM.write(iEEPROM, hour());

iEEPROM++;

EEPROM.write(iEEPROM, minute());

iEEPROM++;

EEPROM.write(iEEPROM, second());

iEEPROM++;

EEPROM.write(iEEPROM, day());

iEEPROM++;

EEPROM.write(iEEPROM, month());

iEEPROM++;

EEPROM.write(iEEPROM, year()-2000);

iEEPROM++;

EEPROM.write(1,iEEPROM);

}

countTime = 0;

}

QS = false; // reseteaza QS

}

delay(50); // delay

}

Anexa 2 Cod Android Studio

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.realtimepulse"
android:versionCode="1"
android:versionName="1.0" >

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

<uses-sdk
android:minSdkVersion="11"
android:targetSdkVersion="21" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:name="com.realtimepulse.MyApplication"
android:theme="@style/Theme.AppCompat" >
<activity
android:name="com.realtimepulse.MainActivity"
android:label="@string/app_name"
android:configChanges="orientation"
android:windowSoftInputMode="adjustResize|stateHidden" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<activity
android:name="com.realtimepulse.Homescreen"
android:configChanges="keyboardHidden|orientation"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".DataActivity"/>
<activity android:name=".POData"/>
<activity android:name="PreferencesActivity"/>
<activity android:name=".TensData"/>
</application>

</manifest>

build.gradle

apply plugin: 'com.android.application'

android {
compileSdkVersion 23
buildToolsVersion "23.0.3"

defaultConfig {
applicationId "com.realtimepulse "
minSdkVersion 11
targetSdkVersion 23
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
}

dependencies {
compile 'com.android.support:appcompat-v7:23.3.0' }

Homescreen.class

package com.realtimepulse;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

public class Homescreen extends Activity {

private Button mBtnSearch;
private Button mBtnConnect;
private ListView mLstDevices;

private BluetoothAdapter mBTAdapter;

private static final int BT_ENABLE_REQUEST = 10; // This is the code we use for BT Enable
private static final int SETTINGS = 20;

private UUID mDeviceUUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); // Standard SPP UUID

private int mBufferSize = 50000; //Default

public static final String DEVICE_EXTRA = "com.realtimepulse.SOCKET";
public static final String DEVICE_UUID = "com.realtimepulse.uuid";
private static final String DEVICE_LIST = "com.realtimepulse.devicelist";
private static final String DEVICE_LIST_SELECTED = "com.realtimepulse.devicelistselected";
public static final String BUFFER_SIZE = "com.realtimepulse.buffersize";
private static final String TAG = "BlueTest5-Homescreen";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.activity_homescreen);

ActivityHelper.initialize(this); //This is to ensure that the rotation persists across activities and not just this one
Log.d(TAG, "Created");

mBtnSearch = (Button) findViewById(R.id.btnSearch);
mBtnConnect = (Button) findViewById(R.id.btnConnect);
mLstDevices = (ListView) findViewById(R.id.lstDevices);
/*
*Check if there is a savedInstanceState. If yes, that means the onCreate was probably triggered by a configuration change
*like screen rotate etc. If that's the case then populate all the views that are necessary here
*/
if (savedInstanceState != null) {
ArrayList<BluetoothDevice> list = savedInstanceState.getParcelableArrayList(DEVICE_LIST);
if(list!=null){
initList(list);
MyAdapter adapter = (MyAdapter)mLstDevices.getAdapter();
int selectedIndex = savedInstanceState.getInt(DEVICE_LIST_SELECTED);
if(selectedIndex != -1){
adapter.setSelectedIndex(selectedIndex);
mBtnConnect.setEnabled(true);
}
} else {
initList(new ArrayList<BluetoothDevice>());
}

} else {
initList(new ArrayList<BluetoothDevice>());
}

mBtnSearch.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View arg0) {
mBTAdapter = BluetoothAdapter.getDefaultAdapter();

if (mBTAdapter == null) {
Toast.makeText(getApplicationContext(), "Bluetooth not found", Toast.LENGTH_SHORT).show();
} else if (!mBTAdapter.isEnabled()) {
Intent enableBT = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBT, BT_ENABLE_REQUEST);
} else {
new SearchDevices().execute();
}
}
});

mBtnConnect.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View arg0) {
BluetoothDevice device = ((MyAdapter) (mLstDevices.getAdapter())).getSelectedItem();
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
intent.putExtra(DEVICE_EXTRA, device);
intent.putExtra(DEVICE_UUID, mDeviceUUID.toString());
intent.putExtra(BUFFER_SIZE, mBufferSize);
startActivity(intent);
}
});
}

public void openData( View view)
{

Intent intent1= new Intent(this,DataActivity.class);
startActivity(intent1 ); }

/**
* Called when the screen rotates. If this isn't handled, data already generated is no longer available
*/
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
MyAdapter adapter = (MyAdapter) (mLstDevices.getAdapter());
ArrayList<BluetoothDevice> list = (ArrayList<BluetoothDevice>) adapter.getEntireList();

if (list != null) {
outState.putParcelableArrayList(DEVICE_LIST, list);
int selectedIndex = adapter.selectedIndex;
outState.putInt(DEVICE_LIST_SELECTED, selectedIndex);
}
}

@Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();
}

@Override
protected void onStop() {
// TODO Auto-generated method stub
super.onStop();
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case BT_ENABLE_REQUEST:
if (resultCode == RESULT_OK) {
msg("Bluetooth Enabled successfully");
new SearchDevices().execute();
} else {
msg("Bluetooth couldn't be enabled");
}

break;
case SETTINGS: //If the settings have been updated
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String uuid = prefs.getString("prefUuid", "Null");
mDeviceUUID = UUID.fromString(uuid);
Log.d(TAG, "UUID: " + uuid);
String bufSize = prefs.getString("prefTextBuffer", "Null");
mBufferSize = Integer.parseInt(bufSize);

String orientation = prefs.getString("prefOrientation", "Null");
Log.d(TAG, "Orientation: " + orientation);
if (orientation.equals("Landscape")) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
} else if (orientation.equals("Portrait")) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
} else if (orientation.equals("Auto")) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
}
break;
default:
break;
}
super.onActivityResult(requestCode, resultCode, data);
}

/**
* Quick way to call the Toast
* @param str
*/
private void msg(String str) {
Toast.makeText(getApplicationContext(), str, Toast.LENGTH_SHORT).show();
}

/**
* Initialize the List adapter
* @param objects
*/
private void initList(List<BluetoothDevice> objects) {
final MyAdapter adapter = new MyAdapter(getApplicationContext(), R.layout.list_item, R.id.lstContent, objects);
mLstDevices.setAdapter(adapter);
mLstDevices.setOnItemClickListener(new OnItemClickListener() {

@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
adapter.setSelectedIndex(position);
mBtnConnect.setEnabled(true);
}
});
}

private class SearchDevices extends AsyncTask<Void, Void, List<BluetoothDevice>> {

@Override
protected List<BluetoothDevice> doInBackground(Void… params) {
Set<BluetoothDevice> pairedDevices = mBTAdapter.getBondedDevices();
List<BluetoothDevice> listDevices = new ArrayList<BluetoothDevice>();
for (BluetoothDevice device : pairedDevices) {
listDevices.add(device);
}
return listDevices;

}

@Override
protected void onPostExecute(List<BluetoothDevice> listDevices) {
super.onPostExecute(listDevices);
if (listDevices.size() > 0) {
MyAdapter adapter = (MyAdapter) mLstDevices.getAdapter();
adapter.replaceItems(listDevices);
} else {
msg("No paired devices found, please pair your serial BT device and try again");
}
}

}

/**
* Custom adapter to show the current devices in the list.
*/
private class MyAdapter extends ArrayAdapter<BluetoothDevice> {
private int selectedIndex;
private Context context;
private int selectedColor = Color.parseColor("#abcdef");
private List<BluetoothDevice> myList;

public MyAdapter(Context ctx, int resource, int textViewResourceId, List<BluetoothDevice> objects) {
super(ctx, resource, textViewResourceId, objects);
context = ctx;
myList = objects;
selectedIndex = -1;
}

public void setSelectedIndex(int position) {
selectedIndex = position;
notifyDataSetChanged();
}

public BluetoothDevice getSelectedItem() {
return myList.get(selectedIndex);
}

@Override
public int getCount() {
return myList.size();
}

@Override
public BluetoothDevice getItem(int position) {
return myList.get(position);
}

@Override
public long getItemId(int position) {
return position;
}

private class ViewHolder {
TextView tv;
}

public void replaceItems(List<BluetoothDevice> list) {
myList = list;
notifyDataSetChanged();
}

public List<BluetoothDevice> getEntireList() {
return myList;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
View vi = convertView;
ViewHolder holder;
if (convertView == null) {
vi = LayoutInflater.from(context).inflate(R.layout.list_item, null);
holder = new ViewHolder();

holder.tv = (TextView) vi.findViewById(R.id.lstContent);

vi.setTag(holder);
} else {
holder = (ViewHolder) vi.getTag();
}

if (selectedIndex != -1 && position == selectedIndex) {
holder.tv.setBackgroundColor(selectedColor);
} else {
holder.tv.setBackgroundColor(Color.WHITE);
}
BluetoothDevice device = myList.get(position);
holder.tv.setText(device.getName() + "\n " + device.getAddress());

return vi;
}

}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.homescreen, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_settings:
Intent intent1 = new Intent(Homescreen.this, PreferencesActivity.class);
startActivityForResult(intent1, SETTINGS);
break;
}
return super.onOptionsItemSelected(item);
}
}

MainActivity

package com.realtimepulse;

import java.io.IOException;
import java.io.InputStream;
import java.util.Calendar;
import java.util.UUID;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.Editable;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {

private static final String TAG = "PulseApp-MainActivity";
private int mMaxChars = 50000;// Default
private UUID mDeviceUUID;
private BluetoothSocket mBTSocket;
private ReadInput mReadThread = null;

private boolean mIsUserInitiatedDisconnect = false;

// All controls here
private TextView mTxtReceive;
private TextView mTxtAverage;
private Button mBtnSend;
private Button mBtnClear;
private Button mBtnSync;
private ScrollView scrollView;
private CheckBox chkScroll;
private CheckBox chkReceiveText;
private String strReceive;

private boolean mIsBluetoothConnected = false;

private BluetoothDevice mDevice;

private ProgressDialog progressDialog;

final Context context = this;
private String strTimes = "";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActivityHelper.initialize(this);

Intent intent = getIntent();
Bundle b = intent.getExtras();
mDevice = b.getParcelable(Homescreen.DEVICE_EXTRA);
mDeviceUUID = UUID.fromString(b.getString(Homescreen.DEVICE_UUID));
mMaxChars = b.getInt(Homescreen.BUFFER_SIZE);

Log.d(TAG, "Ready");

mBtnSend = (Button) findViewById(R.id.btnSave);
mBtnClear = (Button) findViewById(R.id.btnClear);
mBtnSync = (Button) findViewById(R.id.btnSync);
mTxtReceive = (TextView) findViewById(R.id.txtReceive);
mTxtAverage = (TextView) findViewById(R.id.txtAverage);
scrollView = (ScrollView) findViewById(R.id.viewScroll);
chkScroll = (CheckBox) findViewById(R.id.chkScroll);
chkReceiveText = (CheckBox) findViewById(R.id.chkReceiveText);

mTxtReceive.setMovementMethod(new ScrollingMovementMethod());

mBtnSend.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View arg0) {

// get prompts.xml view
LayoutInflater li = LayoutInflater.from(context);
View promptsView = li.inflate(R.layout.prompts, null);

AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(
context);

// set prompts.xml to alertdialog builder
alertDialogBuilder.setView(promptsView);

final EditText userInput = (EditText) promptsView
.findViewById(R.id.editTextDialogUserInput);

// set dialog message
alertDialogBuilder
.setCancelable(false)
.setPositiveButton("OK",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int id) {
// get user input and set it to result
// edit text
strTimes = userInput.getText()
.toString();

try {
mBTSocket
.getOutputStream()
.write(("times " + strTimes + "\n")
.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
})
.setNegativeButton("Cancel",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int id) {
dialog.cancel();
}
});

// create alert dialog
AlertDialog alertDialog = alertDialogBuilder.create();

// show it
alertDialog.show();
}
});

mBtnSync.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View arg0) {
try {
String str = "sync ";

Calendar cal = Calendar.getInstance();

str += String.format("%tH %tM %tS %td %tm %ty\n", cal, cal,
cal, cal, cal, cal);

mBTSocket.getOutputStream().write(str.getBytes());

} catch (IOException e) {
e.printStackTrace();
}
}
});

mBtnClear.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View arg0) {
mTxtReceive.setText("");
try {
mBTSocket.getOutputStream().write("clear\n".getBytes());
} catch (IOException e) {
e.printStackTrace();
}

}
});

}

private class ReadInput implements Runnable {

private boolean bStop = false;
private Thread t;

public ReadInput() {
t = new Thread(this, "Input Thread");
t.start();
}

public boolean isRunning() {
return t.isAlive();
}

@Override
public void run() {
InputStream inputStream;

try {
inputStream = mBTSocket.getInputStream();
while (!bStop) {
byte[] buffer = new byte[256];
if (inputStream.available() > 0) {
inputStream.read(buffer);
int i = 0;
/*
* This is needed because new String(buffer) is taking
* the entire buffer i.e. 256 chars on Android 2.3.4
* http://stackoverflow.com/a/8843462/1287554
*/
for (i = 0; i < buffer.length && buffer[i] != 0; i++) {
}
final String strInput = new String(buffer, 0, i);

/*
* If checked then receive text, better design would
* probably be to stop thread if unchecked and free
* resources, but this is a quick fix
*/

if (chkReceiveText.isChecked()) {
mTxtReceive.post(new Runnable() {
@SuppressLint("ShowToast")
@Override
public void run() {

//mTxtReceive.setText("");
mTxtReceive.append("");
strReceive += strInput;

if (strReceive.indexOf("average") >= 0) {
String strAverage[] = strReceive
.split("average");

int pos = strAverage[1].indexOf("\n");
if(pos >= 0)
strAverage[1] =
strAverage[1].substring(0, pos);

Calendar cal = Calendar.getInstance();

mTxtAverage.setText("Puls: "
+ strAverage[1]
+ " bpm"
);

strReceive = "";
} else if (strReceive.indexOf("sync") >= 0) {
String strEEPROM[] = strInput
.split("sync");

String strEEPROM2 = "";
if (strEEPROM.length > 0) {

String arrStr[] = strEEPROM[1]
.split(" ");

if (arrStr.length >= 6
&& arrStr.length % 7 == 0) {
int k = 0;
while (k < arrStr.length) {
strEEPROM2 += "Puls: "
+ arrStr[k++]
+ " bpm "
+ " Data: ";
strEEPROM2 += arrStr[k++]
+ ":";
strEEPROM2 += arrStr[k++]
+ ":";
strEEPROM2 += arrStr[k++]
+ " ";
strEEPROM2 += arrStr[k++]
+ "/";
strEEPROM2 += arrStr[k++]
+ "/";
strEEPROM2 += arrStr[k++]
+ "\n";

}
mTxtReceive.setText("");
mTxtReceive.setText(strEEPROM2);
}else{
mTxtReceive.setText("");
mTxtReceive.setText("Sync missing data…\n Please press Sync again");
}

}

Toast.makeText(getApplicationContext(),
"Synced", Toast.LENGTH_SHORT)
.show();

strReceive = "";
} else if (strReceive.indexOf("clear") >= 0) {
// mTxtAverage.setText("Cleaned");
Toast.makeText(getApplicationContext(),
"Cleaned", Toast.LENGTH_SHORT)
.show();

strReceive = "";
} else if (strReceive.indexOf("times") >= 0) {

Toast.makeText(getApplicationContext(),
"Set times", Toast.LENGTH_SHORT)
.show();
strReceive = "";
}

int txtLength = mTxtReceive
.getEditableText().length();
if (txtLength > mMaxChars) {
mTxtReceive.getEditableText().delete(0,
txtLength – mMaxChars);
}

if (chkScroll.isChecked()) { // Scroll only
// if this
// is
// checked
scrollView.post(new Runnable() { // Snippet

// http://stackoverflow.com/a/4612082/1287554
@Override
public void run() {
scrollView
.fullScroll(View.FOCUS_DOWN);
}
});
}
}
});
}

}
Thread.sleep(500);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

public void stop() {
bStop = true;
}

}

private class DisConnectBT extends AsyncTask<Void, Void, Void> {

@Override
protected void onPreExecute() {
}

@Override
protected Void doInBackground(Void… params) {

if (mReadThread != null) {
mReadThread.stop();
while (mReadThread.isRunning())
; // Wait until it stops
mReadThread = null;

}

try {
mBTSocket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

return null;
}

@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
mIsBluetoothConnected = false;
if (mIsUserInitiatedDisconnect) {
finish();
}
}

}

private void msg(String s) {
Toast.makeText(getApplicationContext(), s, Toast.LENGTH_SHORT).show();
}

@Override
protected void onPause() {
if (mBTSocket != null && mIsBluetoothConnected) {
new DisConnectBT().execute();
}
Log.d(TAG, "Paused");
super.onPause();
}

@Override
protected void onResume() {
if (mBTSocket == null || !mIsBluetoothConnected) {
new ConnectBT().execute();
}
Log.d(TAG, "Resumed");
super.onResume();
}

@Override
protected void onStop() {
Log.d(TAG, "Stopped");
super.onStop();
}

@Override
protected void onSaveInstanceState(Bundle outState) {
// TODO Auto-generated method stub
super.onSaveInstanceState(outState);
}

private class ConnectBT extends AsyncTask<Void, Void, Void> {
private boolean mConnectSuccessful = true;

@Override
protected void onPreExecute() {
progressDialog = ProgressDialog.show(MainActivity.this, "Hold on",
"Connecting");// http://stackoverflow.com/a/11130220/1287554
}

@Override
protected Void doInBackground(Void… devices) {

try {
if (mBTSocket == null || !mIsBluetoothConnected) {
mBTSocket = mDevice.createInsecureRfcommSocketToServiceRecord(mDeviceUUID);
BluetoothAdapter.getDefaultAdapter().cancelDiscovery();
mBTSocket.connect();
}
} catch (IOException e) {
// Unable to connect to device
e.printStackTrace();
mConnectSuccessful = false;
}
return null;
}

@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);

if (!mConnectSuccessful) {
Toast.makeText(
getApplicationContext(),
"Could not connect to device. Is it a Serial device? Also check if the UUID is correct in the settings",
Toast.LENGTH_LONG).show();
finish();
} else {
msg("Connected to device");
mIsBluetoothConnected = true;
mReadThread = new ReadInput(); // Kick off input reader

try {
String str = "settime ";

Calendar cal = Calendar.getInstance();

str += String.format("%tH %tM %tS %td %tm %ty\n", cal, cal,
cal, cal, cal, cal);

mBTSocket.getOutputStream().write(str.getBytes());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

progressDialog.dismiss();
}

}

}

PreferencesActivity

package com.realtimepulse;

import java.util.Map;

import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;

public class PreferencesActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityHelper.initialize(this);
addPreferencesFromResource(R.xml.preferences); // Using this for compatibility with Android 2.2 devices
}

public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
Preference pref = findPreference(key);

if (pref instanceof ListPreference) {
ListPreference listPref = (ListPreference) pref;
pref.setSummary(listPref.getEntry());
ActivityHelper.initialize(this);
}

if (pref instanceof EditTextPreference) {
EditTextPreference editPref = (EditTextPreference) pref;
pref.setSummary(editPref.getText());
}
}

@Override
protected void onPause() {

PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this);
super.onPause();
}

@Override
protected void onResume() {
PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this);
Map<String, ?> keys = PreferenceManager.getDefaultSharedPreferences(this).getAll();

for (Map.Entry<String, ?> entry : keys.entrySet()) {
Preference pref = findPreference(entry.getKey());
if (pref != null) {
pref.setSummary(entry.getValue().toString());
}
}

super.onResume();
}

}

ActivityHelper

package com.realtimepulse;

import android.app.Activity;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.preference.PreferenceManager;

public class ActivityHelper {
public static void initialize(Activity activity) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);

String orientation = prefs.getString("prefOrientation", "Null");
if ("Landscape".equals(orientation)) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
} else if ("Portrait".equals(orientation)) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
} else {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
}
}
}

CSVFile

package com.realtimepulse;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

public class CSVFile {
InputStream inputStream;

public CSVFile(){

}

public CSVFile(InputStream inputStream){

this.inputStream = inputStream;
}

public List<String[]> read(){
List<String[]> resultList = new ArrayList<String[]>();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
String csvLine;
while ((csvLine = reader.readLine()) != null) {
String[] row = csvLine.split(",");
resultList.add(row);
}
}
catch (IOException ex) {
throw new RuntimeException("Error in reading CSV file: "+ex);
}
finally {
try {
inputStream.close();
}
catch (IOException e) {
throw new RuntimeException("Error while closing input stream: "+e);
}
}
return resultList;
}
}

DataActivity

package com.realtimepulse;

import android.content.Intent;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class DataActivity extends ActionBarActivity {
TextView text1;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_data);
text1 =( TextView )findViewById(R.id.set_date);
}

public void openData( View view)
{
String button_test;
button_test =((Button) view).getText().toString();
if (button_test.equals("PO Data"))
{
Intent intent1= new Intent(this,POData.class);
startActivity(intent1 );
}

else if (button_test.equals("HR Data"))
{
Intent intent= new Intent(this,TensData.class);
startActivity(intent );
}
}

}

POData

package com.realtimepulse;

import android.app.Activity;
import android.content.Intent;
import android.os.Environment;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ListView;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class POData extends Activity {
private ListView listView;
TableLayout table;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_po);
table = (TableLayout)findViewById(R.id.dataList);

read();

}

public void read()
{

File file = new File(Environment.getExternalStorageDirectory(), "/PulseApp/po.csv");

InputStream inputStream = null;
try {
inputStream = new BufferedInputStream(new FileInputStream(file));

CSVFile csvFile1 = new CSVFile(inputStream);
List<String[]> stringArrayList = csvFile1.read();
table.setStretchAllColumns(true);
table.bringToFront();

for(int i = 0; i < stringArrayList.size(); i++){
TableRow tr = new TableRow(this);

String[] strings=stringArrayList.get(i);

for(int x=0;x<strings.length;x++){

List<String> stringArrayList1=new ArrayList<String>(
Arrays.asList(strings[x].split(";")));

for(int w=0; w<stringArrayList1.size(); w++)
{
TextView c1 = new TextView(this);
c1.setText(stringArrayList1.get(w)+" ");

if( i % 2 == 0)
{
c1.setBackgroundResource(R.color.red);
}
else{
c1.setBackgroundResource(R.color.black);
}

tr.addView(c1);
}
}
table.addView(tr);
}

} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
};
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {

menu.add(Menu.NONE, 1, Menu.NONE, "Actualizare Date");

return true;
}

public boolean onOptionsItemSelected(MenuItem item) {
int option = item.getItemId();

switch (option) {

case 1:
Intent i = new Intent(getBaseContext(), POData.class);
startActivity(i);

break;

default:
break;
}

return super.onOptionsItemSelected(item);
}
}

TensData

package com.realtimepulse;

import android.app.Activity;
import android.content.Intent;
import android.os.Environment;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ListView;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class TensData extends Activity {
TableLayout table;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.hr_activity);
table = (TableLayout)findViewById(R.id.dataList1);

read();

}

public void read()
{

File file = new File(Environment.getExternalStorageDirectory(), "/PulseApp/hr.csv");

InputStream inputStream = null;
try {
inputStream = new BufferedInputStream(new FileInputStream(file));

CSVFile csvFile1 = new CSVFile(inputStream);
List<String[]> stringArrayList = csvFile1.read();
table.setStretchAllColumns(true);
table.bringToFront();

for(int i = 0; i < stringArrayList.size(); i++){
TableRow tr = new TableRow(this);

String[] strings=stringArrayList.get(i);

for(int x=0;x<strings.length;x++){

List<String> stringArrayList1=new ArrayList<String>(
Arrays.asList(strings[x].split(";")));

for(int w=0; w<stringArrayList1.size(); w++)
{
TextView c1 = new TextView(this);
c1.setText(stringArrayList1.get(w)+" ");

if( i % 2 == 0)
{
c1.setBackgroundResource(R.color.red);
}
else{
c1.setBackgroundResource(R.color.black);
}

tr.addView(c1);
}

}

table.addView(tr);
}

} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
};
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {

menu.add(Menu.NONE, 1, Menu.NONE, "Actualizare Date");

return true;
}

public boolean onOptionsItemSelected(MenuItem item) {
int option = item.getItemId();

switch (option) {

case 1:
Intent i = new Intent(getBaseContext(), TensData.class);
startActivity(i);

break;

default:
break;
}

return super.onOptionsItemSelected(item);
}
}

activity_data.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Deschideți setul de date dorit"
android:id="@+id/set_date"

/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="PO Data"
android:layout_below="@+id/set_date"
android:onClick="openData"
android:id="@+id/button2" />

<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="HR Data"
android:onClick="openData"
android:id="@+id/button1"
android:layout_below="@+id/button2"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginTop="104dp" />

<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Pulse Data"
android:onClick="openData"
android:id="@+id/button3"
android:layout_below="@+id/button1"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginTop="104dp" />

</RelativeLayout>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Deschideți setul de date dorit"
android:id="@+id/set_date"

/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="PO Data"
android:layout_below="@+id/set_date"
android:onClick="openData"
android:id="@+id/button2" />

<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="HR Data"
android:onClick="openData"
android:id="@+id/button1"
android:layout_below="@+id/button2"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginTop="104dp" />

<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Pulse Data"
android:onClick="openData"
android:id="@+id/button3"
android:layout_below="@+id/button1"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginTop="104dp" />

</RelativeLayout>

activity_homescreen.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Deschideți setul de date dorit"
android:id="@+id/set_date"

/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="PO Data"
android:layout_below="@+id/set_date"
android:onClick="openData"
android:id="@+id/button2" />

<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="HR Data"
android:onClick="openData"
android:id="@+id/button1"
android:layout_below="@+id/button2"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginTop="104dp" />

<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Pulse Data"
android:onClick="openData"
android:id="@+id/button3"
android:layout_below="@+id/button1"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginTop="104dp" />

</RelativeLayout>

activity_helpscreen.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".Helpscreen" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />

</RelativeLayout>

activity_po

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none"
android:layout_weight="1"
android:fillViewport="false">

<HorizontalScrollView
android:id="@+id/horizontalView"
android:layout_height="wrap_content"
android:scrollbars="horizontal|vertical"
android:layout_width="wrap_content"
android:layout_marginTop="5dip">

<TableLayout
android:id="@+id/dataList"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="5dp">

</TableLayout>

</HorizontalScrollView>

</ScrollView>

hr_activity

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none"
android:layout_weight="1"
android:fillViewport="false">

<HorizontalScrollView
android:id="@+id/horizontalView"
android:layout_height="wrap_content"
android:scrollbars="horizontal|vertical"
android:layout_width="wrap_content"
android:layout_marginTop="5dip">

<TableLayout
android:id="@+id/dataList"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="5dp">

</TableLayout>

</HorizontalScrollView>

</ScrollView>

item_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/itm"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_marginLeft="20dp" />

</RelativeLayout>

list_item.xml

<?xml version="1.0" encoding="utf-8"?>

<!– Definig a container for you List Item –>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:gravity="center_vertical"

android:textAlignment="center">

<!– Defining where should text be placed. You set you text color here –>

<TextView

android:id="@+id/lstContent"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:textSize="30sp"

android:padding="5dp"

android:textColor="#000000" />

</LinearLayout>

prompts.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout_root"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:padding="10dp" >

<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Type Your Message : "
android:textAppearance="?android:attr/textAppearanceLarge" />

<EditText
android:id="@+id/editTextDialogUserInput"
android:layout_width="match_parent"
android:layout_height="wrap_content" >

<requestFocus />

</EditText>

</LinearLayout>

preferences.xml

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >

<EditTextPreference
android:title="@string/pref_uuid_title"
android:summary="@string/pref_uuid_summary"
android:key="prefUuid"
android:defaultValue="00001101-0000-1000-8000-00805F9B34FB"/>

<ListPreference
android:key="prefOrientation"
android:entries="@array/orientation"
android:entryValues="@array/orientationValues"
android:summary="@string/pref_orientation_summary"
android:title="@string/pref_orientation_title" />

<EditTextPreference
android:key="prefTextBuffer"
android:title="@string/pref_text_buffer_title"
android:summary="@string/pref_text_buffer_summary"
android:defaultValue = "50000"
android:inputType = "number"
/>

</PreferenceScreen>

Similar Posts