Come costruire in casa un saturimetro

Con l’evolversi dell’epidemia di Coronavirus, è diventato evidente che la saturazione di ossigeno in aria è un indicatore diagnostico di polmonite interstiziale da Covid più affidabile rispetto alla sola visita, che spesso risulta sostanzialmente nella norma. Il valore di satO2 può essere utilizzato anche al domicilio per valutare l’evoluzione della malattia e viene attualmente indicato come uno dei parametri per l’eventuale ricorso a cure ospedaliere. Data la carenza di saturimetri, che risultano esauriti sia presso farmacie e aziende specializzate che presso la grande distribuzione e i colossi dell’e-commerce, abbiamo progettato e realizzato un saturimetro di emergenza a bassa tecnologia, partendo da circuiti semplici e schede elettroniche normalmente già presenti nella “cassetta degli attrezzi” di un Maker. Siamo riusciti a realizzare il primo prototipo in poco più di una settimana, attingendo alle risorse intellettuali rese liberamente disponibili e pubblicate online dagli appassionati di elettronica di tutto il mondo, secondo la filosofia open source.

Quindi, sfruttando appieno le caratteristiche della scheda ESP32, necessaria per eseguire i complessi calcoli necessari all’ottenimento di un parametro di satO2 affidabile, abbiamo implementato nel software anche la possibilità di connettersi alla rete WiFi a casa del paziente per trasmettere in tempo reale via email i parametri rilevati. Questa tecnologia può affiancarsi ai progetti di sorveglianza domiciliare, in corso di attivazione in questi giorni, secondo i quali un infermiere si reca fisicamente a domicilio del paziente per rilevarne la saturazione; tale strategia comporta tuttavia per l’operatore sanitario il rischio di esposizione al Covid.

Ci teniamo a precisare che questo progetto è legato al contesto emergenziale attuale. Qualunque dispositivo medico deve sottostare a precise norme che ne garantiscono la qualità e l’affidabilità, soprattutto se si tratta di prendere decisioni importanti, come un ricovero, sulla base dei parametri da esso forniti. In disperata carenza di saturimetri “certificati” abbiamo comunque adottato una serie di accorgimenti per verificare l’affidabilità di quelli costruiti artigianalmente, come spiegheremo in seguito. Il nostro lavoro è sicuramente migliorabile; ad esempio alcuni saturimetri industriali utilizzano un giroscopio per compensare le vibrazioni del dito. Il nostro non è così raffinato, anche perché abbiamo dovuto essere pragmatici e raggiungere un risultato accettabile in tempi stretti. Avremmo potuto realizzare da zero anche il circuito “LED + rilevatore di luminosità”. Data la necessità di contenere le dimensioni e le difficoltà nel raggiungimento di una calibrazione ottimale, abbiamo preferito usare un modulo già pronto, a basso costo e di ampia disponibilità sui siti di e-commerce.

Di seguito riportiamo le istruzioni per realizzare questo progetto, che rilasciamo gratuitamente sotto licenza open source. Ci rivolgiamo ai Maker e agli appassionati delle discipline coinvolte: miglioratelo, realizzate più saturimetri possibile e distribuiteli a chi ne ha bisogno! Eventualmente rivolgetevi al vostro medico di famiglia, che saprà indicarvi chi ne potrà beneficiare maggiormente. Se doveste incontrare imprecisioni, segnalatecele: siamo in corsa conto il tempo ed è facile sbagliare.

Saturimetro – istruzioni prima configurazione

All’accensione, il saturimetro mostra la scritta “Avvio WiFi”. È necessario connetterlo ad una rete WiFi per poterlo usare. Se è la prima volta che lo si adopera sotto una specifica rete WiFi (per esempio quella di casa), bisogna collegarlo a questa rete inserendone le credenziali. Per farlo, il saturimetro genera una propria rete WiFi di nome “esp32ap” (ovvero si avvia in modalità Access Point). Bisognerà connettersi a questa rete usando uno smartphone o dal computer di casa. Questo consente di accedere ad una pagina di configurazione presente nel software del saturimetro.

