Passa ai contenuti principali

Vista su mappa livelli ossigeno ESP8266+MAX30102

Appunti sparsi per non dimenticare. (da completare)

EDIT: qui link a progetto completo  https://medicitriuggio.altervista.org/come-costruire-in-casa-un-saturimetro/

Medici e infermieri scarseggiano durante le epidemie e riducono la frequenza di visita perché i malati sono troppi e loro stessi si ammalano. Inoltre molti restano senza dispositivi di protezione individuale, così  si ammalano e sono sempre di meno.

Idealmente se avessero una mappa in cui i loro pazienti fossero evidenziati con i valori di saturazione ossigeno, potrebbero fare il giro-visite in modo mirato.

risolutore algo commesso viaggiatore : https://www.graphhopper.com

wiki esp8266
esp8266 datasheet


principio di funzionamento



datasheet max30102

low cost shields:


SpO2 can be calculated

R = (AC RMS of Red / DC of Red)/ (AC RMS of IR / DC of IR)

SpO2 is a percentage value. Using the R value you can calculate the %SpO2

% SpO2= 110 – 25 × R

AC RMS is the RMS value of voltage difference between peak and trough of the RED/IR waveform.
DC is the voltage difference between ground and the trough waveform.

calcolo di R in base alla lettura dei valori luce rossa e infrarossa

relazione fra misura R e spO2 (calibrazione)


criterio di allarme criticità
esempio mappa 


tabella S.Prahl  Tabulated Molar Extinction Coefficient for Hemoglobin in Water

collegamenti e codice per acquisizione e trasmissione dati: achtung SCLe SDA escono dai pin 2 e 4, non da 1 e 2.

guida esp8266

libreria sw x sensori sparkfun (esempio)


calcolo di AC e DC nei 2 casi, luce rossa e luce infrarossa






algoritmo migliorato

https://www.researchgate.net/publication/323005153_Effect_of_Different_Signal_Processing_Techniques_on_a_Calibration_Free_Pulse_Oximeter
dove si evince che il migliore è: Exponential Weighted Moving Averaging ,

Notare comunque libreria di microKalman qui (potrà venire utile) : Micro Kalman Filter for linear and nonlinear systems 
Per un ripasso (in python però): Kalman Filter book using Jupyter Notebook

(osservazione: trovato sito http://t-filter.engineerjs.com che potrebbe permettere di progettare FIR più complessi:

)


https://ieeexplore.ieee.org/document/5599617 (Simple signal processing method for pulse oximetry)

independent component analysis

Comparative evaluation of adaptive filters in motion artifact cancellation for pulse oximetry


app note texas ins slaa655 (ug6409 di maxim)

setup via Arduino IDE.


script Gsheet (TODO: attivatori al minuto/5minuti, calcolo R, calcolo SpO2), preso da
 https://circuitdigest.com/microcontroller-projects/log-temperature-sensor-data-to-google-sheet-using-nodemcu-esp8266
e anche
http://embedded-lab.com/blog/post-data-google-sheets-using-esp8266/
NOTA BENE : la libreria aggiornata  HTTPSRedirect sta su:  GitHub Sujay Phadke 

Proviamo innanzitutto a capire cosa succede realmente quando proviamo a inviare dati a Google. Ovviamente invieremo i dati utilizzando una richiesta GET su un URL che ha un modello di tipo https://script.google.com/… .. Quando inserisci questo URL in un browser Web, il server di Google risponde chiedendo al browser di reindirizzare a un altro URL con domain script.googleusercontent.com con una nuova richiesta GET. Per un browser Web, il reindirizzamento dell'URL è una cosa molto comune e funziona senza problemi. Tuttavia, per ESP8266, questo non è semplice. ESP8266 deve decodificare correttamente le informazioni di intestazione ricevute dal primo server per estrarre l'URL di reindirizzamento ed effettuare una seconda richiesta GET al nuovo server. Per semplificare questo reindirizzamento, Sujay Phadke, alias Electronicsguy, ha condiviso su GitHub un bellissimo pezzo di codice sotto forma di Libreria Arduino chiamato HTTPSRedirect.  L'uso della libreria HTTPSRedirect semplifica notevolmente l'attività evitando la necessità di servizi di terze parti. Quindi, la prima cosa che devi fare è copiare i file della libreria HTTPSRedirect da GitHub e installarli nella cartella delle librerie di Arduino usate per ESP8266.


mappa umap coi valori di ossigeno pazienti a casa

problema residenti stessa abitazione (sovrapposizione marker): da risolvere

icone diverse per criticità : vedere post alesarrett per spesa a domicilio

