mirror of
https://github.com/bjoernellens1/ESP32-PowerGuard.git
synced 2024-11-23 09:35:05 +00:00
update and cleanup
This commit is contained in:
parent
e2c6f699fe
commit
ff60911d6d
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.pio/
|
||||||
|
.vscode/
|
||||||
|
src/secrets.h
|
93
README.md
93
README.md
@ -1,26 +1,97 @@
|
|||||||
# ESP32-PowerGuard - Digitalization in Energy Engineering
|
# ESP32-PowerGuard - Digitalization in Energy Engineering
|
||||||
|
|
||||||
This repository contains the source code for our university group project focused on digitalizing energy engineering in the industrial sector. Our project aims to ensure save power distribution for retrofit mobile robots with capable mini PCs. This led to a little project using ESP32 and some relay boards.
|
This repository contains the source code for our university group project focused on digitalizing energy engineering in the industrial sector. Our project aims to ensure safe power distribution for retrofit mobile robots with capable mini PCs. However, we wanted to come up with a solution that can be applied to any power circuit. This led to a little project using ESP32 and some relay boards.
|
||||||
|
|
||||||
### Key Features:
|
### Key Features:
|
||||||
|
|
||||||
ESP32-based power monitoring for voltage and consumption.
|
- ESP32-based power monitoring for voltage and consumption.
|
||||||
Integration of 2 relay boards to prevent unforeseen voltage conditions.
|
- Integration of 2 relay boards to prevent unforeseen voltage conditions.
|
||||||
Data logging to InfluxDB for comprehensive analysis.
|
- Data logging to InfluxDB for comprehensive analysis.
|
||||||
Grafana dashboard for visualizing power conditions.
|
- Grafana dashboard for visualizing power conditions.
|
||||||
Embedded webserver on ESP32 for real-time voltage and power readings and relay control.
|
- Embedded webserver on ESP32 for real-time voltage and power readings and relay control.
|
||||||
|
|
||||||
### How to Use:
|
### Quick start
|
||||||
Hardware needed:
|
1. Spin up InfluxDb and Grafana via Docker Compose:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Open this folder with Visual Studio Code and install the PlatformIO plugin.
|
||||||
|
3. Rename `secrets.h.example` to `secrets.h` and fill in your credentials.
|
||||||
|
4. Flash ESP32 with the code. Connect the INA219 module according to the schematics and power up the ESP32.
|
||||||
|
5. The ESP32 will connect to your WiFi and start sending data to the InfluxDB. InfluxDB webgui is available at [http://localhost:8087](http://localhost:8087). You can now start the Grafana dashboard and connect it to the InfluxDB. The Grafana dashboard is available at [http://localhost:3002](http://localhost:3002).
|
||||||
|
6. Control the ESP32 via the webserver at `http://<ESP_IP>:80`.
|
||||||
|
You can obtain the ESP32 IP address from the serial console.
|
||||||
|
|
||||||
|
## Tutorial:
|
||||||
|
### Contents of this repository
|
||||||
|
- `platformio.ini`: Contains the configuration for the PlatformIO plugin.
|
||||||
|
- `docker-compose.yml`: Contains the configuration for the Docker Compose setup.
|
||||||
|
- `src`: Contains the source code for the ESP32.
|
||||||
|
- `doc`: Here you will find the schematics and the project documentation.
|
||||||
|
|
||||||
|
### Hardware needed:
|
||||||
- ESP32 Devkit C (or derivative)
|
- ESP32 Devkit C (or derivative)
|
||||||
- Relay Module board with 2x Relays
|
- Relay Module board with 2x Relays
|
||||||
- INA219 Power Monitoring Module
|
- INA219 Power Monitoring Module
|
||||||
- Computer/Server for hosting the Database and Grafana.
|
- Computer/Server for hosting the Database and Grafana.
|
||||||
|
The code was written using the PlatformIO plugin for Visual Studio Code. You are advised to use the same setup.
|
||||||
|
|
||||||
The code was written using the PlatformIO plugin for Visual Studio Code. Your are advised to use the same setup.
|
Clone this repository:
|
||||||
|
```shell
|
||||||
|
git clone https://github.com/bjoernellens1/ESP32-PowerGuard.git
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setup your services
|
||||||
|
Install the latest version of Docker and Docker Compose:
|
||||||
|
[https://docs.docker.com/get-docker/](https://docs.docker.com/get-docker/)
|
||||||
|
Start the services via:
|
||||||
|
```shell
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you can access Grafana and InfluxDB via the webgui.
|
||||||
|
|
||||||
|
The Grafana dashboard is available at [http://localhost:3002](http://localhost:3002).
|
||||||
|
|
||||||
|
InfluxDB webgui is available at [http://localhost:8087](http://localhost:8087).
|
||||||
|
|
||||||
|
Having the services running, it is time to start the setup wizard in InfluxDB. Login with username 'influxUser' and password 'influxUserPW' .
|
||||||
|
On the welcome page you will see the Ardino setup tutorial. Click on the 'Arduino' button and follow the instructions. Here you will obtain parameters needed in the next step.
|
||||||
|
|
||||||
|
### Setup ESP32
|
||||||
|
#### Hardware Setup
|
||||||
|
Connect the INA219 module according to the schematics.
|
||||||
|
Connect the relay module to the ESP32. The relay module should be connected to the pins 26 and 27 of the ESP32. The relay module should be powered by an external power supply. The relay module should be connected to the power circuit you want to monitor and control. The INA219 module should be connected to the power circuit you want to monitor. The INA219 module should be powered by the ESP32.
|
||||||
|
![Schematics - Wiring](resources/wiring.png)
|
||||||
|
|
||||||
|
|
||||||
### License:
|
#### Software Setup
|
||||||
MIT License
|
Connect the ESP to your PC via USB cable. Install Visual Studio Code and the PlatformIO extension from the extension store.
|
||||||
|
Open this folder in Visual Studio Code. The PlatformIO welcome page will open automatically and needed packages should be installed.
|
||||||
|
|
||||||
|
Then you need to change some files to include your configuration:
|
||||||
|
|
||||||
|
Rename `secrets.h.example` to `secrets.h` and fill in your credentials. INFLUXDB_TOKEN is the token you obtained in the previous step. INFLUXDB_ORG is the organization name you chose. INFLUXDB_BUCKET is the bucket name you chose. INFLUXDB_URL is the URL of your InfluxDB instance. In our case we are hosting it locally, so we need to fill in the PCs IP Adress followed by the InfluxDB port, for instance:
|
||||||
|
|
||||||
|
```
|
||||||
|
INFLUXDB_URL = "http://192.168.1.100:8087"
|
||||||
|
```
|
||||||
|
|
||||||
|
WIFI_SSID and WIFI_PASS are your WiFi credentials.
|
||||||
|
|
||||||
|
|
||||||
|
Now you can click on the upload button to flash the ESP.
|
||||||
|
|
||||||
|
![Upload Button](resources/upload.png)
|
||||||
|
|
||||||
|
Use the serial monitor to see the output of the ESP. You will see the IP address of the ESP. You can now access the webserver at `http://<ESP_IP>:80`.
|
||||||
|
Also, the ESP should now be able to send data to the database. Now the data should appear in the database and you should be greeted be able to configure these views:
|
||||||
|
|
||||||
|
![Grafana Dashboard](resources/dashboard.png)
|
||||||
|
![InfluxDB Data](resources/influxdb.jpeg)
|
||||||
|
![ESP32 Webserver](resources/webserver.png)
|
||||||
|
|
||||||
|
|
||||||
Explore the code, contribute, and provide feedback.
|
Explore the code, contribute, and provide feedback.
|
||||||
|
42
docker-compose.yml
Normal file
42
docker-compose.yml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# Docker Compose Stack mit InfluxDB und Grafana für das Projekt "Digitalisierung in der Energietechnik"
|
||||||
|
version: '3.6'
|
||||||
|
services:
|
||||||
|
influxdb:
|
||||||
|
container_name: influxdb-test
|
||||||
|
image: influxdb:2.7.1-alpine
|
||||||
|
ports:
|
||||||
|
- '8087:8086' # InfluxDB wird am Server unter Port 8087 bereitgestellt.
|
||||||
|
volumes:
|
||||||
|
- influxdb-data:/var/lib/influxdb2 # Docker Volume: Hier werden die Daten permanent gespeichert.
|
||||||
|
environment:
|
||||||
|
- DOCKER_INFLUXDB_INIT_MODE=setup
|
||||||
|
- DOCKER_INFLUXDB_INIT_USERNAME=influxUser
|
||||||
|
- DOCKER_INFLUXDB_INIT_PASSWORD=influxUserPW # Sollte geaendert werden.
|
||||||
|
- DOCKER_INFLUXDB_INIT_ORG=org-uni
|
||||||
|
- DOCKER_INFLUXDB_INIT_BUCKET=bucket-digitalisierung
|
||||||
|
- DOCKER_INFLUXDB_INIT_RETENTION=1w # (optional) data retention: Wann werden Daten geloescht?
|
||||||
|
- DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=8gs8r6ZZp@%EGnR@xRpJ@d4sH2M%5m8# # (optional) admin-token definieren (sollte geaendert werden).
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
grafana:
|
||||||
|
image: grafana/grafana
|
||||||
|
container_name: grafana-testserver
|
||||||
|
restart: always
|
||||||
|
depends_on:
|
||||||
|
- influxdb
|
||||||
|
environment:
|
||||||
|
- GF_SECURITY_ADMIN_USER=gfUser
|
||||||
|
- GF_SECURITY_ADMIN_PASSWORD=gfUserPW # Sollte geaendert werden.
|
||||||
|
- GF_INSTALL_PLUGINS=
|
||||||
|
- GF_SERVER_ROOT_URL="https://gf.bjoernellens1.com" # Dashboard derzeit unter dieser Subdomain erreichbar.
|
||||||
|
- GF_SERVER_DOMAIN=gf.bjoernellens1.com
|
||||||
|
links:
|
||||||
|
- influxdb
|
||||||
|
ports:
|
||||||
|
- '3002:3000' # Grafana wird am Server unter Port 3002 bereitgestellt.
|
||||||
|
volumes:
|
||||||
|
- grafana_data:/var/lib/grafana # Docker Volume: Hier werden die Daten permanent gespeichert.
|
||||||
|
|
||||||
|
volumes: # Die Docker Volumes muessen hier definiert werden, werden dann automatisch mit dem Stack erstellt.
|
||||||
|
grafana_data: {}
|
||||||
|
influxdb-data: {}
|
22
platformio.ini
Normal file
22
platformio.ini
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
; 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:az-delivery-devkit-v4]
|
||||||
|
platform = espressif32
|
||||||
|
board = az-delivery-devkit-v4
|
||||||
|
framework = arduino
|
||||||
|
lib_deps =
|
||||||
|
Wire
|
||||||
|
Adafruit INA219
|
||||||
|
ESP Async WebServer
|
||||||
|
ArduinoJson
|
||||||
|
tobiasschuerg/ESP8266 Influxdb@^3.13.1
|
||||||
|
upload_port = COM3
|
||||||
|
monitor_speed = 115200
|
BIN
resources/dashboard.png
Normal file
BIN
resources/dashboard.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 155 KiB |
BIN
resources/influxdb.jpeg
Normal file
BIN
resources/influxdb.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 282 KiB |
BIN
resources/upload.png
Normal file
BIN
resources/upload.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.9 KiB |
BIN
resources/webserver.png
Normal file
BIN
resources/webserver.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
BIN
resources/wiring.png
Normal file
BIN
resources/wiring.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 138 KiB |
16
src/Config.h
Normal file
16
src/Config.h
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// include secrets file.
|
||||||
|
#include "secrets.h"
|
||||||
|
|
||||||
|
#ifndef CONFIG_H
|
||||||
|
#define CONFIG_H
|
||||||
|
|
||||||
|
// InfluxDB config - This part needs to be changed when you are doing the Arduino setup wizard in InfluxDB.
|
||||||
|
const char* INFLUXDB_URL = "https://db.bjoernellens1.com";
|
||||||
|
const char* INFLUXDB_ORG = "48cec24009273b5e";
|
||||||
|
const char* INFLUXDB_BUCKET = "bucket-digitalisierung";
|
||||||
|
|
||||||
|
// Other configurations
|
||||||
|
#define RELAY_PIN 2
|
||||||
|
#define SECOND_RELAY_PIN 15
|
||||||
|
|
||||||
|
#endif // CONFIG_H
|
39
src/INA219Module.cpp
Normal file
39
src/INA219Module.cpp
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// INA219Module.cpp
|
||||||
|
#include "INA219Module.h"
|
||||||
|
|
||||||
|
INA219Module::INA219Module() : _lastEnergyCalculationTime(0), _totalEnergy(0.0), _ina219(INA219_I2C_ADDRESS) {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool INA219Module::begin() {
|
||||||
|
return _ina219.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
float INA219Module::getCurrent() {
|
||||||
|
return _ina219.getCurrent_mA() / 1000.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float INA219Module::getBusVoltage() {
|
||||||
|
return _ina219.getBusVoltage_V();
|
||||||
|
}
|
||||||
|
|
||||||
|
float INA219Module::getPower() {
|
||||||
|
return _ina219.getPower_mW() / 1000.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float INA219Module::calculateAveragePower() {
|
||||||
|
return _ina219.getPower_mW() / 1000.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float INA219Module::getTotalEnergy() {
|
||||||
|
unsigned long currentTime = millis();
|
||||||
|
float elapsedTime = (currentTime - _lastEnergyCalculationTime) / 3600000.0;
|
||||||
|
|
||||||
|
float currentPower = getPower();
|
||||||
|
|
||||||
|
_totalEnergy += currentPower * elapsedTime;
|
||||||
|
|
||||||
|
_lastEnergyCalculationTime = currentTime;
|
||||||
|
|
||||||
|
return _totalEnergy;
|
||||||
|
}
|
||||||
|
|
23
src/INA219Module.h
Normal file
23
src/INA219Module.h
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// INA219Module.h
|
||||||
|
#ifndef INA219Module_h
|
||||||
|
#define INA219Module_h
|
||||||
|
#define INA219_I2C_ADDRESS 0x40
|
||||||
|
|
||||||
|
#include <Adafruit_INA219.h>
|
||||||
|
|
||||||
|
class INA219Module {
|
||||||
|
public:
|
||||||
|
INA219Module();
|
||||||
|
bool begin();
|
||||||
|
float getCurrent();
|
||||||
|
float getBusVoltage();
|
||||||
|
float getPower();
|
||||||
|
float calculateAveragePower();
|
||||||
|
float getTotalEnergy();
|
||||||
|
private:
|
||||||
|
Adafruit_INA219 _ina219;
|
||||||
|
unsigned long _lastEnergyCalculationTime = 0; // Initialisierung der Zeitvariable
|
||||||
|
float _totalEnergy = 0.0; // Initialisierung der Gesamtenergie
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
23
src/InfluxDBModule.cpp
Normal file
23
src/InfluxDBModule.cpp
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// InfluxDBModule.cpp
|
||||||
|
#include "InfluxDBModule.h"
|
||||||
|
|
||||||
|
InfluxDBModule::InfluxDBModule(String url, String org, String bucket, String token)
|
||||||
|
: client(url, org, bucket, token, InfluxDbCloud2CACert) {}
|
||||||
|
|
||||||
|
bool InfluxDBModule::connectToInfluxDB() {
|
||||||
|
return client.validateConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InfluxDBModule::sendData(float current, float busVoltage, float power, float avgPower, float totalEnergy, int relay1State, int relay2State) {
|
||||||
|
Point sensorData("sensor_data");
|
||||||
|
sensorData.addTag("device", "ESP32");
|
||||||
|
sensorData.addField("current", current);
|
||||||
|
sensorData.addField("busVoltage", busVoltage);
|
||||||
|
sensorData.addField("power", power);
|
||||||
|
sensorData.addField("avgPower", avgPower);
|
||||||
|
sensorData.addField("totalEnergy", totalEnergy);
|
||||||
|
sensorData.addField("relay1StateINT", relay1State);
|
||||||
|
sensorData.addField("relay2StateINT", relay2State);
|
||||||
|
|
||||||
|
return client.writePoint(sensorData);
|
||||||
|
}
|
17
src/InfluxDBModule.h
Normal file
17
src/InfluxDBModule.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// InfluxDBModule.h
|
||||||
|
#ifndef InfluxDBModule_h
|
||||||
|
#define InfluxDBModule_h
|
||||||
|
|
||||||
|
#include <InfluxDbClient.h>
|
||||||
|
#include <InfluxDbCloud.h>
|
||||||
|
|
||||||
|
class InfluxDBModule {
|
||||||
|
public:
|
||||||
|
InfluxDBModule(String url, String org, String bucket, String token);
|
||||||
|
bool connectToInfluxDB();
|
||||||
|
bool sendData(float current, float busVoltage, float power, float avgPower, float totalEnergy, int relay1State, int relay2State);
|
||||||
|
private:
|
||||||
|
InfluxDBClient client;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
35
src/WebServerModule.h
Normal file
35
src/WebServerModule.h
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// WebServerModule.h
|
||||||
|
#ifndef WebServerModule_h
|
||||||
|
#define WebServerModule_h
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <AsyncTCP.h>
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
#include "INA219Module.h"
|
||||||
|
|
||||||
|
class WebServerModule {
|
||||||
|
public:
|
||||||
|
WebServerModule(INA219Module& inaModule, int relayPin, int secondRelayPin);
|
||||||
|
|
||||||
|
void begin();
|
||||||
|
bool getRelayState() const;
|
||||||
|
bool getSecondRelayState() const;
|
||||||
|
void handleToggleState(bool state);
|
||||||
|
void handleSecondToggleState(bool state);
|
||||||
|
|
||||||
|
private:
|
||||||
|
INA219Module& _inaModule;
|
||||||
|
AsyncWebServer _server;
|
||||||
|
|
||||||
|
bool relayState;
|
||||||
|
bool secondRelayState;
|
||||||
|
int _relayPin;
|
||||||
|
int _secondRelayPin;
|
||||||
|
|
||||||
|
void handleRoot(AsyncWebServerRequest *request);
|
||||||
|
void handleValues(AsyncWebServerRequest *request);
|
||||||
|
void handleToggle(AsyncWebServerRequest *request);
|
||||||
|
void handleSecondToggle(AsyncWebServerRequest *request);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
175
src/WebserverModule.cpp
Normal file
175
src/WebserverModule.cpp
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
// WebServerModule.cpp
|
||||||
|
#include "WebServerModule.h"
|
||||||
|
|
||||||
|
WebServerModule::WebServerModule(INA219Module& inaModule, int relayPin, int secondRelayPin)
|
||||||
|
: _inaModule(inaModule), _server(80), _relayPin(relayPin), _secondRelayPin(secondRelayPin), relayState(false), secondRelayState(false) {}
|
||||||
|
|
||||||
|
void WebServerModule::begin() {
|
||||||
|
// Konfiguriere die Pins für die Relais als Ausgänge
|
||||||
|
pinMode(_relayPin, OUTPUT);
|
||||||
|
pinMode(_secondRelayPin, OUTPUT);
|
||||||
|
|
||||||
|
// Konfiguriere die Server-Endpunkte
|
||||||
|
_server.on("/", HTTP_GET, std::bind(&WebServerModule::handleRoot, this, std::placeholders::_1));
|
||||||
|
_server.on("/values", HTTP_GET, std::bind(&WebServerModule::handleValues, this, std::placeholders::_1));
|
||||||
|
_server.on("/toggle1", HTTP_GET, std::bind(&WebServerModule::handleToggle, this, std::placeholders::_1));
|
||||||
|
_server.on("/toggle2", HTTP_GET, std::bind(&WebServerModule::handleSecondToggle, this, std::placeholders::_1));
|
||||||
|
|
||||||
|
_server.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebServerModule::handleRoot(AsyncWebServerRequest *request) {
|
||||||
|
String html = "<html><head><style>";
|
||||||
|
// CSS-Stil für die Schalter und Beschriftungen
|
||||||
|
html += ".toggle-container {";
|
||||||
|
html += " display: flex;";
|
||||||
|
html += " align-items: center;";
|
||||||
|
html += " margin-bottom: 10px;";
|
||||||
|
html += "}";
|
||||||
|
html += ".toggle-label {";
|
||||||
|
html += " margin-right: 10px;";
|
||||||
|
html += "}";
|
||||||
|
html += ".toggle {";
|
||||||
|
html += " position: relative;";
|
||||||
|
html += " display: inline-block;";
|
||||||
|
html += " width: 60px;";
|
||||||
|
html += " height: 34px;";
|
||||||
|
html += "}";
|
||||||
|
html += ".toggle input {";
|
||||||
|
html += " opacity: 0;";
|
||||||
|
html += " width: 0;";
|
||||||
|
html += " height: 0;";
|
||||||
|
html += "}";
|
||||||
|
html += ".slider {";
|
||||||
|
html += " position: absolute;";
|
||||||
|
html += " cursor: pointer;";
|
||||||
|
html += " top: 0;";
|
||||||
|
html += " left: 0;";
|
||||||
|
html += " right: 0;";
|
||||||
|
html += " bottom: 0;";
|
||||||
|
html += " background-color: #ccc;";
|
||||||
|
html += " border-radius: 34px;";
|
||||||
|
html += " transition: .4s;";
|
||||||
|
html += "}";
|
||||||
|
html += ".inner-slider {";
|
||||||
|
html += " position: absolute;";
|
||||||
|
html += " content: \"\";";
|
||||||
|
html += " height: 26px;";
|
||||||
|
html += " width: 26px;";
|
||||||
|
html += " left: 4px;";
|
||||||
|
html += " bottom: 4px;";
|
||||||
|
html += " background-color: white;";
|
||||||
|
html += " border-radius: 50%;";
|
||||||
|
html += " transition: .4s;";
|
||||||
|
html += "}";
|
||||||
|
html += "input:checked + .slider {";
|
||||||
|
html += " background-color: #2196F3;";
|
||||||
|
html += "}";
|
||||||
|
html += "input:focus + .slider {";
|
||||||
|
html += " box-shadow: 0 0 1px #2196F3;";
|
||||||
|
html += "}";
|
||||||
|
html += "</style></head><body>";
|
||||||
|
html += "<h1>Energieverbrauchsmonitor</h1>";
|
||||||
|
// Anzeige der Werte
|
||||||
|
html += "<p>Strom: <span id='current_A'></span> A</p>";
|
||||||
|
html += "<p>Spannung: <span id='busVoltage_V'></span> V</p>";
|
||||||
|
html += "<p>Leistung: <span id='power_W'></span> W</p>";
|
||||||
|
html += "<p>Durchschnittliche Leistung: <span id='avgPower_W'></span> W</p>";
|
||||||
|
html += "<p>Gesamtenergie: <span id='totalEnergy'></span> Wh</p>";
|
||||||
|
// JavaScript-Code für die Werteaktualisierung und Schaltersteuerung
|
||||||
|
html += "<script>setInterval(updateValues, 5000);";
|
||||||
|
html += "function updateValues() { fetch('/values').then(response => response.json()).then(data => {";
|
||||||
|
html += "document.getElementById('current_A').textContent = data.current_A;";
|
||||||
|
html += "document.getElementById('busVoltage_V').textContent = data.busVoltage_V;";
|
||||||
|
html += "document.getElementById('power_W').textContent = data.power_W;";
|
||||||
|
html += "document.getElementById('avgPower_W').textContent = data.avgPower_W;";
|
||||||
|
html += "document.getElementById('totalEnergy').textContent = data.totalEnergy;";
|
||||||
|
html += "}); }</script>";
|
||||||
|
// Schalter 1 (K1)
|
||||||
|
html += "<div class='toggle-container'>";
|
||||||
|
html += "<label class='toggle-label'>Relais 1</label>"; // Beschriftung für den ersten Schalter
|
||||||
|
html += "<label class='toggle'>";
|
||||||
|
html += "<input type='checkbox' id='relaySwitch1' onchange='toggleRelay(1, this.checked)'>";
|
||||||
|
html += "<span class='slider'></span>";
|
||||||
|
html += "<span class='inner-slider'></span>";
|
||||||
|
html += "</label>";
|
||||||
|
html += "</div>";
|
||||||
|
// Schalter 2 (K2)
|
||||||
|
html += "<div class='toggle-container'>";
|
||||||
|
html += "<label class='toggle-label'>Relais 2</label>"; // Beschriftung für den zweiten Schalter
|
||||||
|
html += "<label class='toggle'>";
|
||||||
|
html += "<input type='checkbox' id='relaySwitch2' onchange='toggleRelay(2, this.checked)'>";
|
||||||
|
html += "<span class='slider'></span>";
|
||||||
|
html += "<span class='inner-slider'></span>";
|
||||||
|
html += "</label>";
|
||||||
|
html += "</div>";
|
||||||
|
// JavaScript-Code für die Schaltersteuerung
|
||||||
|
html += "<script>";
|
||||||
|
html += "function toggleRelay(relay, state) {";
|
||||||
|
html += " fetch(`/toggle${relay}?state=${Number(state)}`).then(response => response.text()).then(data => {";
|
||||||
|
html += " const innerSlider = document.querySelectorAll('.inner-slider')[relay - 1];"; // Das entsprechende Inner Slider
|
||||||
|
html += " const translation = state ? '26px' : '0';"; // Verschiebungsberechnung
|
||||||
|
html += " innerSlider.style.transform = `translateX(${translation})`;"; // Anwenden der Verschiebung
|
||||||
|
html += " });";
|
||||||
|
html += "}";
|
||||||
|
html += "</script>";
|
||||||
|
html += "</body></html>";
|
||||||
|
request->send(200, "text/html", html);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebServerModule::handleValues(AsyncWebServerRequest *request) {
|
||||||
|
String values = "{\"current_A\":" + String(_inaModule.getCurrent()) + ",";
|
||||||
|
values += "\"busVoltage_V\":" + String(_inaModule.getBusVoltage()) + ",";
|
||||||
|
values += "\"power_W\":" + String(_inaModule.getPower()) + ",";
|
||||||
|
values += "\"avgPower_W\":" + String(_inaModule.calculateAveragePower()) + ",";
|
||||||
|
values += "\"totalEnergy\":" + String(_inaModule.getTotalEnergy()) + "}";
|
||||||
|
request->send(200, "application/json", values);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebServerModule::handleToggle(AsyncWebServerRequest *request) {
|
||||||
|
if (request->hasParam("state")) {
|
||||||
|
int state = request->getParam("state")->value().toInt();
|
||||||
|
relayState = state;
|
||||||
|
digitalWrite(_relayPin, relayState ? HIGH : LOW);
|
||||||
|
|
||||||
|
Serial.print("Relay 1 state set to: ");
|
||||||
|
Serial.println(relayState);
|
||||||
|
|
||||||
|
request->send(200, "text/plain", relayState ? "Relay 1 turned on" : "Relay 1 turned off");
|
||||||
|
} else {
|
||||||
|
request->send(400); // Fehler, keine Zustandsinformation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebServerModule::handleSecondToggle(AsyncWebServerRequest *request) {
|
||||||
|
if (request->hasParam("state")) {
|
||||||
|
int state = request->getParam("state")->value().toInt();
|
||||||
|
secondRelayState = state;
|
||||||
|
digitalWrite(_secondRelayPin, secondRelayState ? HIGH : LOW);
|
||||||
|
|
||||||
|
Serial.print("Relay 2 state set to: ");
|
||||||
|
Serial.println(secondRelayState);
|
||||||
|
|
||||||
|
request->send(200, "text/plain", secondRelayState ? "Relay 2 turned on" : "Relay 2 turned off");
|
||||||
|
} else {
|
||||||
|
request->send(400); // Fehler, keine Zustandsinformation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebServerModule::handleToggleState(bool state) {
|
||||||
|
relayState = state;
|
||||||
|
digitalWrite(_relayPin, relayState ? HIGH : LOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebServerModule::handleSecondToggleState(bool state) {
|
||||||
|
secondRelayState = state;
|
||||||
|
digitalWrite(_secondRelayPin, secondRelayState ? HIGH : LOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WebServerModule::getRelayState() const {
|
||||||
|
return relayState;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WebServerModule::getSecondRelayState() const {
|
||||||
|
return secondRelayState;
|
||||||
|
}
|
36
src/WiFiModule.cpp
Normal file
36
src/WiFiModule.cpp
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#include "WiFiModule.h"
|
||||||
|
|
||||||
|
WiFiModule::WiFiModule(const char* ssid, const char* password) {
|
||||||
|
// Kopiere SSID und Passwort in die Member-Variablen
|
||||||
|
strncpy(_ssid, ssid, sizeof(_ssid) - 1);
|
||||||
|
_ssid[sizeof(_ssid) - 1] = '\0'; // Stelle sicher, dass der String nullterminiert ist
|
||||||
|
|
||||||
|
strncpy(_password, password, sizeof(_password) - 1);
|
||||||
|
_password[sizeof(_password) - 1] = '\0'; // Stelle sicher, dass der String nullterminiert ist
|
||||||
|
}
|
||||||
|
|
||||||
|
void WiFiModule::connect() {
|
||||||
|
WiFi.begin(_ssid, _password);
|
||||||
|
while (WiFi.status() != WL_CONNECTED) {
|
||||||
|
delay(1000);
|
||||||
|
Serial.println("Connecting to WiFi...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WiFiModule::isConnected() {
|
||||||
|
return WiFi.status() == WL_CONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
IPAddress WiFiModule::getLocalIP() {
|
||||||
|
return WiFi.localIP();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WiFiModule::printConnectionInfo() {
|
||||||
|
if (isConnected()) {
|
||||||
|
Serial.println("Connected to WiFi");
|
||||||
|
Serial.print("Local IP: ");
|
||||||
|
Serial.println(getLocalIP());
|
||||||
|
} else {
|
||||||
|
Serial.println("Connection to WiFi failed");
|
||||||
|
}
|
||||||
|
}
|
19
src/WiFiModule.h
Normal file
19
src/WiFiModule.h
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#ifndef WiFiModule_h
|
||||||
|
#define WiFiModule_h
|
||||||
|
|
||||||
|
#include <WiFi.h>
|
||||||
|
|
||||||
|
class WiFiModule {
|
||||||
|
public:
|
||||||
|
WiFiModule(const char* ssid, const char* password);
|
||||||
|
void connect();
|
||||||
|
bool isConnected();
|
||||||
|
IPAddress getLocalIP();
|
||||||
|
void printConnectionInfo();
|
||||||
|
|
||||||
|
private:
|
||||||
|
char _ssid[32]; // Speicher für SSID und Passwort reservieren
|
||||||
|
char _password[32]; // Angenommene Maximalwerte, entsprechend anpassen falls nötig
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
120
src/main.cpp
Normal file
120
src/main.cpp
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include "WiFiModule.h"
|
||||||
|
#include <Adafruit_INA219.h>
|
||||||
|
#include <Adafruit_BusIO_Register.h>
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
#include "INA219Module.h"
|
||||||
|
#include "InfluxDBModule.h"
|
||||||
|
#include "WebServerModule.h"
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <InfluxDbClient.h>
|
||||||
|
#include <InfluxDbCloud.h>
|
||||||
|
#include "Config.h"
|
||||||
|
|
||||||
|
#define DEVICE "ESP32"
|
||||||
|
|
||||||
|
WiFiModule wifiModule(WIFI_SSID, WIFI_PASSWORD);
|
||||||
|
INA219Module inaModule;
|
||||||
|
InfluxDBModule influxDB(INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN);
|
||||||
|
WebServerModule webServer(inaModule, RELAY_PIN, SECOND_RELAY_PIN);
|
||||||
|
|
||||||
|
void readAndSendSensorData();
|
||||||
|
void controlRelays();
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial.println("... init ...");
|
||||||
|
Wire.begin();
|
||||||
|
Serial.println("Wire initialized");
|
||||||
|
|
||||||
|
wifiModule.connect();
|
||||||
|
Serial.println("WiFi connected");
|
||||||
|
wifiModule.printConnectionInfo();
|
||||||
|
Serial.println("WiFi info printed");
|
||||||
|
|
||||||
|
if (!inaModule.begin()) {
|
||||||
|
Serial.println("INA219 sensor not found");
|
||||||
|
while (1) {
|
||||||
|
delay(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Serial.println("INA219 sensor found");
|
||||||
|
|
||||||
|
if (!influxDB.connectToInfluxDB()) {
|
||||||
|
Serial.println("Failed to connect to InfluxDB");
|
||||||
|
}
|
||||||
|
Serial.println("Connected to InfluxDB");
|
||||||
|
|
||||||
|
webServer.begin();
|
||||||
|
Serial.println("Web server started");
|
||||||
|
|
||||||
|
Serial.println();
|
||||||
|
Serial.println("......");
|
||||||
|
Serial.println();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
readAndSendSensorData();
|
||||||
|
controlRelays();
|
||||||
|
delay(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void readAndSendSensorData() {
|
||||||
|
float current = inaModule.getCurrent();
|
||||||
|
float busVoltage = inaModule.getBusVoltage();
|
||||||
|
float power = inaModule.getPower();
|
||||||
|
float avgPower = inaModule.calculateAveragePower();
|
||||||
|
float totalEnergy = inaModule.getTotalEnergy();
|
||||||
|
bool relay1State = webServer.getRelayState();
|
||||||
|
bool relay2State = webServer.getSecondRelayState();
|
||||||
|
// Ändere die bool's zu Float-Werten
|
||||||
|
int relay1StateInt = relay1State ? 1 : 0;
|
||||||
|
int relay2StateInt = relay2State ? 1 : 0;
|
||||||
|
|
||||||
|
influxDB.sendData(current, busVoltage, power, avgPower, totalEnergy, relay1StateInt , relay2StateInt );
|
||||||
|
|
||||||
|
Serial.print("current: ");
|
||||||
|
Serial.print(current);
|
||||||
|
Serial.println(" A");
|
||||||
|
|
||||||
|
Serial.print("busVoltage: ");
|
||||||
|
Serial.print(busVoltage);
|
||||||
|
Serial.println(" V");
|
||||||
|
|
||||||
|
Serial.print("power: ");
|
||||||
|
Serial.print(power);
|
||||||
|
Serial.println(" W");
|
||||||
|
|
||||||
|
Serial.print("avgPower: ");
|
||||||
|
Serial.print(avgPower);
|
||||||
|
Serial.println(" W");
|
||||||
|
|
||||||
|
Serial.print("totalEnergy: ");
|
||||||
|
Serial.print(totalEnergy);
|
||||||
|
Serial.println(" Wh");
|
||||||
|
|
||||||
|
Serial.println();
|
||||||
|
Serial.println("... waiting .... ");
|
||||||
|
Serial.println();
|
||||||
|
}
|
||||||
|
|
||||||
|
void controlRelays() {
|
||||||
|
float power = inaModule.getPower();
|
||||||
|
float busVoltage = inaModule.getBusVoltage();
|
||||||
|
|
||||||
|
// Zentrale Logik für die Relaissteuerung
|
||||||
|
if (power > 70.0) {
|
||||||
|
Serial.println("Turning on relays...");
|
||||||
|
webServer.handleToggleState(false); // Relay 1 ausschalten
|
||||||
|
webServer.handleSecondToggleState(false); // Relay 2 ausschalten
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zusatz: Relais einschalten, wenn die Spannung zwischen 0 und 14V liegt
|
||||||
|
if (busVoltage > 0.0 && busVoltage < 14.0) {
|
||||||
|
Serial.println("Turning on relays due to voltage condition...");
|
||||||
|
webServer.handleToggleState(true); // Relay 1 einschalten
|
||||||
|
webServer.handleSecondToggleState(true); // Relay 2 einschalten
|
||||||
|
}
|
||||||
|
}
|
9
src/secrets.h.example
Normal file
9
src/secrets.h.example
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// need to move all passwords and tokens into this file for safety
|
||||||
|
// this file is not tracked by git
|
||||||
|
|
||||||
|
// WiFi credentials - change this to your WiFi credentials.
|
||||||
|
const char* WIFI_SSID = "<SSID>";
|
||||||
|
const char* WIFI_PASSWORD = "<PASSWORD>";
|
||||||
|
|
||||||
|
// InfluxDB credentials - change this to your InfluxDB credentials.
|
||||||
|
const char* INFLUXDB_TOKEN = "<YOUR_SECRET_TOKEN>";
|
Loading…
Reference in New Issue
Block a user