Introdução
Na primeira parte desta prova de conceito de uma pulseira capaz de detectar crises convulsivas epilépticas, coletamos os dados e treinamos o modelo de machine learning. Nesta segunda etapa, vamos implementar esse modelo, utilizando-o para identificar crises e, em seguida, enviar uma mensagem de emergência via WhatsApp para um contato de emergência. Isso é interessante, pois crises epilépticas podem ser perigosas, frequentemente resultando em quedas ou outros acidentes que colocam a saúde do paciente em risco.
Material necessário
Utilize os mesmos componentes e circuito que foi mostrado na Parte 1.
Versão 1: Envio de Mensagem Serial ao Detectar Crise Epiléptica
- Na Etapa 1, realizamos o deployment do modelo e baixamos um pacote .zip. Para importar esse pacote na sua IDE do Arduino, siga os passos: vá até Sketch > Include Library > Add .ZIP Library e selecione o arquivo .zip correspondente ao modelo.
- Inclua o nome da sua biblioteca em seu código. Por exemplo:
#include <epilepsia_inferencing.h>- Inclua também as seguintes bibliotecas necessárias e defina os parâmetros:
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_LIS3DH.h>
#include <Adafruit_Sensor.h>
static bool debug_nn = false; // Set this to true to see e.g. features generated from the raw signal
#define LIS3DH_CLK 36
#define LIS3DH_MISO 37
#define LIS3DH_MOSI 35
#define LIS3DH_CS 34
// software SPI
Adafruit_LIS3DH lis = Adafruit_LIS3DH(LIS3DH_CS, LIS3DH_MOSI, LIS3DH_MISO, LIS3DH_CLK);
- No bloco void setup(), inicie a comunicação serial, verifique se o acelerômetro foi inicializado e configure suas definições iniciais, incluindo o alcance, modo e taxa de amostragem. Além disso, confirme se o número de dados de classificação é igual a 6.
void setup(void) {
Serial.begin(115200);
if (!lis.begin(0x18)) {
Serial.println("Couldn't start");
while (1) yield();
}
lis.setRange(LIS3DH_RANGE_2_G); // 2, 4, 8 or 16 G
lis.setPerformanceMode(LIS3DH_MODE_NORMAL); //LIS3DH_MODE_LOW_POWER, LIS3DH_MODE_NORMAL, LIS3DH_MODE_HIGH_RESOLUTION
lis.setDataRate(LIS3DH_DATARATE_50_HZ); //1Hz (1 leitura por segundo), 10Hz, 25Hz, 50Hz, 100Hz, 200Hz, 400Hz, POWERDOWN, LOWPOWER_5KHZ, LOWPOWER_1K6HZ
if (EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME != 6) {
ei_printf("ERR: EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME should be equal to 6 (the 6 sensor axes)\n");
return;
}
}
- No bloco void loop(), implemente a coleta das leituras do acelerômetro, armazenando os valores em um buffer. Para cada amostra, capture as acelerações nos eixos X, Y e Z, e calcule os ângulos de rotação (roll, pitch e yaw). As leituras, juntamente com os ângulos calculados, devem ser armazenadas no buffer para posterior análise. Em seguida, converta o buffer em um sinal que será utilizado pelo classificador. Se a conversão for bem-sucedida, execute o classificador e aguarde os resultados. Imprima no console os resultados da classificação e baseando-se nas previsões do classificador, verifique se a probabilidade de não haver epilepsia é superior a 0,8. Se essa condição for satisfeita, exiba “Sem Epilepsia” no console. Caso contrário, se a probabilidade de epilepsia for superior a 0,8, exiba “Epilepsia identificada”. Se nenhuma condição for satisfeita exiba “Nao eh possivel determinar”.
void loop() {
ei_printf("\nClassificacao inicia em 10 segundos...\n");
delay(10000);
ei_printf("Sampling...\n");
// buffer para leitura acelerometro
float buffer[EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE] = { 0 };
for (size_t ix = 0; ix < EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE; ix += 6) {
// proximo next tick
uint64_t next_tick = micros() + (EI_CLASSIFIER_INTERVAL_MS * 1000);
// captura accel X, Y e Z em m/s^2
sensors_event_t event;
lis.getEvent(&event);
// Calcular o ângulo em relação aos eixos X (roll), Y (pitch) e Z (yaw)
float roll = (atan2(event.acceleration.y, event.acceleration.z) * 180 / M_PI)*0.1;
float pitch = (atan2(-event.acceleration.x, sqrt(event.acceleration.y * event.acceleration.y + event.acceleration.z * event.acceleration.z)) * 180 / M_PI)*0.1;
// Aproximação do ângulo yaw (rotação em torno do eixo Z) usando atan2 com X e Y
float yaw = (atan2(event.acceleration.y, event.acceleration.x) * 180 / M_PI)*0.1;
// coloca leituras no buffer
buffer[ix + 0] = roll;
buffer[ix + 1] = pitch;
buffer[ix + 2] = yaw;
buffer[ix + 3] = event.acceleration.x;
buffer[ix + 4] = event.acceleration.y;
buffer[ix + 5] = event.acceleration.z;
delayMicroseconds(next_tick - micros());
}
// Turn the raw buffer in a signal which we can the classify
signal_t signal;
int err = numpy::signal_from_buffer(buffer, EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, &signal);
if (err != 0) {
ei_printf("Failed to create signal from buffer (%d)\n", err);
return;
}
// roda classificador
ei_impulse_result_t result = { 0 };
err = run_classifier(&signal, &result, debug_nn);
if (err != EI_IMPULSE_OK) {
ei_printf("ERR: Failed to run classifier (%d)\n", err);
return;
}
// predicoes
ei_printf("Predictions ");
ei_printf("(DSP: %d ms., Classification: %d ms., Anomaly: %d ms.)",
result.timing.dsp, result.timing.classification, result.timing.anomaly);
ei_printf(": \n");
for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
ei_printf(" %s: %.5f\n", result.classification[ix].label, result.classification[ix].value);
}
// Controle dos LEDs com base nas predições
if (result.classification[0].value > 0.8) { // epilepsia
Serial.println("Sem Epilepsia");
} else if (result.classification[1].value > 0.8) { // sem epilepsia"
Serial.println("Epilepsia identificada");
} else {
// Se nenhuma predição for suficientemente
Serial.println("Nao eh possivel determinar");
}
}
- O resultado será:
Versão 2: Integração com API para Envio de Mensagem pelo WhatsApp
Nosso objetivo vai além de indicar a ocorrência de uma crise epiléptica via comunicação serial. Queremos também enviar uma mensagem de alerta pelo WhatsApp para um contato de emergência. Para isso, utilizaremos a API da CallMeBot (https://www.callmebot.com).
- Acesse a API da CallMeBot (https://www.callmebot.com) para ler a documentação.
- Adicione o número +34 611 04 87 48 da API nos contatos de quem será o seu contato de emergência
- Envie a seguinte mensagem pelo whatsapp para o número da callmebot: I allow callmebot to send me messages
- Aguarde receber api key
- Instale biblioteca UrlEncode
- Inclua as seguintes bibliotecas:
#include <WiFi.h>
#include <HTTPClient.h>
#include <UrlEncode.h>- Crise as constantes:
const char* ssid = "SUA REDE WIFI";
const char* password = "SENHA DO SEU WIFI";
String phoneNumber = "+55SEUNUMEROEMERGIAAQUI";
String apiKey = "SUA APIKEY";- Crie a função que será responsável por enviar a mensagem no whatsapp seguindo a url indicada pela API:
void sendMessage(String message) {
// Data to send with HTTP POST
String url = "https://api.callmebot.com/whatsapp.php?phone=" + phoneNumber + "&apikey=" + apiKey + "&text=" + urlEncode(message);
HTTPClient http;
http.begin(url);
// Specify content-type header
http.addHeader("Content-Type", "application/x-www-form-urlencoded");
// Send HTTP POST request
int httpResponseCode = http.POST(url);
if (httpResponseCode == 200) {
Serial.print("Mensagem enviada com sucesso");
} else {
Serial.println("Erro no envio da mensagem");
Serial.print("HTTP response code: ");
Serial.println(httpResponseCode);
}
// Free resources
http.end();
}
- Modifique o seu bloco void setup() para que além das configuraçoes feitasd na versão 1 também conete na rede wifi:
void setup(void) {
Serial.begin(115200);
// configuracao acelerometro
if (!lis.begin(0x18)) {
Serial.println("Couldn't start");
while (1) yield();
}
lis.setRange(LIS3DH_RANGE_2_G); // 2, 4, 8 or 16 G
lis.setPerformanceMode(LIS3DH_MODE_NORMAL); //LIS3DH_MODE_LOW_POWER, LIS3DH_MODE_NORMAL, LIS3DH_MODE_HIGH_RESOLUTION
lis.setDataRate(LIS3DH_DATARATE_50_HZ); //1Hz (1 leitura por segundo), 10Hz, 25Hz, 50Hz, 100Hz, 200Hz, 400Hz, POWERDOWN, LOWPOWER_5KHZ, LOWPOWER_1K6HZ
// verificacao numero samples
if (EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME != 6) {
ei_printf("ERR: EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME should be equal to 6 (the 6 sensor axes)\n");
return;
}
// configuracao conexao wifi
WiFi.begin(ssid, password);
Serial.println("Conectando");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Conectado ao WiFi neste IP ");
Serial.println(WiFi.localIP());
}
Nessa versão, o setup é conectado na rede wifi.
- Modifique o seu bloco void setup() para que, além das configurações realizadas na versão 1, também inclua a conexão à rede Wi-Fi:
void loop() {
ei_printf("\nClassificacao inicia em 10 segundos...\n");
delay(60000);
ei_printf("Sampling...\n");
// buffer para leitura acelerometro
float buffer[EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE] = { 0 };
for (size_t ix = 0; ix < EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE; ix += 6) {
// proximo next tick
uint64_t next_tick = micros() + (EI_CLASSIFIER_INTERVAL_MS * 1000);
// captura accel X, Y e Z em m/s^2
sensors_event_t event;
lis.getEvent(&event);
// Calcular o ângulo em relação aos eixos X (roll), Y (pitch) e Z (yaw)
float roll = (atan2(event.acceleration.y, event.acceleration.z) * 180 / M_PI)*0.1;
float pitch = (atan2(-event.acceleration.x, sqrt(event.acceleration.y * event.acceleration.y + event.acceleration.z * event.acceleration.z)) * 180 / M_PI)*0.1;
// Aproximação do ângulo yaw (rotação em torno do eixo Z) usando atan2 com X e Y
float yaw = (atan2(event.acceleration.y, event.acceleration.x) * 180 / M_PI)*0.1;
// coloca leituras no buffer
buffer[ix + 0] = roll;
buffer[ix + 1] = pitch;
buffer[ix + 2] = yaw;
buffer[ix + 3] = event.acceleration.x;
buffer[ix + 4] = event.acceleration.y;
buffer[ix + 5] = event.acceleration.z;
delayMicroseconds(next_tick - micros());
}
// Turn the raw buffer in a signal which we can the classify
signal_t signal;
int err = numpy::signal_from_buffer(buffer, EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, &signal);
if (err != 0) {
ei_printf("Failed to create signal from buffer (%d)\n", err);
return;
}
// roda classificador
ei_impulse_result_t result = { 0 };
err = run_classifier(&signal, &result, debug_nn);
if (err != EI_IMPULSE_OK) {
ei_printf("ERR: Failed to run classifier (%d)\n", err);
return;
}
// predicoes
ei_printf("Predictions ");
ei_printf("(DSP: %d ms., Classification: %d ms., Anomaly: %d ms.)",
result.timing.dsp, result.timing.classification, result.timing.anomaly);
ei_printf(": \n");
for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
ei_printf(" %s: %.5f\n", result.classification[ix].label, result.classification[ix].value);
}
// Controle dos LEDs com base nas predições
if (result.classification[0].value > 0.8) { // sem epilepsia
Serial.println("Sem Epilepsia");
} else if (result.classification[1].value > 0.8) { // com epilepsia"
Serial.println("Epilepsia identificada");
sendMessage("Olá, você está no contato de emergência de Graziele.Ela está passando por uma crise epiléptica e precisa de ajuda urgente. Por favor, procure-a imediatamente!");
} else {
// Se nenhuma predição for suficientemente
Serial.println("Nao eh possivel determinar");
}
}
Nesta versão, ao detectar uma crise epiléptica, a função é acionada e a mensagem destinada ao contato de emergência é encaminhada. O código para enviar a mensagem é o seguinte: sendMessage(“Olá, você está no contato de emergência de Graziele. Ela está passando por uma crise epiléptica e precisa de ajuda urgente. Por favor, procure-a imediatamente!”);
- Funcionamento
Ao simular uma crise epiléptica com o braço (vídeo simulação), o evento é detectado e uma mensagem é enviada ao meu contato de emergência.
- Código completo:
void loop() {
ei_printf("\nClassificacao inicia em 10 segundos...\n");
delay(60000);
ei_printf("Sampling...\n");
// buffer para leitura acelerometro
float buffer[EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE] = { 0 };
for (size_t ix = 0; ix < EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE; ix += 6) {
// proximo next tick
uint64_t next_tick = micros() + (EI_CLASSIFIER_INTERVAL_MS * 1000);
// captura accel X, Y e Z em m/s^2
sensors_event_t event;
lis.getEvent(&event);
// Calcular o ângulo em relação aos eixos X (roll), Y (pitch) e Z (yaw)
float roll = (atan2(event.acceleration.y, event.acceleration.z) * 180 / M_PI)*0.1;
float pitch = (atan2(-event.acceleration.x, sqrt(event.acceleration.y * event.acceleration.y + event.acceleration.z * event.acceleration.z)) * 180 / M_PI)*0.1;
// Aproximação do ângulo yaw (rotação em torno do eixo Z) usando atan2 com X e Y
float yaw = (atan2(event.acceleration.y, event.acceleration.x) * 180 / M_PI)*0.1;
// coloca leituras no buffer
buffer[ix + 0] = roll;
buffer[ix + 1] = pitch;
buffer[ix + 2] = yaw;
buffer[ix + 3] = event.acceleration.x;
buffer[ix + 4] = event.acceleration.y;
buffer[ix + 5] = event.acceleration.z;
delayMicroseconds(next_tick - micros());
}
// Turn the raw buffer in a signal which we can the classify
signal_t signal;
int err = numpy::signal_from_buffer(buffer, EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, &signal);
if (err != 0) {
ei_printf("Failed to create signal from buffer (%d)\n", err);
return;
}
// roda classificador
ei_impulse_result_t result = { 0 };
err = run_classifier(&signal, &result, debug_nn);
if (err != EI_IMPULSE_OK) {
ei_printf("ERR: Failed to run classifier (%d)\n", err);
return;
}
// predicoes
ei_printf("Predictions ");
ei_printf("(DSP: %d ms., Classification: %d ms., Anomaly: %d ms.)",
result.timing.dsp, result.timing.classification, result.timing.anomaly);
ei_printf(": \n");
for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
ei_printf(" %s: %.5f\n", result.classification[ix].label, result.classification[ix].value);
}
// Controle dos LEDs com base nas predições
if (result.classification[0].value > 0.8) { // sem epilepsia
Serial.println("Sem Epilepsia");
} else if (result.classification[1].value > 0.8) { // com epilepsia"
Serial.println("Epilepsia identificada");
sendMessage("Olá, você está no contato de emergência de Graziele.Ela está passando por uma crise epiléptica e precisa de ajuda urgente. Por favor, procure-a imediatamente!");
} else {
// Se nenhuma predição for suficientemente
Serial.println("Nao eh possivel determinar");
}
}
https://github.com/Graziele-Rodrigues/TinyML_FranzininhoWifi/tree/main/epilepsia
Conclusão
Este projeto serviu como uma prova de conceito, demonstrando a viabilidade de criar uma pulseira capaz de identificar crises epilépticas. Os dados coletados foram simulados, mas, em uma aplicação real, seriam obtidos em diversas situações com pessoas que vivenciam essas crises. Quanto mais variados forem os dados, melhor será o modelo na identificação de uma crise epiléptica.
Além disso, este projeto abre oportunidades para diversas melhorias. No exemplo apresentado, foi enviada uma mensagem simples pelo WhatsApp a um contato fixo no código. Contudo, há potencial para desenvolver um aplicativo que permita a mudança desse contato e que salve o histórico de crise. Seria interessante, também, enviar a localização junto com a mensagem, para que o contato de emergência saiba com mais precisão onde está a pessoa. Outra aplicação interessante seria não apenas identificar quando a crise está ocorrendo, mas também prever sua ocorrência antes que aconteça.





