commit 203a4a006df48aea6fd67105b88cb0f3acb53095 Author: Amaro Lopes Date: Wed Feb 11 10:27:08 2026 -0300 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/chips/heartrate.chip.json b/chips/heartrate.chip.json new file mode 100644 index 0000000..a460a1b --- /dev/null +++ b/chips/heartrate.chip.json @@ -0,0 +1,19 @@ +{ + "name": "heartrate", + "author": "Amaro Lopes", + "pins": [ + "VCC", + "GND", + "BTN", + "OUT" + ], + "controls": [ + { + "id": "pulseValue", + "label": "Heart Rate", + "type": "slider", + "min": 40, + "max": 200 + } + ] +} \ No newline at end of file diff --git a/chips/heartrate.chip.wasm b/chips/heartrate.chip.wasm new file mode 100755 index 0000000..f54c25b Binary files /dev/null and b/chips/heartrate.chip.wasm differ diff --git a/diagram.json b/diagram.json new file mode 100644 index 0000000..d9665ff --- /dev/null +++ b/diagram.json @@ -0,0 +1,188 @@ +{ + "version": 1, + "author": "Amaro Lopes", + "editor": "wokwi", + "parts": [ + { + "type": "board-esp32-devkit-c-v4", + "id": "esp", + "top": 0, + "left": 0, + "attrs": {} + }, + { + "type": "chip-heartrate", + "id": "chip1", + "top": -114.18, + "left": -148.8, + "attrs": {} + }, + { + "type": "wokwi-resistor", + "id": "r1", + "top": -33.6, + "left": -67.75, + "rotate": 90, + "attrs": { + "value": "1000" + } + }, + { + "type": "wokwi-slide-switch", + "id": "sw1", + "top": -130, + "left": 127.9, + "attrs": {} + }, + { + "type": "wokwi-mpu6050", + "id": "imu1", + "top": 99.82, + "left": -199.28, + "attrs": {} + }, + { + "type": "wokwi-led", + "id": "led1", + "top": -32.4, + "left": 167, + "attrs": { + "color": "red" + } + } + ], + "connections": [ + [ + "esp:TX", + "$serialMonitor:RX", + "", + [] + ], + [ + "esp:RX", + "$serialMonitor:TX", + "", + [] + ], + [ + "esp:3V3", + "chip1:VCC", + "green", + [ + "h-187.01", + "v-134.4" + ] + ], + [ + "chip1:GND", + "esp:GND.2", + "black", + [ + "v28.8", + "h244.76" + ] + ], + [ + "r1:1", + "chip1:BTN", + "green", + [ + "h0" + ] + ], + [ + "r1:2", + "esp:3V3", + "green", + [ + "h0" + ] + ], + [ + "esp:35", + "chip1:OUT", + "green", + [ + "h-23.81", + "v-182.4" + ] + ], + [ + "sw1:1", + "chip1:BTN", + "green", + [ + "v0" + ] + ], + [ + "sw1:2", + "sw1:3", + "black", + [ + "v0" + ] + ], + [ + "sw1:3", + "esp:GND.2", + "black", + [ + "v0" + ] + ], + [ + "imu1:VCC", + "esp:3V3", + "red", + [ + "v0" + ] + ], + [ + "imu1:GND", + "esp:GND.2", + "black", + [ + "v-134.4", + "h28.88" + ] + ], + [ + "imu1:SCL", + "esp:22", + "green", + [ + "v9.6", + "h268.88", + "v-67.2" + ] + ], + [ + "imu1:SDA", + "esp:21", + "green", + [ + "v0" + ] + ], + [ + "esp:GND.2", + "led1:C", + "black", + [ + "v-19.2", + "h-4.76" + ] + ], + [ + "esp:17", + "led1:A", + "green", + [ + "h0" + ] + ] + ], + "dependencies": {} +} \ No newline at end of file diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..261c781 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,18 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:esp32] +platform = espressif32 +framework = arduino +board = esp32dev +lib_deps = + hwspeedy/DHT-Sensor + knolleary/PubSubClient + adafruit/Adafruit MPU6050@^2.2.8 diff --git a/src/main.ino b/src/main.ino new file mode 100644 index 0000000..a8ac417 --- /dev/null +++ b/src/main.ino @@ -0,0 +1,402 @@ +// ============================================================================ +// Sistema de monitoramento de cozinha com ESP32 +// Monitora temperatura, umidade e gás com detecção avançada de incêndio +// Envia dados via MQTT e controla coifa e ar-condicionado +// ============================================================================ + +// INCLUDES +#include +#include +#include // MQTT +#include // WiFi +#include + +// ============================================================================ +// DEFINES - Pinos GPIO +// ============================================================================ +#define PULSE_PIN 35 // Pin for the pulse sensor +#define LED_PIN 17 // Pin for the LED + +// ============================================================================ +// DEFINES - Configurações estáticas +// ============================================================================ +#define SAMPLE_PERIOD_MS 10 // 100 Hz +#define FILTER_ALPHA 0.9 + +#define TH_START 1.15 +#define TH_END 1.05 + +#define MIN_PEAK_AMPLITUDE 1.5 + +#define MIN_REP_TIME 800 +#define MAX_REP_TIME 5000 +// ============================================================================ +// ENUMS +// ============================================================================ +enum EstadoRepeticao { + OCIOSO, + DESCANSO, + SUBINDO, + DESCENDO +}; + +// ============================================================================ +// CONSTANTES - WiFi e MQTT +// ============================================================================ +const char* WIFI_SSID = "Wokwi-GUEST"; +const char* WIFI_PASSWORD = ""; +const char* MQTT_BROKER = "77.37.69.84"; +const int MQTT_PORT = 1883; +const char* MQTT_SUB_TOPICS[] = { + "academia/reps", + "academia/sets", + "academia/t_descanso" +}; +const char* MQTT_PUB_TOPIC = "academia"; +const int MQTT_SUB_COUNT = sizeof(MQTT_SUB_TOPICS) / sizeof(MQTT_SUB_TOPICS[0]); + +// ============================================================================ +// VARIÁVEIS GLOBAIS - Objetos +// ============================================================================ +WiFiClient espClient; +PubSubClient mqtt(espClient); +Adafruit_MPU6050 mpu; +sensors_event_t event; + +// ============================================================================ +// VARIÁVEIS GLOBAIS - Estado do sistema +// ============================================================================ + +EstadoRepeticao estado = OCIOSO; + +unsigned long ultimaAmostra = 0; + +unsigned long t_inicio = 0; +unsigned long t_pico = 0; +unsigned long t_fim = 0; +unsigned long t_descanso = 30; + +float aceleracaoFiltrada = 1.0; +float aceleracaoAnterior = 1.0; +float aceleracaoPico = 0; + +int repeticoes = 0; +int max_reps = 0; +int set = 0; +int sets = 0; + +// Frequência cardíaca (atual e agregados por repetição) +int frequenciaCardiacaAtual = 0; +unsigned long somaFrequenciaCardiaca = 0; +unsigned int contagemFrequenciaCardiaca = 0; + +// MQTT reconnect control +unsigned long ultimoMqttAttempt = 0; +const unsigned long MQTT_RECONNECT_INTERVAL_MS = 5000; + +// Buffer MQTT +char msg[256]; +char MQTT_CLIENTID[32]; + +// ============================================================================ +// FUNÇÕES AUXILIARES - Callbacks MQTT +// ============================================================================ +void mqttCallback(char* topic, byte* payload, unsigned int length) +{ + Serial.print("[MQTT] Mensagem em ["); + Serial.print(topic); + Serial.println("]"); + + // Copia payload para buffer null-terminated + char buf[64]; + int n = (length < (int)sizeof(buf) - 1) ? length : (int)sizeof(buf) - 1; + memcpy(buf, payload, n); + buf[n] = '\0'; + + Serial.print("[MQTT] payload: "); + Serial.println(buf); + + // Processa tópicos + if (strcmp(topic, "academia/reps") == 0) { + int v = atoi(buf); + if (!isnan(v)) { + Serial.print("[MQTT] reps = "); + Serial.println(v); + max_reps = v; + } + } else if (strcmp(topic, "academia/sets") == 0) { + int v = atoi(buf); + if (!isnan(v)) { + Serial.print("[MQTT] sets = "); + Serial.println(v); + sets = v; + } + } else if (strcmp(topic, "academia/t_descanso") == 0) { + int v = atoi(buf); + if (!isnan(v)) { + Serial.print("[MQTT] t_descanso = "); + Serial.println(v); + t_descanso = v; + } + } +} + +// ============================================================================ +// FUNÇÕES AUXILIARES - Conectividade +// ============================================================================ +// Conecta o ESP32 à rede WiFi +void connectWiFi() +{ + Serial.print("[WiFi] Conectando..."); + WiFi.begin(WIFI_SSID, WIFI_PASSWORD); + int attempts = 0; + while (WiFi.status() != WL_CONNECTED && attempts < 20) { + delay(500); + Serial.print("."); + attempts++; + } + Serial.println("\r\n[WiFi] Conectado!"); +} + +// Inscreve em tópicos MQTT para receber atualizações de limiares +void mqttSubscribe() +{ + Serial.println("[MQTT] Conectado!"); + for (int i = 0; i < MQTT_SUB_COUNT; ++i) { + if (mqtt.subscribe(MQTT_SUB_TOPICS[i])) { + Serial.print("[MQTT] Tópico: "); + Serial.println(MQTT_SUB_TOPICS[i]); + } + } +} + +// Conecta ao broker MQTT +void connectMQTT() +{ + mqtt.setServer(MQTT_BROKER, MQTT_PORT); + mqtt.setCallback(mqttCallback); + + while (!mqtt.connected()) { + Serial.println("[MQTT] Conectando..."); + if (mqtt.connect(MQTT_CLIENTID)) { + mqttSubscribe(); + break; + } else { + Serial.print("[MQTT] Falha rc="); + Serial.println(mqtt.state()); + delay(2000); + } + } +} + +// ============================================================================ +// FUNÇÕES AUXILIARES - Sensores +// ============================================================================ +// Lê valores de aceleração do sensor MPU +void amostraMPU() +{ + sensors_event_t a, g, temp; + mpu.getEvent(&a, &g, &temp); + + float magnitudeAceleracao = sqrt( + a.acceleration.x * a.acceleration.x + + a.acceleration.y * a.acceleration.y + + a.acceleration.z * a.acceleration.z); + + float aceleracaoCorrigida = magnitudeAceleracao - 9.81; + + aceleracaoFiltrada = FILTER_ALPHA * aceleracaoFiltrada + (1.0 - FILTER_ALPHA) * aceleracaoCorrigida; + + detectarRepeticao(aceleracaoFiltrada); +} + +// ============================================================================ +// FUNÇÕES - Detecção de alarmes +// ============================================================================ +// Verifica qual alarme deve ser ativado baseado nos limites +void detectarRepeticao(float sinal) +{ + unsigned long agora = millis(); + + switch (estado) { + + case OCIOSO: + if (sinal > TH_START) { + t_inicio = agora; + aceleracaoPico = sinal; + estado = SUBINDO; + // Inicia acumulação de frequência cardíaca para esta repetição + somaFrequenciaCardiaca = 0; + contagemFrequenciaCardiaca = 0; + } + break; + + case SUBINDO: + if (sinal > aceleracaoPico) + aceleracaoPico = sinal; + + if (sinal < aceleracaoAnterior) { + t_pico = agora; + estado = DESCENDO; + } + break; + + case DESCENDO: + if (sinal < TH_END) { + t_fim = agora; + + analisarRepeticao(); + verificarSerie(); + } + break; + } + + // Se estamos no meio de uma repetição, acumula a frequência cardíaca atual + if (estado != OCIOSO) { + somaFrequenciaCardiaca += (unsigned long)frequenciaCardiacaAtual; + contagemFrequenciaCardiaca++; + } + + aceleracaoAnterior = sinal; +} + +void analisarRepeticao() +{ + unsigned long tempoTotal = t_fim - t_inicio; + unsigned long tempoSubida = t_pico - t_inicio; + unsigned long tempoDescida = t_fim - t_pico; + + bool ruim = false; + + Serial.print("Total: "); + Serial.print(tempoTotal); + Serial.print(" ms"); + + if (aceleracaoPico < MIN_PEAK_AMPLITUDE) { + Serial.print(" | Repetição parcial"); + ruim = true; + } + + if (tempoTotal < MIN_REP_TIME) { + Serial.print(" | Muito rápido"); + ruim = true; + } + + if (tempoTotal > MAX_REP_TIME) { + Serial.print(" | Muito lento"); + ruim = true; + } + + if (tempoDescida < tempoSubida * 0.6) { + Serial.print(" | Deixando cair o peso"); + ruim = true; + } + + if (!ruim) { + Serial.print(" | Boa repetição"); + repeticoes++; + Serial.print(" | Repetição "); + Serial.print(repeticoes); + } + // Calcula e mostra frequência cardíaca média durante a repetição + unsigned int fcMedia = 0; + if (contagemFrequenciaCardiaca > 0) { + fcMedia = (unsigned int)(somaFrequenciaCardiaca / contagemFrequenciaCardiaca); + Serial.print(" | FC média: "); + Serial.print(fcMedia); + Serial.print(" bpm"); + } + + // Zera agregados após uso + somaFrequenciaCardiaca = 0; + contagemFrequenciaCardiaca = 0; + + snprintf(msg, sizeof(msg), + "{\"fcm\": %d, \"reptime\": %d, \"timeup\": %d, \"timedown\": %d}", + fcMedia, tempoTotal, tempoSubida, tempoDescida); + + mqtt.publish(MQTT_PUB_TOPIC, msg); + + Serial.println(); +} + +void verificarSerie() +{ + if (repeticoes >= max_reps && max_reps > 0) { + Serial.println("[SÉRIE] Série finalizada, iniciando descanso..."); + repeticoes = 0; + estado = DESCANSO; + set++; + digitalWrite(LED_PIN, HIGH); + if (set >= sets && sets > 0) { + Serial.println("[TREINO] Treino finalizado, parabéns!"); + set = 0; + } + } else { + estado = OCIOSO; + } +} + +// ============================================================================ +// FUNÇÕES - Inicialização +// ============================================================================ +// Inicializa o ESP32 e configura os periféricos +void setup() +{ + Serial.begin(115200); + delay(100); + + Serial.println("\n[SETUP] Iniciando sistema..."); + + // Gera ID único MQTT + snprintf(MQTT_CLIENTID, sizeof(MQTT_CLIENTID), "esp32_%08X", + (uint32_t)(ESP.getEfuseMac() & 0xFFFFFFFF)); + + // Conecta + connectWiFi(); + connectMQTT(); + + while (!mpu.begin()) { + Serial.println("MPU6050 not connected!"); + delay(1000); + } + mpu.setAccelerometerRange(MPU6050_RANGE_4_G); + mpu.setGyroRange(MPU6050_RANGE_500_DEG); + mpu.setFilterBandwidth(MPU6050_BAND_21_HZ); + Serial.println("MPU6050 ready!"); + + pinMode(LED_PIN, OUTPUT); + digitalWrite(LED_PIN, LOW); + Serial.println("[SETUP] Pronto!\n"); +} + +// ============================================================================ +// LOOP PRINCIPAL +// ============================================================================ +// Loop principal - executa continuamente +void loop() +{ + if (!mqtt.connected()) { + connectMQTT(); + } + mqtt.loop(); + + if (millis() - ultimaAmostra >= SAMPLE_PERIOD_MS && estado != DESCANSO) { + // Verifica MQTT + + int16_t valorPulso = analogRead(PULSE_PIN); + + // Converter valorPulso para tensão + float tensao = valorPulso * (5.0 / 4095.0); + + // Atualiza frequência cardíaca atual (valor usado nas acumulações) + frequenciaCardiacaAtual = (int)((tensao / 3.3) * 675); + + ultimaAmostra += SAMPLE_PERIOD_MS; + amostraMPU(); + } else if (millis() - t_fim >= t_descanso * 100 && estado == DESCANSO) { + estado = OCIOSO; + digitalWrite(LED_PIN, LOW); + Serial.println("[DESCANSO] Descanso finalizado, pronto para próxima série."); + } +} \ No newline at end of file diff --git a/wokwi.toml b/wokwi.toml new file mode 100644 index 0000000..837c4d1 --- /dev/null +++ b/wokwi.toml @@ -0,0 +1,13 @@ +# Wokwi Configuration File +# Reference: https://docs.wokwi.com/vscode/project-config +[wokwi] +version = 1 +firmware = '.pio/build/esp32/firmware.bin' +elf = '.pio/build/esp32/firmware.elf' + +[[chip]] +name = 'heartrate' +binary = 'chips/heartrate.chip.wasm' + + +