/*
* ╔══════════════════════════════════════════════════════════════╗
* ║ DEEPSHIELD — Sistema de Monitorizacao ║
* ║ Fato de Operação em Reforço de Tecnologia e Segurança ║
* ║ Sociedade Mineira de Catoca — Lunda Sul, Angola ║
* ║ Instituto Politécnico Dom Damião Franklin — 2026 ║
* ╠══════════════════════════════════════════════════════════════╣
* ║ BROKER: test.mosquitto.org — Porta 1883 (sem TLS, público) ║
* ║ Sem autenticação — compatível com Wokwi e IoT MQTT Panel ║
* ╠══════════════════════════════════════════════════════════════╣
* ║ SLIDERS calibrados pela Tabela de Limiares ISO 45001:2018 ║
* ║ SLIDER 1 → Monóxido de Carbono CO (0 – 100 ppm) ║
* ║ SLIDER 2 → Metano CH4 (0.0 – 2.0 %LEL) ║
* ║ SLIDER 3 → Oxigénio O2 (16.0 – 21.0 %) ║
* ║ SLIDER 4 → Sulf. Hidrogénio H2S (0.0 – 20.0 ppm) ║
* ║ SLIDER 5 → Frequência Cardíaca BPM (40 – 160 bpm) ║
* ║ SLIDER 6 → Saturação de O2 SpO2 (70 – 100 %) ║
* ╠══════════════════════════════════════════════════════════════╣
* ║ OSCILAÇÕES AUTOMÁTICAS a cada 30 segundos: ║
* ║ Fase 0 (0-30s) → Todos os sensores em estado NORMAL ║
* ║ Fase 1 (30-60s) → CO e CH4 sobem → estado ATENÇÃO ║
* ║ Fase 2 (60-90s) → CO explode, O2 cai, BPM dispara→ PERIGO ║
* ║ Fase 3 (90-120s)→ Valores recuperam → volta a NORMAL ║
* ╚══════════════════════════════════════════════════════════════╝
*/
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <DHT.h>
#include <Adafruit_BMP280.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
// ── WiFi ──────────────────────────────────────────────────────
const char* WIFI_SSID = "Wokwi-GUEST";
const char* WIFI_PASS = "";
// ── Broker MQTT Público — test.mosquitto.org ─────────────────
const char* MQTT_HOST = "test.mosquitto.org";
const int MQTT_PORT = 1883;
const char* MQTT_USER = ""; // sem autenticação
const char* MQTT_PASS = ""; // sem autenticação
const char* MQTT_TOPIC = "deepshield/sensores";
const char* MQTT_CLIENT = "DeepShield-Catoca-01";
// ── Pinos ─────────────────────────────────────────────────────
#define DHT_PIN 15
#define DHT_TYPE DHT22
#define PIN_CO 34
#define PIN_CH4 35
#define PIN_O2 32
#define PIN_H2S 33
#define PIN_BPM 36
#define PIN_SPO2 39
#define LED_GREEN 25
#define LED_YELLOW 26
#define LED_RED 27
#define BUZZER 2
// ── Limiares ISO 45001:2018 + OIT Convenção 176 ───────────────
#define CO_NORMAL_MAX 25.0
#define CO_PERIGO_MIN 35.0
#define CH4_NORMAL_MAX 0.5
#define CH4_PERIGO_MIN 1.0
#define O2_NORMAL_MIN 19.5
#define O2_PERIGO_MAX 16.0
#define H2S_NORMAL_MAX 1.0
#define H2S_PERIGO_MIN 5.0
#define TEMP_NORMAL_MAX 28.0
#define TEMP_PERIGO_MIN 35.0
#define HUM_NORMAL_MAX 60.0
#define HUM_PERIGO_MIN 80.0
#define BPM_MIN_SAFE 50.0
#define BPM_ATENCAO 100.0
#define BPM_PERIGO 120.0
#define SPO2_NORMAL_MIN 95.0
#define SPO2_ATENCAO 90.0
#define FASE_DURACAO 30000UL
// ── Objectos ──────────────────────────────────────────────────
LiquidCrystal_I2C lcd(0x27, 16, 2);
DHT dht(DHT_PIN, DHT_TYPE);
Adafruit_BMP280 bmp;
Adafruit_MPU6050 mpu;
WiFiClient wifiClient; // sem SSL — WiFiClient simples
PubSubClient mqtt(wifiClient); // usa WiFiClient simples
// ── Variáveis de sensores ─────────────────────────────────────
float co, ch4, o2, h2s;
float temperatura, humidade, pressao;
float bpm, spo2;
float ax, ay, az;
int estado = 0;
bool imobilidade = false;
unsigned long imobStart = 0;
unsigned long lastMQTT = 0;
unsigned long lastSerial = 0;
unsigned long lastBuzzer = 0;
unsigned long lastLCDSwap = 0;
unsigned long lastLCDDraw = 0;
int lcdPage = 0;
int faseAtual = 0;
unsigned long faseInicio = 0;
bool modoAuto = true;
// ── Dados de cada fase ────────────────────────────────────────
struct FaseDados {
float co_b, ch4_b, o2_b, h2s_b, bpm_b, spo2_b, temp_b, hum_b;
const char* nome;
};
FaseDados fases[4] = {
{12.0, 0.20, 20.5, 0.3, 78.0, 97.0, 26.0, 52.0, "NORMAL "},
{28.0, 0.70, 19.8, 1.5, 105.0, 93.0, 30.0, 65.0, "ATENCAO "},
{72.0, 1.40, 14.5, 7.0, 135.0, 85.0, 37.0, 82.0, "PERIGO!! "},
{18.0, 0.30, 20.2, 0.5, 88.0, 94.0, 28.5, 58.0, "RECUPERACAO"},
};
// ── Helpers ───────────────────────────────────────────────────
float mapSlider(int pin, float minV, float maxV) {
return minV + ((float)analogRead(pin) / 4095.0f) * (maxV - minV);
}
int calcEstado() {
if (co > CO_PERIGO_MIN || ch4 > CH4_PERIGO_MIN || o2 < O2_PERIGO_MAX ||
h2s > H2S_PERIGO_MIN || temperatura > TEMP_PERIGO_MIN ||
humidade > HUM_PERIGO_MIN || bpm > BPM_PERIGO || bpm < BPM_MIN_SAFE ||
spo2 < SPO2_ATENCAO || imobilidade) return 2;
if (co > CO_NORMAL_MAX || ch4 > CH4_NORMAL_MAX || o2 < O2_NORMAL_MIN ||
h2s > H2S_NORMAL_MAX || temperatura > TEMP_NORMAL_MAX ||
humidade > HUM_NORMAL_MAX || bpm > BPM_ATENCAO ||
spo2 < SPO2_NORMAL_MIN) return 1;
return 0;
}
void actualizarLEDs() {
digitalWrite(LED_GREEN, estado == 0 ? HIGH : LOW);
digitalWrite(LED_YELLOW, estado == 1 ? HIGH : LOW);
digitalWrite(LED_RED, estado == 2 ? HIGH : LOW);
}
void actualizarBuzzer() {
if (estado == 2 && millis() - lastBuzzer > 400) {
tone(BUZZER, 1400, 120); lastBuzzer = millis();
} else if (estado == 1 && millis() - lastBuzzer > 2500) {
tone(BUZZER, 800, 80); lastBuzzer = millis();
} else if (estado == 0) {
noTone(BUZZER);
}
}
void actualizarLCD() {
if (millis() - lastLCDSwap > 3000) {
lcdPage = (lcdPage + 1) % 5;
lastLCDSwap = millis();
lcd.clear();
}
if (millis() - lastLCDDraw < 200) return;
lastLCDDraw = millis();
char buf[17];
switch (lcdPage) {
case 0:
lcd.setCursor(0, 0); lcd.print(" DEEPSHIELD ");
lcd.setCursor(0, 1);
if (estado == 0) lcd.print("ESTADO: NORMAL ");
else if (estado == 1) lcd.print("ESTADO: ATENCAO!");
else lcd.print("ESTADO:*PERIGO* ");
break;
case 1:
lcd.setCursor(0, 0);
snprintf(buf, 17, "CO:%-5.1fppm %s", co,
co > CO_PERIGO_MIN ? "[!]" : co > CO_NORMAL_MAX ? "[~]" : "[ok]");
lcd.print(buf);
lcd.setCursor(0, 1);
snprintf(buf, 17, "CH4:%-4.2f%%LEL%s", ch4,
ch4 > CH4_PERIGO_MIN ? "[!]" : ch4 > CH4_NORMAL_MAX ? "[~]" : "[ok]");
lcd.print(buf);
break;
case 2:
lcd.setCursor(0, 0);
snprintf(buf, 17, "O2:%-5.1f%% %s", o2,
o2 < O2_PERIGO_MAX ? "[!]" : o2 < O2_NORMAL_MIN ? "[~]" : "[ok]");
lcd.print(buf);
lcd.setCursor(0, 1);
snprintf(buf, 17, "H2S:%-5.1fppm%s", h2s,
h2s > H2S_PERIGO_MIN ? "[!]" : h2s > H2S_NORMAL_MAX ? "[~]" : "[ok]");
lcd.print(buf);
break;
case 3:
lcd.setCursor(0, 0);
snprintf(buf, 17, "T:%-4.1fC H:%-3.0f%% ", temperatura, humidade);
lcd.print(buf);
lcd.setCursor(0, 1);
snprintf(buf, 17, "P:%-6.0fhPa ", pressao);
lcd.print(buf);
break;
case 4:
lcd.setCursor(0, 0);
snprintf(buf, 17, "BPM:%-3d SpO2:%-2d%%", (int)bpm, (int)spo2);
lcd.print(buf);
lcd.setCursor(0, 1);
if (imobilidade) lcd.print("!!IMOBILIDADE!! ");
else if (estado==2) lcd.print("Vital: *PERIGO* ");
else if (estado==1) lcd.print("Vital: ATENCAO ");
else lcd.print("Vital: NORMAL ");
break;
}
}
void publicarMQTT() {
if (!mqtt.connected()) return;
StaticJsonDocument<512> doc;
doc["co_ppm"] = round(co * 10) / 10.0;
doc["ch4_lel"] = round(ch4 * 100) / 100.0;
doc["o2_percent"] = round(o2 * 10) / 10.0;
doc["h2s_ppm"] = round(h2s * 10) / 10.0;
doc["temperatura"] = round(temperatura * 10) / 10.0;
doc["humidade"] = round(humidade * 10) / 10.0;
doc["pressao_hpa"] = round(pressao);
doc["bpm"] = (int)bpm;
doc["spo2"] = (int)spo2;
doc["imobilidade"] = imobilidade;
doc["estado"] = estado == 0 ? "NORMAL" : estado == 1 ? "ATENCAO" : "PERIGO";
doc["fase"] = fases[faseAtual].nome;
doc["modo"] = modoAuto ? "AUTO" : "MANUAL";
doc["mineiro"] = "Trabalhador-01";
doc["local"] = "Catoca-Sector4B";
char payload[512];
serializeJson(doc, payload);
mqtt.publish(MQTT_TOPIC, payload, true);
Serial.print("[MQTT] Publicado: "); Serial.println(payload);
}
void imprimirSerial() {
unsigned long fs = (millis() - faseInicio) / 1000;
Serial.println("\n╔══════════════════════════════════════════╗");
Serial.println( "║ DEEPSHIELD — DADOS EM TEMPO REAL ║");
Serial.print( "║ Fase: "); Serial.print(fases[faseAtual].nome);
Serial.print(" | "); Serial.print(fs); Serial.println("s ║");
Serial.println( "╚══════════════════════════════════════════╝");
Serial.print("🌫️ CO : "); Serial.print(co,1); Serial.print(" ppm "); Serial.println(co>CO_PERIGO_MIN?"⚠️ PERIGO":co>CO_NORMAL_MAX?"⚡ ATENÇÃO":"✅ OK");
Serial.print("💨 CH4 : "); Serial.print(ch4,3); Serial.print(" %LEL "); Serial.println(ch4>CH4_PERIGO_MIN?"⚠️ PERIGO":ch4>CH4_NORMAL_MAX?"⚡ ATENÇÃO":"✅ OK");
Serial.print("🌬️ O2 : "); Serial.print(o2,1); Serial.print(" % "); Serial.println(o2<O2_PERIGO_MAX?"⚠️ PERIGO":o2<O2_NORMAL_MIN?"⚡ ATENÇÃO":"✅ OK");
Serial.print("☣️ H2S : "); Serial.print(h2s,2); Serial.print(" ppm "); Serial.println(h2s>H2S_PERIGO_MIN?"⚠️ PERIGO":h2s>H2S_NORMAL_MAX?"⚡ ATENÇÃO":"✅ OK");
Serial.print("🌡️ Temp : "); Serial.print(temperatura,1); Serial.print(" °C "); Serial.println(temperatura>TEMP_PERIGO_MIN?"⚠️ PERIGO":temperatura>TEMP_NORMAL_MAX?"⚡ ATENÇÃO":"✅ OK");
Serial.print("💧 Humidade : "); Serial.print(humidade,1); Serial.print(" % "); Serial.println(humidade>HUM_PERIGO_MIN?"⚠️ PERIGO":humidade>HUM_NORMAL_MAX?"⚡ ATENÇÃO":"✅ OK");
Serial.print("📊 Pressão : "); Serial.print(pressao,1); Serial.println(" hPa");
Serial.print("❤️ BPM : "); Serial.print((int)bpm); Serial.print(" bpm "); Serial.println((bpm>BPM_PERIGO||bpm<BPM_MIN_SAFE)?"⚠️ PERIGO":bpm>BPM_ATENCAO?"⚡ ATENÇÃO":"✅ OK");
Serial.print("🩸 SpO2 : "); Serial.print((int)spo2); Serial.print(" % "); Serial.println(spo2<SPO2_ATENCAO?"⚠️ PERIGO":spo2<SPO2_NORMAL_MIN?"⚡ ATENÇÃO":"✅ OK");
Serial.print("🧍 Imob.>30s : "); Serial.println(imobilidade?"⚠️ SIM":"✅ NÃO");
Serial.print("🚦 ESTADO : ");
if (estado==0) Serial.println("✅ NORMAL");
else if (estado==1) Serial.println("⚡ ATENÇÃO");
else Serial.println("⚠️ PERIGO — EVACUAÇÃO IMEDIATA!");
Serial.print("[MQTT] "); Serial.println(mqtt.connected()?"✅ Ligado (mosquitto 1883)":"❌ Desligado");
Serial.print("[MODO] "); Serial.println(modoAuto?"🔄 AUTO (30s/fase)":"🖐 MANUAL (sliders)");
}
void conectarWiFi() {
lcd.clear();
lcd.setCursor(0,0); lcd.print("A ligar WiFi... ");
WiFi.begin(WIFI_SSID, WIFI_PASS);
int t = 0;
while (WiFi.status() != WL_CONNECTED && t < 20) {
delay(500); Serial.print("."); t++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\n[WiFi] ✅ Ligado!");
lcd.setCursor(0,1); lcd.print("WiFi OK! ");
} else {
Serial.println("\n[WiFi] ❌ Falhou.");
lcd.setCursor(0,1); lcd.print("WiFi FALHOU ");
}
delay(800);
}
void conectarMQTT() {
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setBufferSize(512);
lcd.clear();
lcd.setCursor(0,0); lcd.print("A ligar Broker ");
lcd.setCursor(0,1); lcd.print("mosquitto:1883 ");
int t = 0;
while (!mqtt.connected() && t < 5) {
Serial.print("[MQTT] A ligar...");
if (mqtt.connect(MQTT_CLIENT)) {
Serial.println(" ✅ Ligado a test.mosquitto.org:1883!");
lcd.setCursor(0,1); lcd.print("Broker OK! :) ");
} else {
Serial.print(" ❌ Erro: "); Serial.println(mqtt.state());
lcd.setCursor(0,1); lcd.print("MQTT erro... ");
delay(1500);
}
t++;
}
delay(800);
}
// ══════════════════════════════════════════════════════════════
void setup() {
Serial.begin(115200);
pinMode(LED_GREEN, OUTPUT); pinMode(LED_YELLOW, OUTPUT);
pinMode(LED_RED, OUTPUT); pinMode(BUZZER, OUTPUT);
pinMode(PIN_CO, INPUT); pinMode(PIN_CH4, INPUT);
pinMode(PIN_O2, INPUT); pinMode(PIN_H2S, INPUT);
pinMode(PIN_BPM, INPUT); pinMode(PIN_SPO2, INPUT);
lcd.init(); lcd.backlight();
lcd.setCursor(0,0); lcd.print(" DEEPSHIELD ");
lcd.setCursor(0,1); lcd.print(" Iniciando... ");
delay(1200);
dht.begin();
if (!bmp.begin(0x76)) Serial.println("[BMP280] ❌ Não encontrado!");
if (!mpu.begin()) {
Serial.println("[MPU6050] ❌ Não encontrado!");
} else {
mpu.setAccelerometerRange(MPU6050_RANGE_8_G);
mpu.setGyroRange(MPU6050_RANGE_500_DEG);
mpu.setFilterBandwidth(MPU6050_BAND_21_HZ);
}
// Teste LEDs
lcd.clear(); lcd.setCursor(0,0); lcd.print("Teste de LEDs...");
digitalWrite(LED_GREEN, HIGH); delay(250); digitalWrite(LED_GREEN, LOW);
digitalWrite(LED_YELLOW, HIGH); delay(250); digitalWrite(LED_YELLOW, LOW);
digitalWrite(LED_RED, HIGH); delay(250); digitalWrite(LED_RED, LOW);
tone(BUZZER, 1000, 100); delay(300); noTone(BUZZER);
conectarWiFi();
if (WiFi.status() == WL_CONNECTED) conectarMQTT();
faseInicio = millis();
lcd.clear();
lcd.setCursor(0,0); lcd.print(" DEEPSHIELD ");
lcd.setCursor(0,1); lcd.print("Sistema Activo! ");
delay(800); lcd.clear();
Serial.println("\n[DeepShield] ✅ Sistema iniciado!");
Serial.println("[DeepShield] Broker: test.mosquitto.org porta 1883 (público)");
Serial.println("[DeepShield] Oscilações automáticas: 30s por fase");
}
// ══════════════════════════════════════════════════════════════
void loop() {
// ── Manter MQTT ──
if (WiFi.status() == WL_CONNECTED && !mqtt.connected()) conectarMQTT();
if (mqtt.connected()) mqtt.loop();
// ── Gerir fases (30 segundos cada) ──
if (millis() - faseInicio >= FASE_DURACAO) {
faseAtual = (faseAtual + 1) % 4;
faseInicio = millis();
lcd.clear();
Serial.print("\n[FASE] → "); Serial.println(fases[faseAtual].nome);
}
// ── Detectar se slider foi movido ──
bool sliderMovido = (analogRead(PIN_CO) > 200 ||
analogRead(PIN_CH4) > 200 ||
analogRead(PIN_O2) < 3700 ||
analogRead(PIN_H2S) > 200 ||
analogRead(PIN_BPM) > 600 ||
analogRead(PIN_SPO2)< 3500);
modoAuto = !sliderMovido;
if (modoAuto) {
// ── MODO AUTOMÁTICO ──
FaseDados& f = fases[faseAtual];
float t = millis() / 1000.0f;
co = constrain(f.co_b + 3.0f * sin(t * 0.8f), 0.0f, 100.0f);
ch4 = constrain(f.ch4_b + 0.05f * sin(t * 0.6f), 0.0f, 2.0f);
o2 = constrain(f.o2_b + 0.2f * sin(t * 0.5f), 14.0f, 21.0f);
h2s = constrain(f.h2s_b + 0.3f * sin(t * 0.7f), 0.0f, 20.0f);
bpm = constrain(f.bpm_b + 5.0f * sin(t * 1.2f), 30.0f, 160.0f);
spo2 = constrain(f.spo2_b + 1.0f * sin(t * 0.9f), 70.0f, 100.0f);
temperatura = constrain(f.temp_b + 0.5f * sin(t * 0.4f), 15.0f, 50.0f);
humidade = constrain(f.hum_b + 2.0f * sin(t * 0.3f), 20.0f, 100.0f);
} else {
// ── MODO MANUAL (sliders) ──
co = mapSlider(PIN_CO, 0.0f, 100.0f);
ch4 = mapSlider(PIN_CH4, 0.0f, 2.0f);
o2 = mapSlider(PIN_O2, 16.0f, 21.0f);
h2s = mapSlider(PIN_H2S, 0.0f, 20.0f);
bpm = mapSlider(PIN_BPM, 40.0f, 160.0f);
spo2 = mapSlider(PIN_SPO2, 70.0f, 100.0f);
float t_d = dht.readTemperature();
float h_d = dht.readHumidity();
if (!isnan(t_d)) temperatura = t_d;
if (!isnan(h_d)) humidade = h_d;
}
// ── BMP280 ──
pressao = bmp.readPressure() / 100.0f;
if (pressao < 900 || pressao > 1100) pressao = 1012.0f;
// ── MPU-6050 ──
sensors_event_t a, g, tmp;
mpu.getEvent(&a, &g, &tmp);
ax = a.acceleration.x; ay = a.acceleration.y; az = a.acceleration.z;
// ── Imobilidade ──
float mov = abs(ax) + abs(ay) + abs(az - 9.8f);
if (mov < 0.5f) {
if (imobStart == 0) imobStart = millis();
if (millis() - imobStart > 30000UL) imobilidade = true;
} else { imobStart = 0; imobilidade = false; }
// ── Estado + Saídas ──
estado = calcEstado();
actualizarLEDs();
actualizarBuzzer();
actualizarLCD();
if (millis() - lastMQTT > 2000) { publicarMQTT(); lastMQTT = millis(); }
if (millis() - lastSerial > 3000) { imprimirSerial(); lastSerial = millis(); }
delay(2);
}
DEEPSHIELD — Sistema de Monitorizacao em Tempo Real
SLIDER 1 — Monox. Carbono (CO)
Normal<25 | Atenc.25-35 | Perigo>35 ppm
SLIDER 2 — Metano (CH4)
Normal<0.5 | Atenc.0.5-1 | Perigo>1 %LEL
SLIDER 3 — Oxigenio (O2)
Normal>19.5 | Atenc.16-19.5 | Perigo<16%
SLIDER 4 — Sulf.Hidrogenio (H2S)
Normal<1 | Atenc.1-5 | Perigo>5 ppm
SLIDER 5 — Freq.Cardiaca (BPM)
Normal 60-100 | Atenc.100-120 | Perigo>120
SLIDER 6 — Saturacao O2 (SpO2)
Normal>=95 | Atenc.90-94 | Perigo<90%
NORMAL
ATENC.
PERIGO
ALARME