From cf6b350ca8d88ccf1a05be66eed38e91eb88e291 Mon Sep 17 00:00:00 2001 From: agres Date: Fri, 26 Sep 2025 16:57:05 +0200 Subject: [PATCH] Ready Device --- esp.code-workspace | 8 ++++ include/README | 37 +++++++++++++++++ lib/README | 46 +++++++++++++++++++++ platformio.ini | 18 ++++++++ src/clock_display.cpp | 22 ++++++++++ src/dht20.cpp | 38 +++++++++++++++++ src/include/clock_display.h | 18 ++++++++ src/include/dht20.h | 22 ++++++++++ src/include/mq2.h | 19 +++++++++ src/include/mqtt_client.h | 27 ++++++++++++ src/include/time_sync.h | 23 +++++++++++ src/include/wifi_connection.h | 19 +++++++++ src/main.cpp | 77 +++++++++++++++++++++++++++++++++++ src/mq2.cpp | 25 ++++++++++++ src/mqtt_client.cpp | 37 +++++++++++++++++ src/secrets/secrets.h.example | 11 +++++ src/time_sync.cpp | 43 +++++++++++++++++++ src/wifi_connection.cpp | 35 ++++++++++++++++ test/README | 11 +++++ 19 files changed, 536 insertions(+) create mode 100644 esp.code-workspace create mode 100644 include/README create mode 100644 lib/README create mode 100644 platformio.ini create mode 100644 src/clock_display.cpp create mode 100644 src/dht20.cpp create mode 100644 src/include/clock_display.h create mode 100644 src/include/dht20.h create mode 100644 src/include/mq2.h create mode 100644 src/include/mqtt_client.h create mode 100644 src/include/time_sync.h create mode 100644 src/include/wifi_connection.h create mode 100644 src/main.cpp create mode 100644 src/mq2.cpp create mode 100644 src/mqtt_client.cpp create mode 100644 src/secrets/secrets.h.example create mode 100644 src/time_sync.cpp create mode 100644 src/wifi_connection.cpp create mode 100644 test/README diff --git a/esp.code-workspace b/esp.code-workspace new file mode 100644 index 0000000..bab1b7f --- /dev/null +++ b/esp.code-workspace @@ -0,0 +1,8 @@ +{ + "folders": [ + { + "path": ".." + } + ], + "settings": {} +} \ No newline at end of file diff --git a/include/README b/include/README new file mode 100644 index 0000000..49819c0 --- /dev/null +++ b/include/README @@ -0,0 +1,37 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the convention is to give header files names that end with `.h'. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..9379397 --- /dev/null +++ b/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into the executable file. + +The source code of each library should be placed in a separate directory +("lib/your_library_name/[Code]"). + +For example, see the structure of the following example libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +Example contents of `src/main.c` using Foo and Bar: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +The PlatformIO Library Dependency Finder will find automatically dependent +libraries by scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..7c07959 --- /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:nodemcuv2] +platform = espressif8266 +board = nodemcuv2 +framework = arduino +upload_port = /dev/cu.usbserial-0001 +lib_deps = adafruit/Adafruit AHTX0 @ ^2.0.5, knolleary/PubSubClient @ ^2.8, https://github.com/avishorp/TM1637.git + + diff --git a/src/clock_display.cpp b/src/clock_display.cpp new file mode 100644 index 0000000..5d68dda --- /dev/null +++ b/src/clock_display.cpp @@ -0,0 +1,22 @@ +#include "include/clock_display.h" + +ClockDisplay::ClockDisplay(uint8_t clkPin, uint8_t dioPin, uint8_t brightness) +: _clk(clkPin), _dio(dioPin), _brightness(brightness), _display(clkPin, dioPin) {} + +void ClockDisplay::begin() { + _display.setBrightness(_brightness); // 0-7 + _display.clear(); +} + +void ClockDisplay::showTime(int hour24, int minute, bool colonOn) { + uint8_t data[] = { + _display.encodeDigit((hour24 / 10) % 10), + _display.encodeDigit(hour24 % 10), + _display.encodeDigit((minute / 10) % 10), + _display.encodeDigit(minute % 10) + }; + _display.setSegments(data); + _display.showNumberDecEx( (hour24/10)*1000 + (hour24%10)*100 + (minute/10)*10 + (minute%10), + colonOn ? 0b01000000 : 0, + true, 4, 0); +} \ No newline at end of file diff --git a/src/dht20.cpp b/src/dht20.cpp new file mode 100644 index 0000000..827fb54 --- /dev/null +++ b/src/dht20.cpp @@ -0,0 +1,38 @@ +#include "include/dht20.h" + +DHT20Sensor::DHT20Sensor() {} + +bool DHT20Sensor::begin() { + if (!aht.begin()) { + Serial.println("Failed to load DHT20 sensor."); + return false; + } + Serial.println("Initialized the DHT20 sensor."); + return true; +} + +void DHT20Sensor::readAndPrint() { + sensors_event_t humidity, temp; + aht.getEvent(&humidity, &temp); + + Serial.print("Temp: "); + Serial.print(temp.temperature); + Serial.print(" °C, Humidity: "); + Serial.print(humidity.relative_humidity); + Serial.println(" %"); +} + +void DHT20Sensor::read() { + sensors_event_t humidity, temp; + aht.getEvent(&humidity, &temp); + _temperature = temp.temperature; + _humidity = humidity.relative_humidity; +} + +float DHT20Sensor::getTemperature() const { + return _temperature; +} + +float DHT20Sensor::getHumidity() const { + return _humidity; +} diff --git a/src/include/clock_display.h b/src/include/clock_display.h new file mode 100644 index 0000000..9e02b24 --- /dev/null +++ b/src/include/clock_display.h @@ -0,0 +1,18 @@ +#ifndef CLOCK_DISPLAY_H +#define CLOCK_DISPLAY_H + +#include +#include + +class ClockDisplay { +public: + ClockDisplay(uint8_t clkPin, uint8_t dioPin, uint8_t brightness = 3); + void begin(); + void showTime(int hour24, int minute, bool colonOn); + +private: + uint8_t _clk, _dio, _brightness; + TM1637Display _display; +}; + +#endif \ No newline at end of file diff --git a/src/include/dht20.h b/src/include/dht20.h new file mode 100644 index 0000000..98f0978 --- /dev/null +++ b/src/include/dht20.h @@ -0,0 +1,22 @@ +#ifndef DHT20_SENSOR_H +#define DHT20_SENSOR_H + +#include +#include + +class DHT20Sensor { +public: + DHT20Sensor(); + bool begin(); + void readAndPrint(); + void read(); + float getTemperature() const; + float getHumidity() const; + +private: + Adafruit_AHTX0 aht; + float _temperature; + float _humidity; +}; + +#endif \ No newline at end of file diff --git a/src/include/mq2.h b/src/include/mq2.h new file mode 100644 index 0000000..2b8448d --- /dev/null +++ b/src/include/mq2.h @@ -0,0 +1,19 @@ +#ifndef MQ2_SENSOR_H +#define MQ2_SENSOR_H + +#include + +class MQ2Sensor { +public: + MQ2Sensor(uint8_t digitalPin); + void begin(); + void read(); + bool isGasDetected() const; + void printStatus(); + +private: + uint8_t _digitalPin; + bool _gasDetected; +}; + +#endif diff --git a/src/include/mqtt_client.h b/src/include/mqtt_client.h new file mode 100644 index 0000000..a4f1d33 --- /dev/null +++ b/src/include/mqtt_client.h @@ -0,0 +1,27 @@ +#ifndef MQTT_CLIENT_H +#define MQTT_CLIENT_H + +#include +#include +#include + +class MQTTClientWrapper { +public: + MQTTClientWrapper(const char* broker, uint16_t port, const char* user, const char* password); + void begin(); + void loop(); + void publishSensorData(float temp, float humidity, bool gas); + +private: + const char* _broker; + uint16_t _port; + const char* _user; + const char* _password; + + WiFiClient _wifiClient; + PubSubClient _client; + + void reconnect(); +}; + +#endif \ No newline at end of file diff --git a/src/include/time_sync.h b/src/include/time_sync.h new file mode 100644 index 0000000..d9a3a89 --- /dev/null +++ b/src/include/time_sync.h @@ -0,0 +1,23 @@ +#ifndef TIME_SYNC_H +#define TIME_SYNC_H + +#include +#include + +class TimeSync { +public: + // tzExample: "CET-1CEST,M3.5.0,M10.5.0/3" for Europe/Berlin + void begin(const char* tzString = "CET-1CEST,M3.5.0,M10.5.0/3"); + bool isTimeSet() const; + void update(); + int hour() const; + int minute() const; + time_t now() const; + +private: + bool _timeSet = false; + mutable tm _tmCache{}; + void refreshLocalTM() const; +}; + +#endif \ No newline at end of file diff --git a/src/include/wifi_connection.h b/src/include/wifi_connection.h new file mode 100644 index 0000000..d9af04a --- /dev/null +++ b/src/include/wifi_connection.h @@ -0,0 +1,19 @@ +#ifndef WIFI_CONNECTION_H +#define WIFI_CONNECTION_H + +#include +#include + +class WiFiConnection { +public: + WiFiConnection(const char* ssid, const char* password); + void begin(); + bool isConnected(); + void printIP(); + +private: + const char* _ssid; + const char* _password; +}; + +#endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..a364cf4 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,77 @@ +#include +#include "include/dht20.h" +#include "include/mq2.h" +#include "include/wifi_connection.h" +#include "include/mqtt_client.h" +#include "include/clock_display.h" +#include "include/time_sync.h" +#include "secrets/secrets.h" + + +DHT20Sensor dht20; +MQ2Sensor mq2(D5); +WiFiConnection wifi(WIFI_SSID, WIFI_PASSWORD); +MQTTClientWrapper mqtt(MQTT_BROKER, 1883, MQTT_USER, MQTT_PASSWORD); +TimeSync timeSync; +ClockDisplay clockDisp(/*CLK=*/D6, /*DIO=*/D7, /*brightness=*/4); + +unsigned long lastDHTPublish = 0; +const unsigned long dhtInterval = 60000; +unsigned long lastClockUpdate = 0; +bool colon = false; + + +void setup() { + Serial.begin(9600); + delay(100); + + if (!dht20.begin()) { + Serial.println("Sensor init failed, check wiring!"); + while (1) delay(100); + } + mq2.begin(); + + delay(1000); + wifi.begin(); + mqtt.begin(); + + timeSync.begin("CET-1CEST,M3.5.0,M10.5.0/3"); + clockDisp.begin(); +} + +void loop() { + + if (!wifi.isConnected()) { + Serial.println("Lost WiFi, reconnecting..."); + wifi.begin(); + } + + mqtt.loop(); + unsigned long nowMs = millis(); + + if (nowMs - lastDHTPublish >= dhtInterval) { + dht20.read(); + mq2.read(); + float temp = dht20.getTemperature() - 0.3f; + float hum = dht20.getHumidity(); + bool gas = mq2.isGasDetected(); + mqtt.publishSensorData(temp, hum, gas); + lastDHTPublish = nowMs; + } + + if (nowMs - lastClockUpdate >= 1000) { + lastClockUpdate = nowMs; + colon = !colon; + + if (timeSync.isTimeSet()) { + int h = timeSync.hour(); + int m = timeSync.minute(); + clockDisp.showTime(h, m, colon); + } else { + clockDisp.showTime(0, 0, colon); + } + } + + delay(50); +} + diff --git a/src/mq2.cpp b/src/mq2.cpp new file mode 100644 index 0000000..7992264 --- /dev/null +++ b/src/mq2.cpp @@ -0,0 +1,25 @@ +#include "include/mq2.h" + +MQ2Sensor::MQ2Sensor(uint8_t digitalPin) : _digitalPin(digitalPin) {} + +void MQ2Sensor::begin() { + pinMode(_digitalPin, INPUT); + Serial.println("Initialized the MQ-2 sensor."); +} + +void MQ2Sensor::read() { + int state = digitalRead(_digitalPin); + _gasDetected = (state == LOW); +} + +bool MQ2Sensor::isGasDetected() const { + return _gasDetected; +} + +void MQ2Sensor::printStatus() { + if (isGasDetected()) { + Serial.println("Gas/Smoke detected!"); + } else { + Serial.println("Clean"); + } +} diff --git a/src/mqtt_client.cpp b/src/mqtt_client.cpp new file mode 100644 index 0000000..24e0c27 --- /dev/null +++ b/src/mqtt_client.cpp @@ -0,0 +1,37 @@ +#include "include/mqtt_client.h" + +MQTTClientWrapper::MQTTClientWrapper(const char* broker, uint16_t port, const char* user, const char* password) +: _broker(broker), _port(port), _user(user), _password(password), _client(_wifiClient) {} + +void MQTTClientWrapper::begin() { + _client.setServer(_broker, _port); +} + +void MQTTClientWrapper::loop() { + if (!_client.connected()) { + reconnect(); + } + _client.loop(); +} + +void MQTTClientWrapper::publishSensorData(float temp, float humidity, bool gas) { + if (_client.connected()) { + _client.publish("home/esp8266-1/temp", String(temp, 1).c_str()); + _client.publish("home/esp8266-1/humidity", String(humidity, 1).c_str()); + _client.publish("home/esp8266-1/gas", gas ? "1" : "0"); + } +} + +void MQTTClientWrapper::reconnect() { + while (!_client.connected()) { + Serial.print("Connecting to MQTT..."); + if (_client.connect("ESP8266Client", _user, _password)) { + Serial.println("connected"); + } else { + Serial.print("failed, rc="); + Serial.print(_client.state()); + Serial.println(" try again in 5s"); + delay(5000); + } + } +} \ No newline at end of file diff --git a/src/secrets/secrets.h.example b/src/secrets/secrets.h.example new file mode 100644 index 0000000..6d77dae --- /dev/null +++ b/src/secrets/secrets.h.example @@ -0,0 +1,11 @@ +#ifndef SECRETS_H +#define SECRETS_H + +#define WIFI_SSID "SSID" +#define WIFI_PASSWORD "WIFI_PASSWORD" + +#define MQTT_BROKER "LOCAL_IP_TO_MQTT_SERVER" +#define MQTT_USER "MQTT_USER" +#define MQTT_PASSWORD "MQTT_PASSWORD" + +#endif \ No newline at end of file diff --git a/src/time_sync.cpp b/src/time_sync.cpp new file mode 100644 index 0000000..df82343 --- /dev/null +++ b/src/time_sync.cpp @@ -0,0 +1,43 @@ +#include "include/time_sync.h" + +void TimeSync::begin(const char* tzString) { + configTime(0, 0, "pool.ntp.org", "time.nist.gov", "time.google.com"); + setenv("TZ", tzString, 1); + tzset(); + for (int i = 0; i < 50; i++) { + time_t t = time(nullptr); + if (t > 1600000000) { + _timeSet = true; + break; + } + delay(100); + } +} + +bool TimeSync::isTimeSet() const { + time_t t = time(nullptr); + return (t > 1600000000); +} + +void TimeSync::update() { + +} + +void TimeSync::refreshLocalTM() const { + time_t t = time(nullptr); + localtime_r(&t, &_tmCache); +} + +int TimeSync::hour() const { + refreshLocalTM(); + return _tmCache.tm_hour; +} + +int TimeSync::minute() const { + refreshLocalTM(); + return _tmCache.tm_min; +} + +time_t TimeSync::now() const { + return time(nullptr); +} \ No newline at end of file diff --git a/src/wifi_connection.cpp b/src/wifi_connection.cpp new file mode 100644 index 0000000..fb21b1b --- /dev/null +++ b/src/wifi_connection.cpp @@ -0,0 +1,35 @@ +#include "include/wifi_connection.h" + +WiFiConnection::WiFiConnection(const char* ssid, const char* password) +: _ssid(ssid), _password(password) {} + +void WiFiConnection::begin() { + Serial.print("Connecting to WiFi: "); + Serial.println(_ssid); + + WiFi.mode(WIFI_STA); + WiFi.begin(_ssid, _password); + + int retries = 0; + while (WiFi.status() != WL_CONNECTED && retries < 20) { + delay(500); + Serial.print("."); + retries++; + } + + if (WiFi.status() == WL_CONNECTED) { + Serial.println("\nWiFi connected!"); + printIP(); + } else { + Serial.println("\nFailed to connect to WiFi."); + } +} + +bool WiFiConnection::isConnected() { + return WiFi.status() == WL_CONNECTED; +} + +void WiFiConnection::printIP() { + Serial.print("IP Address: "); + Serial.println(WiFi.localIP()); +} diff --git a/test/README b/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html