commit 932a3610ddcd43f279fe15808c883bbea7ec2aa6 Author: Amaro Lopes Date: Thu Jan 8 21:40:59 2026 -0300 Versao 1.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..72b7d79 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.pio +.vscode/launch.json +.vscode/c_cpp_properties.json \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..4c3a146 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,11 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide", + "wokwi.wokwi-vscode" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..456c525 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "C_Cpp.clang_format_fallbackStyle": "WebKit", + "editor.formatOnSave": true +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..620c954 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2022 Uri Shaked + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..da9a6a0 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# ESP32 NTP Clock + +A simple clock that displays the time and date on an LCD1602 display. The time is synced via NTP. + +Use [Wokwi](https://marketplace.visualstudio.com/items?itemName=wokwi.wokwi-vscode) to simulate this project. + +## Building + +This is a [PlatformIO](https://platformio.org) project. To build it, [install PlatformIO](https://docs.platformio.org/en/latest/core/installation/index.html), and then run the following command: + +``` +pio run +``` + +## Simulate the project + +To simulate this project, install [Wokwi for VS Code](https://marketplace.visualstudio.com/items?itemName=wokwi.wokwi-vscode). Open the project directory in Visual Studio Code, press **F1** and select "Wokwi: Start Simulator". diff --git a/diagram.json b/diagram.json new file mode 100644 index 0000000..9878da6 --- /dev/null +++ b/diagram.json @@ -0,0 +1,139 @@ +{ + "version": 1, + "author": "Amaro Lopes", + "editor": "wokwi", + "parts": [ + { "type": "board-esp32-devkit-c-v4", "id": "esp", "top": -19.2, "left": -119.96, "attrs": {} }, + { "type": "wokwi-gas-sensor", "id": "gas1", "top": 98.7, "left": -559.4, "attrs": {} }, + { "type": "wokwi-relay-module", "id": "relay1", "top": 86.6, "left": 96, "attrs": {} }, + { + "type": "wokwi-dht22", + "id": "dht1", + "top": -86.1, + "left": -331.8, + "attrs": { "temperature": "21" } + }, + { "type": "wokwi-gnd", "id": "gnd2", "top": 268.8, "left": -298.2, "attrs": {} }, + { + "type": "wokwi-led", + "id": "led1", + "top": 82.8, + "left": 330.6, + "attrs": { "color": "red", "flip": "1" } + }, + { "type": "wokwi-gnd", "id": "gnd3", "top": 268.8, "left": 345, "attrs": {} }, + { + "type": "wokwi-resistor", + "id": "r1", + "top": 119.15, + "left": 259.2, + "attrs": { "value": "220" } + }, + { "type": "wokwi-vcc", "id": "vcc3", "top": -181.64, "left": -412.8, "attrs": {} }, + { "type": "wokwi-vcc", "id": "vcc4", "top": -181.64, "left": 403.2, "attrs": {} }, + { "type": "wokwi-relay-module", "id": "relay2", "top": 0.2, "left": 124.8, "attrs": {} }, + { + "type": "wokwi-led", + "id": "led2", + "top": -3.6, + "left": 359.4, + "attrs": { "color": "green", "flip": "1" } + }, + { + "type": "wokwi-resistor", + "id": "r2", + "top": 32.75, + "left": 288, + "attrs": { "value": "220" } + }, + { "type": "wokwi-relay-module", "id": "relay3", "top": 86.6, "left": 96, "attrs": {} }, + { + "type": "wokwi-led", + "id": "led3", + "top": 82.8, + "left": 330.6, + "attrs": { "color": "red", "flip": "1" } + }, + { + "type": "wokwi-resistor", + "id": "r3", + "top": 119.15, + "left": 259.2, + "attrs": { "value": "220" } + }, + { "type": "wokwi-relay-module", "id": "relay4", "top": -86.2, "left": 134.4, "attrs": {} }, + { + "type": "wokwi-led", + "id": "led4", + "top": -90, + "left": 369, + "attrs": { "color": "blue", "flip": "1" } + }, + { + "type": "wokwi-resistor", + "id": "r4", + "top": -53.65, + "left": 297.6, + "attrs": { "value": "220" } + }, + { + "type": "wokwi-text", + "id": "text1", + "top": -76.8, + "left": 422.4, + "attrs": { "text": "COIFA" } + }, + { + "type": "wokwi-text", + "id": "text2", + "top": 9.6, + "left": 422.4, + "attrs": { "text": "AR CONDICIONADO" } + }, + { + "type": "wokwi-text", + "id": "text3", + "top": 96, + "left": 422.4, + "attrs": { "text": "ALARME SONORO" } + } + ], + "connections": [ + [ "esp:TX", "$serialMonitor:RX", "", [] ], + [ "esp:RX", "$serialMonitor:TX", "", [] ], + [ "dht1:GND", "gnd2:GND", "black", [ "v0" ] ], + [ "dht1:SDA", "esp:32", "green", [ "v0" ] ], + [ "gas1:GND", "gnd2:GND", "black", [ "h0" ] ], + [ "led1:A", "r1:2", "green", [ "v0" ] ], + [ "led1:C", "gnd3:GND", "black", [ "v0" ] ], + [ "relay1:NO", "r1:1", "green", [ "h0" ] ], + [ "vcc3:VCC", "vcc4:VCC", "red", [ "v19.2", "h0", "v0", "h57.6" ] ], + [ "esp:3V3", "vcc3:VCC", "red", [ "h-38.25", "v-144", "h0", "v0", "h-249.6" ] ], + [ "dht1:VCC", "vcc3:VCC", "red", [ "v9.6", "h-38.4" ] ], + [ "gnd2:GND", "gnd3:GND", "black", [ "v-28.8", "h0", "v0", "h595.2" ] ], + [ "esp:GND.2", "gnd3:GND", "black", [ "v0", "h57.6", "v230.4", "h268.8" ] ], + [ "vcc4:VCC", "relay1:COM", "red", [ "v19.2", "h-134.4", "v249.6", "h-19.2" ] ], + [ "gas1:VCC", "vcc3:VCC", "red", [ "h0" ] ], + [ "relay1:VCC", "vcc4:VCC", "red", [ "h-28.8", "v-240", "h345.6" ] ], + [ "relay1:GND", "gnd2:GND", "black", [ "h-19.2", "v124.4", "h-364.8" ] ], + [ "relay1:IN", "esp:4", "green", [ "h0" ] ], + [ "led2:A", "r2:2", "green", [ "v0" ] ], + [ "relay2:NO", "r2:1", "green", [ "h0" ] ], + [ "led2:C", "gnd3:GND", "black", [ "v201.6", "h-29.2" ] ], + [ "relay2:VCC", "vcc4:VCC", "red", [ "h-57.6", "v-153.6", "h345.6" ] ], + [ "relay2:GND", "gnd2:GND", "black", [ "h-48", "v210.8", "h-240" ] ], + [ "relay2:IN", "esp:16", "green", [ "h-67.2", "v76.6" ] ], + [ "led3:A", "r3:2", "green", [ "v0" ] ], + [ "relay3:NO", "r3:1", "green", [ "h0" ] ], + [ "led4:A", "r4:2", "green", [ "v0" ] ], + [ "relay4:NO", "r4:1", "green", [ "h0" ] ], + [ "led4:C", "gnd3:GND", "black", [ "v288", "h-38.8" ] ], + [ "relay4:VCC", "vcc4:VCC", "red", [ "h-67.2", "v-67.2", "h345.6" ] ], + [ "relay4:GND", "gnd2:GND", "black", [ "h-57.6", "v297.2", "h-268.8", "v0", "h-96" ] ], + [ "relay4:IN", "esp:17", "green", [ "h-86.4", "v-0.2" ] ], + [ "relay2:COM", "vcc4:VCC", "red", [ "h39.6", "v-164.6", "h134.4" ] ], + [ "relay4:COM", "vcc4:VCC", "red", [ "h30", "v-78.2", "h134.4" ] ], + [ "gas1:AOUT", "esp:33", "green", [ "h230.4", "v-9.6" ] ] + ], + "dependencies": {} +} \ No newline at end of file diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..7c870d9 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,15 @@ +; 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 diff --git a/src/esp32-ntp-clock.ino b/src/esp32-ntp-clock.ino new file mode 100644 index 0000000..27896f5 --- /dev/null +++ b/src/esp32-ntp-clock.ino @@ -0,0 +1,239 @@ +#include +#include +#include + +#define DHTPIN 32 +#define DHTTYPE DHT22 +#define MQ2PIN 33 +#define ALARME_SONORO 4 +#define COIFA 17 +#define AR_CONDICIONADO 16 + +DHT dht(DHTPIN, DHTTYPE); + +enum statusCozinha { + NOMINAL, + GAS, + TEMP_ALTA, + UMIDADE_ALTA +}; + +int alarmStatus = NOMINAL; +float umidade = 0; +float temperatura = 0; +int iqAR = 0; + +const int GAS_ON = 940; // 1000ppm +const int GAS_OFF = 916; // 500ppm +float UMIDADE_ON = 70.0; +float UMIDADE_OFF = UMIDADE_ON - 5; +float TEMP_ON = 30.0; +float TEMP_OFF = TEMP_ON - 2; + +const char* WIFI_SSID = "Wokwi-GUEST"; +const char* WIFI_PASSWORD = ""; +const char* MQTT_BROKER = "77.37.69.84"; +const int MQTT_PORT = 1883; +char MQTT_CLIENTID[32] = { 0 }; +const char* MQTT_SUB_TOPICS[] = { "cozinha/max_tmp", "cozinha/max_umi" }; +const char* MQTT_PUB_TOPIC = "cozinha"; +char msg[255]; + +const int MQTT_SUB_COUNT = sizeof(MQTT_SUB_TOPICS) / sizeof(MQTT_SUB_TOPICS[0]); + +void mqttCallback(char* topic, byte* payload, unsigned int length) +{ + Serial.print("[MQTT] Mensagem em ["); + Serial.print(topic); + Serial.println("]"); + + char buf[64]; + int n = (length < (sizeof(buf) - 1)) ? length : (sizeof(buf) - 1); + memcpy(buf, payload, n); + buf[n] = '\0'; + + // show payload for debugging + Serial.print("[MQTT] payload: "); + Serial.println(buf); + + if (strcmp(topic, "cozinha/max_tmp") == 0) { + float v = atof(buf); + if (!isnan(v)) { + TEMP_ON = v; + TEMP_OFF = TEMP_ON - 2; + Serial.print("[MQTT] TEMP_ON definido para "); + Serial.println(TEMP_ON); + } else { + Serial.println("[MQTT] Temperatura inválida"); + } + } else if (strcmp(topic, "cozinha/max_umi") == 0) { + float v = atof(buf); + if (!isnan(v)) { + UMIDADE_ON = v; + UMIDADE_OFF = UMIDADE_ON - 5; + Serial.print("[MQTT] UMIDADE_ON definido para "); + Serial.println(UMIDADE_ON); + } else { + Serial.println("[MQTT] Umidade inválida"); + } + } +} + +WiFiClient espClient; +PubSubClient mqtt(espClient); + +void connectWiFi() +{ + Serial.print("[WiFi] Conectando..."); + WiFi.begin(WIFI_SSID, WIFI_PASSWORD); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println("\r\n[WiFi] Conectado!"); +} + +void connectMQTT() +{ + mqtt.setServer(MQTT_BROKER, MQTT_PORT); + mqtt.setCallback(mqttCallback); + while (!mqtt.connected()) { + Serial.println("[MQTT] Conectando..."); + if (mqtt.connect(MQTT_CLIENTID)) { + 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 assinado: "); + Serial.println(MQTT_SUB_TOPICS[i]); + } else { + Serial.print("[MQTT] Assinatura falhou: "); + Serial.println(MQTT_SUB_TOPICS[i]); + } + } + break; + } else { + Serial.print(" ERRO! (rc="); + Serial.print(mqtt.state()); + Serial.println("). Tentando novamente em 2s..."); + delay(2000); + } + } +} + +void read_DHT() +{ + umidade = dht.readHumidity(); + temperatura = dht.readTemperature(); + if (isnan(umidade) || isnan(temperatura)) { + Serial.println("Erro na leitura"); + } +} + +void check_alarm() +{ + if (alarmStatus == GAS) { + if (iqAR > GAS_OFF) + return; + } else { + if (iqAR > GAS_ON) { + alarmStatus = GAS; + return; + } + } + + if (alarmStatus == UMIDADE_ALTA) { + if (umidade > UMIDADE_OFF) + return; + } else { + if (umidade > UMIDADE_ON) { + alarmStatus = UMIDADE_ALTA; + return; + } + } + + if (alarmStatus == TEMP_ALTA) { + if (temperatura > TEMP_OFF) + return; + } else { + if (temperatura > TEMP_ON) { + alarmStatus = TEMP_ALTA; + return; + } + } + alarmStatus = NOMINAL; +} + +void refresh_sensors() +{ + read_DHT(); + iqAR = analogRead(MQ2PIN); + + sprintf(msg, "{\"tmp\": %3.2f, \"umi\": %3.2f, \"gas\": %d}", temperatura, umidade, iqAR); + mqtt.publish(MQTT_PUB_TOPIC, msg); + + check_alarm(); +} + +void setup() +{ + Serial.begin(115200); + dht.begin(); + Serial.println("DHT iniciado"); + + snprintf(MQTT_CLIENTID, sizeof(MQTT_CLIENTID), "esp32_%08X", (uint32_t)(ESP.getEfuseMac() & 0xFFFFFFFF)); + + analogReadResolution(10); + + pinMode(ALARME_SONORO, OUTPUT); + pinMode(COIFA, OUTPUT); + pinMode(AR_CONDICIONADO, OUTPUT); + pinMode(MQ2PIN, INPUT); + pinMode(DHTPIN, INPUT); + + digitalWrite(ALARME_SONORO, LOW); + digitalWrite(COIFA, LOW); + digitalWrite(AR_CONDICIONADO, LOW); + + connectWiFi(); + connectMQTT(); +} + +void loop() +{ + if (!mqtt.connected()) { + connectMQTT(); + } + mqtt.loop(); + + digitalWrite(ALARME_SONORO, LOW); + digitalWrite(COIFA, LOW); + digitalWrite(AR_CONDICIONADO, LOW); + refresh_sensors(); + int alarmCounter = 0; + + unsigned long start = millis(); + while (millis() - start < 2000UL) { + mqtt.loop(); + delay(10); + } + while (alarmStatus != NOMINAL) { + switch (alarmStatus) { + case GAS: + digitalWrite(ALARME_SONORO, HIGH); + case UMIDADE_ALTA: + digitalWrite(COIFA, HIGH); + break; + case TEMP_ALTA: + digitalWrite(AR_CONDICIONADO, HIGH); + break; + default: + Serial.println("STATUS DESCONHECIDO"); + } + alarmCounter++; + delay(500); + if (alarmCounter > 5) { + refresh_sensors(); + alarmCounter = 0; + } + } +} \ No newline at end of file diff --git a/wokwi.toml b/wokwi.toml new file mode 100644 index 0000000..0115800 --- /dev/null +++ b/wokwi.toml @@ -0,0 +1,4 @@ +[wokwi] +version = 1 +elf = ".pio/build/esp32/firmware.elf" +firmware = ".pio/build/esp32/firmware.bin"