Le immagini successive si riferiscono alla configurazione attraverso uno smartphone.

  1. Il saturimetro impiega circa 30 secondi ad attivare la modalità Access Point, al termine dei quali si renderà disponibile la rete “esp32ap”.

Sul telefonino comparirà una schermata simile a questa:

  1. Selezionare la rete esp32ap, se è la prima volta che ci si connette inserire la password: 12345678.
  2. Una volta connessi a questa rete, lo smartphone dovrebbe mostrare automaticamente il popup “configura la rete”. In questo caso toccarlo per accedere alla pagina di configurazione. In alternativa si può toccare il nome della rete per accedere alla pagina di configurazione. Se non si riuscisse con nessuno dei due metodi è possibile:
    1. aprire la rete (sempre toccando sulla voce “esp32ap”),
    1. identificare l’indirizzo del server (ovvero quello del saturimetro)
    1. aprirlo nel browser aggiungendo “_ac”. (Nella nostra configurazione l’indirizzo da aprire col browser è “172.217.28.1/_ac”, ma consigliamo di controllare come sopra indicato la correttezza dell’indirizzo)
  1. Una volta visualizzata la pagina di configurazione, aprire il menu in alto a destra (quello con le tre righe orizzontali)
  2. Toccare “Configure new AP”. Comparirà l’elenco delle reti WiFi rilevate.
  3. Selezionare quella di casa e inserire la password nell’apposito campo.
  4. Toccare “Apply”

A procedura terminata, comparirà la scritta “WiFi Ok” e si potrà procedere alle rilevazioni.

Il saturimetro d’ora in avanti si ricorderà di questa rete, per cui all’accensione si collegherà automaticamente ad essa.

Per rilevare la saturazione è sufficiente inserire un dito, tenendo la mano più ferma possibile, per esempio appoggiando completamente mano e avambraccio ad un tavolo. Dovendo rilevare un movimento nell’ordine dei micron (pensate allo spessore di una arteriola del dito che si riempie di sangue), è facile capire come mai sia così importante tenere la mano ferma.

Per valutare se il saturimetro “prende” o meno, si osserva il grafico sottostante. Quando si vedono le pulsazioni, vuol dire che la mano è stabile e in una buona posizione ed è solo questione di tempo per ottenere una misura. In caso contrario, è necessario muovere il dito per trovare la posizione ottimale, ovvero con una leggera pressione (passiva: i muscoli della mano devono essere rilassati) sul vetro del sensore.

Il saturimetro fornirà dei valori. Generalmente le prime rilevazioni sono scarsamente affidabili anche nei saturimetri “commerciali”, pertanto ne effettuerà 10 (comporta circa un minuto di rilevazione). Tutti i valori verranno registrati. Al termine, comparirà la scritta “Email inviata” e le 10 saturazioni saranno trasmesse all’indirizzo di posta del medico, configurato nel software, per una sua interpretazione.

Video: https://youtu.be/x3eEkH8kMfc

Istruzioni di assemblaggio

Lista dei materiali:

I file per la stampa 3D e il software (sketch Arduino) sono disponibili qui: https://drive.google.com/open?id=1zvxwnRE-UM87otZBnbQbLkbCB3mnYoJY

Costruire il saturimetro

Meccanica

Il contenitore è stampato in 3D. Qualunque colore andrà bene ma, se ne avete la possibilità, usate colori scuri perché interferiscono meno con il sensore di luminosità. Come materiale, è indifferente l’uso di PLA o ABS, non dovendo sopportare stress meccanici particolari. Stampate i 3 file, orientandoli opportunamente, rimuovete le strutture di sostegno al termine della stampa. Si consiglia di usare 200 gradi per l’estrusore e 60 per il piano nelle stampe di PLA; 240 per l’estrusore e 100 per il piano per l’ABS.

Inserite la scheda ESP nel pezzo inferiore con i PIN rivolti verso l’alto. Inserite lo schermo LCD nell’apposito alloggiamento sul pezzo superiore. Utilizzate 4 viti M2 per fissarlo. In alternativa (o in aggiunta) applicate della colla, per esempio con una pistola incollatrice. La parte meccanicamente più sollecitata è quella posteriore, dove i PIN attraversano la plastica. Rinforzarla con della colla è una buona idea.