http://u.osmfr.org/m/436413/  info al tocco (Nome, età)


Visualizza a schermo intero


Persone che hanno partecipato con progetti attinenti a questo alla   Arduino Combating COVID-19 Conference  (5.30 - 7.00 PM CEST - Session 1: Arduino based ventilators and medical devices room2; 7 8:30 PM Session 2 Technology and manufacturing challenges )

Nastaran Hashemi (Iran, con Arduino nano e MAX30100)
Mario Milanesi (Italia, ESP32866 e sparkfun 30105)
Luca Toldo (Germania,  crittografato per sicurezza/privacy, monitoraggio letti terapia intensiva)
Dario Pennisi , Resp. Progettazione HW in Arduino, che nella sua presentazione ha riportato la raccomandazione di usare questi: 



inoltre ha riportato il link ad un foglio excel che raccoglie progetti con Arduino su youtube (fra cui ossimetri):

(video youtube della maxim sul 30102, prova con arduino) qui il link al sito delle risorse citato nel video: https://www.maximintegrated.com/en/design/reference-design-center/system-board/6300.html/tb_tab0 

altro codice arduino https://github.com/aromring/MAX30102_by_RF



Codice da customizzare per MAX30102
/*
* ESP822 temprature logging to Google Sheet
* CircuitDigest(www.circuitdigest.com)
*/

#include <ESP8266WiFi.h>
#include "HTTPSRedirect.h"
#include "DebugMacros.h"
#include <DHT.h>

#define DHTPIN D4 // what digital pin we're connected to
#define DHTTYPE DHT11 // select dht type as DHT 11 or DHT22
DHT dht(DHTPIN, DHTTYPE);

float h;
float t;
String sheetHumid = "";
String sheetTemp = "";

const char* ssid = "CircuitDigest"; //replace with our wifi ssid
const char* password = "circuitdigestfun"; //replace with your wifi password

const char* host = "script.google.com";
const char *GScriptId = "AKfycbxy9wAZKoPIpPq5AvqYTFFn5kkqK_-avacf2NU_w7ycoEtlkuNt"; // Replace with your own google script id
const int httpsPort = 443; //the https port is same

// echo | openssl s_client -connect script.google.com:443 |& openssl x509 -fingerprint -noout
const char* fingerprint = "";

//const uint8_t fingerprint[20] = {};
String url = String("/macros/s/") + GScriptId + "/exec?value=Temperature"; // Write Teperature to Google Spreadsheet at cell A1
// Fetch Google Calendar events for 1 week ahead
String url2 = String("/macros/s/") + GScriptId + "/exec?cal"; // Write to Cell A continuosly

//replace with sheet name not with spreadsheet file name taken from google
String payload_base = "{\"command\": \"appendRow\", \
\"sheet_name\": \"TempSheet\", \
\"values\": ";
String payload = "";

HTTPSRedirect* client = nullptr;
// used to store the values of free stack and heap before the HTTPSRedirect object is instantiated
// so that they can be written to Google sheets upon instantiation

void setup() {
delay(1000);
Serial.begin(115200);
dht.begin(); //initialise DHT11

Serial.println();
Serial.print("Connecting to wifi: ");
Serial.println(ssid);

WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());

// Use HTTPSRedirect class to create a new TLS connection
client = new HTTPSRedirect(httpsPort);
client->setInsecure();
client->setPrintResponseBody(true);
client->setContentTypeHeader("application/json");
Serial.print("Connecting to ");
Serial.println(host); //try to connect with "script.google.com"

// Try to connect for a maximum of 5 times then exit
bool flag = false;
for (int i = 0; i < 5; i++) {
int retval = client->connect(host, httpsPort);
if (retval == 1) {
flag = true;
break;
}
else
Serial.println("Connection failed. Retrying...");
}

if (!flag) {
Serial.print("Could not connect to server: ");
Serial.println(host);
Serial.println("Exiting...");
return;
}
// Finish setup() function in 1s since it will fire watchdog timer and will reset the chip.
//So avoid too many requests in setup()

Serial.println("\nWrite into cell 'A1'");
Serial.println("------>");
// fetch spreadsheet data
client->GET(url, host);

Serial.println("\nGET: Fetch Google Calendar Data:");
Serial.println("------>");
// fetch spreadsheet data
client->GET(url2, host);

Serial.println("\nStart Sending Sensor Data to Google Spreadsheet");

// delete HTTPSRedirect object
delete client;
client = nullptr;
}