Inserite il circuito di rilevazione nell’apposito alloggiamento del pezzo intermedio, con i PIN che lo attraversano verso la parte interna. Fissatelo con delle viti o, meglio, con una pistola incollatrice.

Tagliate due rettangoli di gommapiuma della misura della parte interna della “pinza” del saturimetro. Se siete in dubbio con le dimensioni, meglio abbondare: la gommapiuma farà da schermo alla luce esterna. Ritagliate un foro rettangolare in corrispondenza dei led del sensore. Incollate i bordi della gommapiuma alle parti stampate.

Utilizzate due viti M3 per collegare lo snodo tra pezzo intermedio e superiore. Le viti servono anche per tenere in posizione due molle. È importante infatti che la pressione sul dito sia leggera e costante. Abbiamo identificato più modi per ottenere questo effetto.

  • Per il primo (quello più valido esteticamente) utilizzate una molla di una penna a sfera, tagliatene una sezione dello spessore di pochi avvolgimenti, svolgete i due capi per una lunghezza di mezzo centimetro (mantenendo alcuni avvolgimenti al centro). Raddrizzateli ed inseriteli nei due piccoli forellini sul retro/ai lati dei pezzi superiore ed intermedio. Posizionate poi le viti di collegamento tra i due pezzi facendole passare al centro delle molle, in modo da fissarle. Se ne avete a disposizione, utilizzate due dadi autobloccanti nell’incavo centrale.

Il secondo implica il posizionamento di 4 viti M2 ai lati del saturimetro (verso la parte anteriore). Le viti fungono da pulegge per un elastico, opportunamente dimensionato, oppure per delle molle ad estensione. In quest’ultimo caso la tensione della molla va accuratamente regolata; la pressione risultante deve essere leggera, pena il mancato rilevamento del battito.

Elettronica

I collegamenti vanno realizzati secondo la tabella.

ESP32 MAX3012 SSD1306
GND GND GND
3,3V VCC VCC
22 SCL SCL
21 SDA SDA
34 INT  

Sia il sensore del saturimetro che lo schermo comunicano attraverso lo standard I2C. I collegamenti andranno dunque effettuati tra i PIN SDA e SCL di entrambi e i relativi PIN dell’ESP32, che sono rispettivamente 21 e 22. Entrambi poi condividono il voltaggio (3,3V) e la messa a terra, pertanto andranno collegati agli omonimi PIN di ESP (3,3V e GND). Andrà inoltre collegato il PIN “INT” sul sensore con il PIN 34 dell’ESP32 (quest’ultimo può essere variato a livello software in caso di necessità).

Se avete dimestichezza con i cavi Dupont, essi sono un buon modo per evitare corto circuiti, anche se non è facile farli stare nel poco spazio a disposizione e, comunque, dovrete saldarne alcuni tra loro (i due SDA, SCL e 3,3V giocoforza; GND è facoltativo essendoci molteplici PIN con questa funzione sulla ESP32).

In alternativa, potete saldarli direttamente sui PIN del sensore. Fare le saldature in spazi così piccoli non è facile, quindi armatevi di pazienza. Per aumentare lo spazio e diminuire i conflitti tra cavi, potete piegare (con MOLTA cautela) i pin dell’ESP32.

Completato e testato il circuito, è una buona idea rinforzare i fili attorno alle saldature con una goccia di colla della pistola incollatrice, sapendo che se mai dovessimo toglierla basterà avvicinare la punta del saldatore per ritrasformarla in liquido.

Programmazione

Per programmare la scheda Esp32 abbiamo scelto di utilizzare l’IDE di Arduino. Le istruzioni per installare il programma e configurare la scheda si trovano qui: https://randomnerdtutorials.com/installing-the-esp32-board-in-arduino-ide-windows-instructions/ . Quando si tratta di scegliere la scheda dal menù “Strumenti -> scheda” scegliete però “ESP32 Dev Module”. Per poter utilizzare il nostro programma avrete bisogno di installare delle librerie, che implementano delle funzionalità aggiuntive, per esempio la capacità di collegarsi al WiFi.

Quindi dovete scaricare e installare:

Calibrazione e prove sul campo (update 12/5/20: questa è la vecchia procedura, quella nuova la trovate dopo le conclusioni)

Durante le nostre prime prove, abbiamo notato che i valori rilevati di frequenza cardiaca corrispondevano perfettamente a quelli registrati in simultanea da un saturimetro “medico” ufficiale, dimostrando che il relativo algoritmo e la metodica statistica di validazione dei dati sono affidabili. Al contrario, la sat02 non veniva neanche mostrata, segno di un problema con l’algoritmo. Con una serie di tecniche informatiche, abbiamo capito che il problema risiedeva nell’equazione

  // After trend removal, the mean represents DC level

  xy_ratio= (f_y_ac*f_ir_mean)/(f_x_ac*f_red_mean);  //formula is (f_y_ac*f_x_dc) / (f_x_ac*f_y_dc) ;

  if(xy_ratio>0.02 && xy_ratio<1.84) { // Check boundaries of applicability

    *pn_spo2 = (-45.060*xy_ratio + 30.354)*xy_ratio + 94.845;

    *pch_spo2_valid = 1;

  } else {

    *pn_spo2 =  -999 ; // do not use SPO2 since signal an_ratio is out of range

    *pch_spo2_valid  = 0;

  }

sita nel file “algorithm_by_RF.cpp” all’interno della cartella della libreria “MAX30102_by_RF-master”.

La formula è corretta, calcolando il rapporto dei rapporti tra la componente di luce infrarossa durante la sistole sulla media della luce infrarossa, divisa per lo stesso rapporto riguardante la luce rossa. In pratica va a vedere, in percentuale, il delta tra luce infrarossa (sangue ossigenato) e luce rossa (sangue non ossigenato / tessuti) durante la sistole, eliminando il “rumore di fondo” dato dal riflesso del sangue venoso, o comunque “fermo”, e dai tessuti. Il valore risultante viene delimitato dal programma in un intervallo preciso per evitare calcoli su dati evidentemente errati perché fuori scala; viene poi inserito in una equazione parabolica fornita dal produttore del circuito. Questa serve come calibrazione, variando a seconda del modello del circuito e venendo stabilita con tecniche algebriche confermate empiricamente con una serie di test effettuati dal produttore su volontari sani.

Da un punto di vista matematico, il rapporto di cui sopra, che rappresenta il valore di luminosità rilevato, viene posizionato come ascissa sulla parabola, che ricalca la curva logaritmica di diminuzione della satO2 al variare della concentrazione di emoglobina ossigenata. Nel grafico, in ordinata naturalmente c’è la satO2.

Grafico di funzione generato tramite https://www.youmath.it/ym-tools-calcolatore-automatico/analisi-1/studio-e-grafico-di-funzioni.html

Dopo aver ottenuto i primi valori empirici, anche ad un primo sguardo risultava evidente che i conti non tornassero. Lasciando l’algoritmo così com’è i valori prodotti sono infatti sempre fuori scala. Questa anomalia si corregge invertendo il rapporto oppure cambiando di segno la parabola (e quindi ottenendo la simmetrica sull’origine, strategia che abbiamo adottato per facilitare i calcoli successivi). Data la carenza di tempo e lo scarso allenamento con gli studi di funzione (per chi scrive, l’ultimo è stato ai tempi del liceo) abbiamo preso una scorciatoia. Abbiamo riprodotto i test su volontari sani con un solo volontario, cioè il sottoscritto, che ha respirato in un sacchetto di plastica (NON ripetetelo a casa!) fino a raggiungere la saturazione di 90, misurata con un saturimetro commerciale.

Il sacchetto per ricreare l’atmosfera povera di ossigeno che provoca la desaturazione