void loop() {
h = dht.readHumidity(); // Reading temperature or humidity takes about 250 milliseconds!
t = dht.readTemperature(); // Read temperature as Celsius (the default)
if (isnan(h) || isnan(t)) { // Check if any reads failed and exit early (to try again).
Serial.println(F("Failed to read from DHT sensor!"));
return;
}
Serial.print("Humidity: "); Serial.print(h);
sheetHumid = String(h) + String("%"); //convert integer humidity to string humidity
Serial.print("% Temperature: "); Serial.print(t); Serial.println("°C ");
sheetTemp = String(t) + String("°C");

static int error_count = 0;
static int connect_count = 0;
const unsigned int MAX_CONNECT = 20;
static bool flag = false;

payload = payload_base + "\"" + sheetTemp + "," + sheetHumid + "\"}";
if (!flag) {
client = new HTTPSRedirect(httpsPort);
client->setInsecure();
flag = true;
client->setPrintResponseBody(true);
client->setContentTypeHeader("application/json");
}

if (client != nullptr) {
if (!client->connected()) {
client->connect(host, httpsPort);
client->POST(url2, host, payload, false);
Serial.print("Sent : "); Serial.println("Temp and Humid");
}
}
else {
DPRINTLN("Error creating client object!");
error_count = 5;
}

if (connect_count > MAX_CONNECT) {
connect_count = 0;
flag = false;
delete client;
return;
}

// Serial.println("GET Data from cell 'A1':");
// if (client->GET(url3, host)) {
// ++connect_count;
// }
// else {
// ++error_count;
// DPRINT("Error-count while connecting: ");
// DPRINTLN(error_count);
// }

Serial.println("POST or SEND Sensor data to Google Spreadsheet:");
if (client->POST(url2, host, payload)) {
;
}
else {
++error_count;
DPRINT("Error-count while connecting: ");
DPRINTLN(error_count);
}

if (error_count > 3) {
Serial.println("Halting processor...");
delete client;
client = nullptr;
Serial.printf("Final free heap: %u\n", ESP.getFreeHeap());
Serial.printf("Final stack: %u\n", ESP.getFreeContStack());
Serial.flush();
ESP.deepSleep(0);
}

delay(3000); // keep delay of minimum 2 seconds as dht allow reading after 2 seconds interval and also for google sheet
}


opzione semplificata uso IFTTT (servizio WebHook)


/* * Rui Santos * Complete Project Details http://randomnerdtutorials.com */ #ifdef ESP32 #include #else #include #endif #include #include #include // Replace with your SSID and Password const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; // Replace with your unique IFTTT URL resource const char* resource = "REPLACE_WITH_YOUR_IFTTT_URL_RESOURCE"; // How your resource variable should look like, but with your own API KEY (that API KEY below is just an example): //const char* resource = "/trigger/bme280_readings/with/key/nAZjOphL3d-ZO4N3k64-1A7gTlNSrxMJdmqy3"; // Maker Webhooks IFTTT const char* server = "maker.ifttt.com"; // Time to sleep uint64_t uS_TO_S_FACTOR = 1000000; // Conversion factor for micro seconds to seconds // sleep for 30 minutes = 1800 seconds uint64_t TIME_TO_SLEEP = 1800; // Uncomment to use BME280 SPI /*#include #define BME_SCK 13 #define BME_MISO 12 #define BME_MOSI 11 #define BME_CS 10*/ #define SEALEVELPRESSURE_HPA (1013.25) Adafruit_BME280 bme; // I2C //Adafruit_BME280 bme(BME_CS); // hardware SPI //Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // software SPI void setup() { Serial.begin(115200); delay(2000); // initialize BME280 sensor bool status; status = bme.begin(0x76); if (!status) { Serial.println("Could not find a valid BME280 sensor, check wiring!"); while (1); } initWifi(); makeIFTTTRequest(); #ifdef ESP32 // enable timer deep sleep esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR); Serial.println("Going to sleep now"); // start deep sleep for 3600 seconds (60 minutes) esp_deep_sleep_start(); #else // Deep sleep mode for 3600 seconds (60 minutes) Serial.println("Going to sleep now"); ESP.deepSleep(TIME_TO_SLEEP * uS_TO_S_FACTOR); #endif } void loop() { // sleeping so wont get here } // Establish a Wi-Fi connection with your router void initWifi() { Serial.print("Connecting to: "); Serial.print(ssid); WiFi.begin(ssid, password); int timeout = 10 * 4; // 10 seconds while(WiFi.status() != WL_CONNECTED && (timeout-- > 0)) { delay(250); Serial.print("."); } Serial.println(""); if(WiFi.status() != WL_CONNECTED) { Serial.println("Failed to connect, going back to sleep"); } Serial.print("WiFi connected in: "); Serial.print(millis()); Serial.print(", IP address: "); Serial.println(WiFi.localIP()); } // Make an HTTP request to the IFTTT web service void makeIFTTTRequest() { Serial.print("Connecting to "); Serial.print(server); WiFiClient client; int retries = 5; while(!!!client.connect(server, 80) && (retries-- > 0)) { Serial.print("."); } Serial.println(); if(!!!client.connected()) { Serial.println("Failed to connect..."); } Serial.print("Request resource: "); Serial.println(resource); // Temperature in Celsius String jsonObject = String("{\"value1\":\"") + bme.readTemperature() + "\",\"value2\":\"" + (bme.readPressure()/100.0F) + "\",\"value3\":\"" + bme.readHumidity() + "\"}"; // Comment the previous line and uncomment the next line to publish temperature readings in Fahrenheit /*String jsonObject = String("{\"value1\":\"") + (1.8 * bme.readTemperature() + 32) + "\",\"value2\":\"" + (bme.readPressure()/100.0F) + "\",\"value3\":\"" + bme.readHumidity() + "\"}";*/ client.println(String("POST ") + resource + " HTTP/1.1"); client.println(String("Host: ") + server); client.println("Connection: close\r\nContent-Type: application/json"); client.print("Content-Length: "); client.println(jsonObject.length()); client.println(); client.println(jsonObject); int timeout = 5 * 10; // 5 seconds while(!!!client.available() && (timeout-- > 0)){ delay(100); } if(!!!client.available()) { Serial.println("No response..."); } while(client.available()){ Serial.write(client.read()); } Serial.println("\nclosing connection"); client.stop(); }