Rilevato quindi il valore del rapporto I/R di cui sopra si è proceduto al ritorno ad aria ambiente, registrando successivamente tale parametro per tutti i valori di saturazione, fino alla normalità (98), indicati dal saturimetro commerciale. A questo punto, si sono riportati i valori rilevati vs. quelli calcolati su una tabella excel e si è cercato di ricreare l’equazione di calibrazione partendo da piccole variazioni di quella “ufficiale” in modo da trovare la migliore approssimazione tra le due colonne. L’algoritmo risultante è il seguente:

  // After trend removal, the mean represents DC level

  xy_ratio= (f_x_ac*f_red_mean)/(f_y_ac*f_ir_mean);  //formula is (f_y_ac*f_x_dc) / (f_x_ac*f_y_dc) ;

  if(xy_ratio>0.02 && xy_ratio<1.84) { // Check boundaries of applicability

    *pn_spo2 = (-70*xy_ratio + 32)*xy_ratio + 97.5;

    *pch_spo2_valid = 1;

  } else {

    *pn_spo2 =  -999 ; // do not use SPO2 since signal an_ratio is out of range

    *pch_spo2_valid  = 0;

  }

}

Abbiamo aggiunto anche questo file, già aggiornato con la nuova equazione, nella cartella di google drive. Pertanto ricordatevi di sostituire il file originale (che si trova nella cartella Arduino\libraries\MAX30102_by_RF-master ) con il nostro. Se doveste acquistare il sensore da un altro produttore sarà necessario verificare la calibrazione ed eventualmente ripetere la procedura indicata sopra.

Questa calibrazione, come si vede nel video (https://youtu.be/KGl2l_txLxU ), funziona abbastanza bene MA è certificata SOLO per misurare la saturazione… a me. Per generalizzare il risultato sarebbe necessario ripetere il test su un numero congruo (almeno 30) volontari sani con diverse caratteristiche fisiche (per esempio, pelle nera). In realtà, dalle prove effettuate sui colleghi medici e sui pazienti che si sono offerti volontari, il dato sembra affidabile. A saturazioni “normali” tende a sottostimare (quindi un 96 può essere anche un 98) ma desaturando collima perfettamente con il saturimetro commerciale. Naturalmente è sempre necessario attendere più misurazioni per eliminare le oscillazioni intrinseche allo strumento (problema comune anche ai saturimetri commerciali). Pertanto, possiamo affermare che lo strumento da noi costruito è almeno utile per cogliere una desaturazione, se non per quantificarla in modo assoluto. È  comunque intrinsecamente coerente con se stesso, dunque può essere usato come monitoraggio di un possibile miglioramento o peggioramento del quadro clinico; ha un valore assoluto di desaturazione (90) che si allinea in modo preciso con i saturimetri commerciali, almeno nelle prove effettuate, dunque può essere considerato quando vi è la necessità di valutare un ricovero. Se tra i lettori ci fossero esperti nel campo vi chiediamo di aiutarci per migliorare e/o certificare una equazione che possa rendere la misurazione degna del “medical grade”.

Sostituito quest’ultimo file, dunque, possiamo procedere a selezionare la porta a cui la scheda è connessa e a caricare il nostro sketch che si può scaricare sempre da qui https://drive.google.com/open?id=1zvxwnRE-UM87otZBnbQbLkbCB3mnYoJY.

Il codice dello sketch da caricare:

#include <WiFi.h>         

#include <WebServer.h>    

#include <AutoConnect.h>

#include <Wire.h>

#include <Adafruit_SSD1306.h>

#include “ESP32_MailClient.h”

#include “algorithm_by_RF.h”

#include “max30102.h”

#define SCREEN_WIDTH 128

#define SCREEN_HEIGHT 64

#define OLED_RESET     4

#define emailSenderAccount    “xxxxxxxxxx@gmail.com”   

#define emailSenderPassword   “xxxxxxx”

#define emailRecipient        “zzzzzzzzzzzz@zzzzzz.zzz”

#define smtpServer            “smtp.gmail.com”

#define smtpServerPort        465

#define emailSubject          “Sorveglianza domiciliare Sat02”

WebServer Server;

AutoConnect      Portal(Server);

SMTPData smtpData;

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

const byte oxiInt = 34;

uint32_t aun_ir_buffer[BUFFER_SIZE];

uint32_t aun_red_buffer[BUFFER_SIZE];

uint8_t uch_dummy = 0;

unsigned long currentMillis = 0;

unsigned long previousMillis = 0;

long irValueMappato = 0;

long irValueLow = 10000;

long irValueHigh = 0;

int q=0;

int qPrec=0;

String contenuto=””;

String arraySpo2[10]=””;

int z=0;

void sendCallback(SendStatus info);

void rootPage() {

  char content[] = “Hello, world”;

  Server.send(200, “text/plain”, content);

}

void setup() {

  Serial.begin(115200);

  Serial.println();

    display.begin(SSD1306_SWITCHCAPVCC, 0x3C);

    display.clearDisplay();                                

    display.setTextSize(2);                                  

    display.setTextColor(WHITE);

    display.setCursor(0,0);               

    display.println(“Avvio”);            

    display.setCursor(0,16);               

    display.println(“WiFi”); 

    display.display();

    delay(2000);

  Server.on(“/”, rootPage);

  if (Portal.begin()) {

    Serial.println(“WiFi connected: ” + WiFi.localIP().toString());

    display.clearDisplay();                                

    display.setTextSize(2);                                  

    display.setTextColor(WHITE);

    display.setCursor(0,0);                

    display.println(“WiFi”);            

    display.setCursor(0,16);               

    display.println(“Ok”);

    display.display();

    delay(2000);

  }

    display.begin(SSD1306_SWITCHCAPVCC, 0x3C);

    display.clearDisplay();                                

    display.setTextSize(2);                                  

    display.setTextColor(WHITE);

    display.setCursor(0,0);               

    display.println(“Medici”);            

    display.setCursor(0,16);               

    display.println(“Triuggio”);

    display.setCursor(0,32);               

    display.println(“Misuratore”);            

    display.setCursor(0,48);               

    display.println(“Sat O2”);

    display.display();

    delay(2000);

    display.clearDisplay();

    delay(2000);

  pinMode(oxiInt, INPUT);

  Wire.begin();

  maxim_max30102_reset();

  delay(1000);

  maxim_max30102_read_reg(REG_INTR_STATUS_1,&uch_dummy);

  maxim_max30102_init();

}

void loop() {

    Portal.handleClient();

  float n_spo2,ratio,correl;

  int8_t ch_spo2_valid;

  int32_t n_heart_rate;

  int8_t  ch_hr_valid;

  int32_t i;

  char hr_str[10];

  for(i=0;i<1;i++)

  {

    while(digitalRead(oxiInt)==1);

    maxim_max30102_read_fifo((aun_red_buffer+i), (aun_ir_buffer+i));

  }

if (aun_ir_buffer[0] < 7000)

{

    display.clearDisplay();                                

    display.setTextSize(2);                                  

    display.setTextColor(WHITE);

    display.setCursor(0,0);                

    display.println(“Inserire”);            

    display.setCursor(0,16);               

    display.println(“Dito”);

    display.setCursor(0,32);               

    display.println(“Stare”);            

    display.setCursor(0,48);               

    display.println(“Fermi”);

    display.display();

  }

else {

    display.clearDisplay();

    display.setTextSize(2);                                 

    display.setTextColor(WHITE);

    for(int y = 0; y < 128; y++){

        display.drawFastVLine( y, 16, 16, BLACK);

        }

    display.setCursor(10,0);               

    display.println(“BPM”);            

    display.setCursor(10,16); 

       if (n_heart_rate >= 30 && n_heart_rate <= 220){

         display.println(n_heart_rate, DEC);

       }

       else if (n_heart_rate == -999){

        display.println(“–“);

        }                 

    display.setCursor(60,0);               

    display.println(“SatO2”);            

    display.setCursor(60,16);

       if (n_spo2 >= 70 && n_spo2 <= 100){

        int intSpo2 = n_spo2;

         display.println(intSpo2, DEC);

         arraySpo2[z]=String(n_spo2);

         z++;

         Serial.println( “Saturazione valida: ” );

         Serial.println( n_spo2 );

       }

       else if (n_spo2 == -999) {

        display.println(“–“);

        }

    display.display();

  for(i=0;i<BUFFER_SIZE;i++)

  {

    while(digitalRead(oxiInt)==1);

    maxim_max30102_read_fifo((aun_red_buffer+i), (aun_ir_buffer+i));

      currentMillis = millis();

      if (currentMillis – previousMillis >= 4000) {

          irValueLow = (aun_red_buffer[i])-((aun_red_buffer[i])*0.001);

          irValueHigh = (aun_red_buffer[i])+((aun_red_buffer[i])*0.001);

          previousMillis = currentMillis;

      }

      if (irValueLow > (aun_red_buffer[i]) && irValueLow > ((aun_red_buffer[i])/2)){

          irValueLow = (aun_red_buffer[i]);

          }

      if ((irValueHigh <= (aun_red_buffer[i])) && (irValueHigh <= ((aun_red_buffer[i])*2))){

          irValueHigh = (aun_red_buffer[i]);

         }

    irValueMappato = map2((aun_red_buffer[i]), irValueLow, irValueHigh, 64, 32);

         if (i < BUFFER_SIZE){

             q = (i*128);

             q = q/100;

             if(q==qPrec+1){

                Serial.println(q);

                  display.drawLine(q, 64, q, irValueMappato, WHITE);

                  qPrec=q;

             }

             else {

              display.drawLine((q-1), 64, (q-1), irValueMappato, WHITE);

              display.drawLine(q, 64, q, irValueMappato, WHITE);

              qPrec=q;

              }

         }

        else if (i >= BUFFER_SIZE){

             for(int y = 0; y < 128; y++){

              display.drawFastVLine( y, 32, 32, BLACK);

               }

              i=0;

          }

    display.display();

  }

  rf_heart_rate_and_oxygen_saturation(aun_ir_buffer, BUFFER_SIZE, aun_red_buffer, &n_spo2, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid, &ratio, &correl);

}

    if (z==9){

        inviaEmail();

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

        arraySpo2[i]=””;

        }

        contenuto=””;

        z=0;

      }

}

void inviaEmail() {

  while (WiFi.status() != WL_CONNECTED) {

    display.clearDisplay();                                

    display.setTextSize(2);                                  

    display.setTextColor(WHITE);

    display.setCursor(0,0);               

    display.println(“No WiFI”);            

    display.setCursor(0,16);               

    display.println(“Effettuare”);

    display.setCursor(0,32);               

    display.println(“accesso”);                           

    display.display();

    delay(5000);

    Serial.print(“.”);

  }

  Serial.println();

  Serial.println(“WiFi connected.”);

  Serial.println();

  Serial.println(“Preparing to send email”);

  Serial.println();

  // Set the SMTP Server Email host, port, account and password

  smtpData.setLogin(smtpServer, smtpServerPort, emailSenderAccount, emailSenderPassword);

  // For library version 1.2.0 and later which STARTTLS protocol was supported,the STARTTLS will be

  // enabled automatically when port 587 was used, or enable it manually using setSTARTTLS function.

  //smtpData.setSTARTTLS(true);

  // Set the sender name and Email

  smtpData.setSender(“Saturimetro WiFI 1”, emailSenderAccount);

  // Set Email priority or importance High, Normal, Low or 1 to 5 (1 is highest)

  smtpData.setPriority(“Normal”);

  // Set the subject

  smtpData.setSubject(emailSubject);

    contenuto = “”;

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

      contenuto = contenuto+” “+arraySpo2[i];

      }

  // Set the message with HTML format

  smtpData.setMessage(“<div style=\”color:#2f4468;\”><h1>Ultime saturazioni</h1><p>- Inviato in automatico da Saturimetro WiFi 1</p><p>Saturazioni:</p>” + contenuto + “</div>”, true);

  // Set the email message in text format (raw)

  //smtpData.setMessage(“Hello World! – Sent from ESP32 board”, false);

  // Add recipients, you can add more than one recipient

  smtpData.addRecipient(emailRecipient);

  //smtpData.addRecipient(“YOUR_OTHER_RECIPIENT_EMAIL_ADDRESS@EXAMPLE.com”);

  smtpData.setSendCallback(sendCallback);

  //Start sending Email, can be set callback function to track the status

  if (!MailClient.sendMail(smtpData)) {

    Serial.println(“Error sending Email, ” + MailClient.smtpErrorReason());

    display.clearDisplay();                                

    display.setTextSize(2);                                  

    display.setTextColor(WHITE);

    display.setCursor(0,0);               

    display.println(“Errore”);            

    display.setCursor(0,16);               

    display.println(“nell’invio”);

    display.setCursor(0,32);               

    display.println(“dell’email”);            

    display.setCursor(0,48);               

    display.display();

    delay(5000);

  }

  //Clear all data from Email object to free memory

  smtpData.empty();

}

// Callback function to get the Email sending status

void sendCallback(SendStatus msg) {

  // Print the current status

  Serial.println(msg.info());

  // Do something when complete

  if (msg.success()) {

    Serial.println(“—————-“);

    display.clearDisplay();                                

    display.setTextSize(2);                                   

    display.setTextColor(WHITE);

    display.setCursor(0,0);               

    display.println(“Email”);            

    display.setCursor(0,16);               

    display.println(“inviata”);             

    display.display();

    delay(5000);

  }

}

long map2(long x, long in_min, long in_max, long out_min, long out_max)

{

  return (x – in_min) * (out_max – out_min + 1) / (in_max – in_min + 1) + out_min;

}

Conclusioni e possibili evoluzioni

Abbiamo mostrato come sia possibile creare un saturimetro in grado di fornire dati sostanzialmente affidabili partendo da circuiti semplici e materiali a disposizione di ogni Maker. Abbiamo raggiunto questo risultato grazie all’enorme lavoro di chi ci ha preceduto e ha reso disponibili a tutti i risultati dei propri studi. La disponibilità di questa tecnologia in un momento di emergenza come quello attuale può garantire l’assistenza a più pazienti rispetto a quanto sia attuabile oggi con gli strumenti a disposizione. La tecnologia dell’internet of things, cioè degli oggetti connessi ad internet, porta in questo caso il grande vantaggio di ridurre l’esposizione degli operatori sanitari al contagio. Auspichiamo dunque che la pubblicazione in formato open source del nostro lavoro possa venire ripresa e migliorata da professionisti nei singoli campi, in modo da completarla garantendone una sicurezza ed attendibilità “medical grade”.

Feedback ed evoluzioni

  • Mario Milanesio, docente presso l’IIS G.Vallauri di Fossano, Cuneo, ci fa notare che durante la compilazione su sistemi Linux compare un errore con la libreria MAX30102_by_RF. Per evitarlo, abbiamo preparato una cartella (MAX30102_by_RF-master algoritmo corretto) con i file da sostituire. Suggerisce inoltre la possibilità di caricare i dati su uno spazio cloud, possibilmente open, che permetta anche il download dei dati per l’analisi del trend. Per es. Thingspeak, Thingsboard e il più semplice Google Sheets.
  • Grazie all’amico Giacomo Mandelli abbiamo perfezionato la procedura di calibrazione, rendendola più semplice e possibile anche senza l’utilizzo del pericoloso sacchetto di plastica.

La procedura è descritta nel file “CalibrazioneSaturimetro.pdf” nella cartella “Calibrazione”.

In sostanza, bisogna:

  • sostituire i file della libreria MAX30102_by_RF con quelli presenti nella cartella “Calibrazione”;
    • Caricare lo sketch sul saturimetro;
    • Eseguire le operazioni richieste. Per la desaturazione, è possibile trattenere il fiato e riportare i valori del saturimetro commerciale sulla tastiera come da istruzioni. Il programma si occuperà poi di fare l’appaiamento con i valori del saturimetro home-made;
    • Salvare i dati in un file di testo;
    • Caricarli nel programma Fortran presente nella cartella “Programma calcolo curva calibrazione” (il programma gira su Linux, ma nel pdf è descritta una procedura per farlo funzionare anche sotto Windows);
    • Ricavare la curva (o a piacimento la retta) da riportare nel file “algorithm_by_RF.cpp” all’interno della cartella della libreria “MAX30102_by_RF-master”;
    • Ripristinare il file “algorithm_by_RF.h”;
    • Caricare lo sketch “Software.ino”.

Così avrete un saturimetro calibrato e funzionante!