Commenti

Post popolari in questo blog

UMAP: mappare con fotografie nel popup

Premessa: come esempio non userò alberi perché non ho il tempo di uscire , ma foto di Crema del passato prese da Gruppi facebook. I dati saranno messi in un Google Sheet: ogni riga un segnaposto sulla mappa ogni colonna una proprietà del segnaposto della mappa per il tutorial di un prof per alunni delle medie, vedere qui:   https://sites.google.com/view/lucascalzullo/tutorial/umap  Cosa serve modificare nel LAYER della mappa? 1. La cosa che si vuol far apparire quando il mouse passa sopra il segnaposto (in questo caso l'autore ) 2. l'elenco delle colonne da importare nel popup , okkio al numero di parentesi graffe: 3 per le foto le possibilità sono: *asterisco per l'italico* **due asterischi per il testo marcato** # un cancelletto per l'intestazione principale ## due cancelletti per le intestazioni di secondo livello ### tre cancelletti per intestazione di terzo livello Link semplice: [[http://example.com]] Link con testo: [[http://example.com|te

Esportare tracciati (e reimportarli) in umap

------------------  Fase1. Preparare una mappa umap "d'appoggio" in cui lo sfondo è una mappa (*) sulla quale tirare delle linee o poligoni come tracciati che poi verrano esportati in un formato re-importabile in un'altra umap. esempio:  https://umap.openstreetmap.fr/it/map/moruzzi_408788#16/45.3632/9.6832 (*) metodo illustrato qui Fase2. una volta tracciata la linea ( anche più di una linea in realtà), andare sull'unico layer (dove risiede quindi la linea) e scegliere Azioni Avanzate -> Scarica si aprirà un'altra pagina web con il codice geojson : per controllarlo, importarlo (anche con un 'seleziona tutto" e  copia incolla) in geojson.io : (fra l'altro geojson è collegabile a github ) FASE3. salvarlo come 'geojson' nel PC  e importarlo nella umap desiderata cliccando sulla icona "freccia verso l'alto", formato geojson, scegli file risultato

mapwarper e sovrapposizione mappe

come sovrapporre mappe alla mappa di openstreet map? Vediamo come sovrapporre una delle meravigliose mappe di T. Moruzzi alla mappa di Crema su openstreetmap. mura venete , di T Moruzzi Si usa  http://mapwarper.net  . Cliccare sulla linguetta  Upload Map e caricare l'immagine della mappa di Moruzzi. Dopo di che occorre "rettificare" in modo che si adatti (venga "stirata") sulla mappa di osm. Cliccare sulla linguetta Rectify . Appaiono le due mappe una accanto all'altra : per far capire a mapwarper come stirarla occorre piazzare dei punti di riferimento sull'una e sull'altra , usando l'iconcina segnalibro azzurra in alto e poi cliccando sul bottone in basso per aggiungere la coppia di punti e dopo averne piazzati almeno 3 (coppie), cliccare il comando warp image! corrispondenze esecuzione comando Cliccando la linguetta Preview , si possono vedere le mappe una sull'altra, dosando la trasparenza col cursore: