Moved the documentation into docs folder and removed folders that should not belont to this repository
|
@ -1,38 +0,0 @@
|
|||
# Grafana
|
||||
|
||||
Grafana is an open source analytics and interactive visualization tool. It provides charts, graphs, and alerts for the web when connected to supported data sources.
|
||||
|
||||
As a visualization tool, Grafana is a popular component in monitoring stacks, often used in combination with time series databases such as InfluxDB.
|
||||
|
||||
## Connection
|
||||
|
||||
To connect Grafana to our Influx-DB, you have to create a data source.
|
||||
|
||||
The `URL`of our InfluxDB is `http://influxdb:8086`.
|
||||
|
||||
In InfluxDB you have to create a `token` to connect: [Load Data -> API Tokens](http://localhost:8086/orgs/721027680173bf2f/load-data/tokens).
|
||||
|
||||
![Influx Create Token](../flow/docs/images/influx-create-token.png)
|
||||
|
||||
You can use this token to [create a connection from Grafana to Influx-DB](http://localhost:3000/datasources/).
|
||||
|
||||
![Connection](./docs/images/database-connection.png)
|
||||
|
||||
After having a connection to a database you can easily create an own dashboard in Grafana.
|
||||
|
||||
Here's the demo snippet (directly copyied from Influx Data Explorer) and the screen shot.
|
||||
|
||||
```
|
||||
from(bucket: "test")
|
||||
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|
||||
|> filter(fn: (r) => r["_measurement"] == "msg")
|
||||
|> filter(fn: (r) => r["_field"] == "value")
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "mean")
|
||||
```
|
||||
|
||||
![Example Dashboard](./docs/images/grafana-example-dashboard.png)
|
||||
|
||||
## CSV Import
|
||||
|
||||
See [CSV Import](./docs/csv-import.md).
|
|
@ -1,30 +0,0 @@
|
|||
# CSV Import
|
||||
|
||||
## Grafana
|
||||
|
||||
In Grafana there is a [csv-datasource plugin](https://grafana.github.io/grafana-csv-datasource/). This plug lets you visualize data from any URL that returns CSV data, such as REST APIs or static file servers. You can even load data from a local file path. But it is *not* importing CSV from Grafana into a data source (like InfluxDB) or uploading CSV data to a local file system.
|
||||
|
||||
> For visualizing CSV data this is enough. Why should you duplicate data if the true data source is a CSV file located somewhere in the internet. If the CSV is not online, this might be another story.
|
||||
|
||||
I added this plugin to our Grafana installation, so it's ready to be used.
|
||||
|
||||
> I just mounted the whole [Grafana directory](../grafana/) into our Docker-Compose setup. Maybe we have to come up with a better solution.
|
||||
|
||||
![Mounted directory](./images/mounted-grafana-directory.png)
|
||||
|
||||
Garafana is running on [localhost on port 3000 with credentials admin:admin](http://localhost:3000). So let's get there to add the CSV file as a usable data source.
|
||||
|
||||
> I added the CSV file to this repository and mounted it into our Docker-Compose setup. So the CSV file is also available in Grafana. Maybe we have to come up with a better solution..
|
||||
|
||||
As the CSV is reachable by Grafana, you can create a data source, there.
|
||||
|
||||
![Mounting a CSV file](./images/grafana-csv-data-source.png)
|
||||
|
||||
> grafana.ini is also mounted from [this repository](../grafana.ini). So local data mode is enabled.
|
||||
|
||||
Whith Grafana having access to data in the CSV file, it's easy to access this data to create a dashboard.
|
||||
|
||||
![Using CSV data](./images/grafana-csv-data.png)
|
||||
![Defining types](./images/grafana-csv-data-type.png)
|
||||
![Two boards in Grafana](./images/grafana-two-boards.png)
|
||||
|
Before Width: | Height: | Size: 112 KiB |
Before Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 85 KiB |
Before Width: | Height: | Size: 207 KiB |
Before Width: | Height: | Size: 214 KiB |
Before Width: | Height: | Size: 117 KiB |
Before Width: | Height: | Size: 44 KiB |
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"levels": [
|
||||
{},
|
||||
{
|
||||
"m": 33554432,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 33554432,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 67108864,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 134217728,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 268435456,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 536870912,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 1073741824,
|
||||
"k": 6
|
||||
}
|
||||
],
|
||||
"files": [
|
||||
"L0-00000001.tsl"
|
||||
],
|
||||
"version": 1
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"levels": [
|
||||
{},
|
||||
{
|
||||
"m": 33554432,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 33554432,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 67108864,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 134217728,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 268435456,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 536870912,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 1073741824,
|
||||
"k": 6
|
||||
}
|
||||
],
|
||||
"files": [
|
||||
"L0-00000001.tsl"
|
||||
],
|
||||
"version": 1
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"levels": [
|
||||
{},
|
||||
{
|
||||
"m": 33554432,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 33554432,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 67108864,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 134217728,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 268435456,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 536870912,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 1073741824,
|
||||
"k": 6
|
||||
}
|
||||
],
|
||||
"files": [
|
||||
"L0-00000001.tsl"
|
||||
],
|
||||
"version": 1
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"levels": [
|
||||
{},
|
||||
{
|
||||
"m": 33554432,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 33554432,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 67108864,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 134217728,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 268435456,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 536870912,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 1073741824,
|
||||
"k": 6
|
||||
}
|
||||
],
|
||||
"files": [
|
||||
"L0-00000001.tsl"
|
||||
],
|
||||
"version": 1
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"levels": [
|
||||
{},
|
||||
{
|
||||
"m": 33554432,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 33554432,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 67108864,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 134217728,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 268435456,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 536870912,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 1073741824,
|
||||
"k": 6
|
||||
}
|
||||
],
|
||||
"files": [
|
||||
"L0-00000001.tsl"
|
||||
],
|
||||
"version": 1
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"levels": [
|
||||
{},
|
||||
{
|
||||
"m": 33554432,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 33554432,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 67108864,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 134217728,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 268435456,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 536870912,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 1073741824,
|
||||
"k": 6
|
||||
}
|
||||
],
|
||||
"files": [
|
||||
"L0-00000001.tsl"
|
||||
],
|
||||
"version": 1
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"levels": [
|
||||
{},
|
||||
{
|
||||
"m": 33554432,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 33554432,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 67108864,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 134217728,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 268435456,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 536870912,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 1073741824,
|
||||
"k": 6
|
||||
}
|
||||
],
|
||||
"files": [
|
||||
"L0-00000001.tsl"
|
||||
],
|
||||
"version": 1
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"levels": [
|
||||
{},
|
||||
{
|
||||
"m": 33554432,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 33554432,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 67108864,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 134217728,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 268435456,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 536870912,
|
||||
"k": 6
|
||||
},
|
||||
{
|
||||
"m": 1073741824,
|
||||
"k": 6
|
||||
}
|
||||
],
|
||||
"files": [
|
||||
"L0-00000001.tsl"
|
||||
],
|
||||
"version": 1
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
#!/bin/sh
|
||||
echo Publishing random values to topic '/iot-platform/energy-monitor/test-device/watt'
|
||||
while true
|
||||
do
|
||||
mosquitto_pub -h localhost -p 1883 -t '/iot-platform/energy-monitor/test-device/watt' -m $((1 + $RANDOM % 10 * 500))
|
||||
printf '%s' "."
|
||||
sleep 1
|
||||
done
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
SCT-013 Sensor - Power meassurement, based on Thomas Edlinger's code for "www.edistechlab.com"
|
||||
Required libraries (Tools -> manage libraries)
|
||||
- EmonLib libary V1.1.0 by OpenEnergyMonitor
|
||||
Based on EmonLibrary examples openenergymonitor.org, Licence GNU GPL V3
|
||||
*/
|
||||
|
||||
#include "EmonLib.h"
|
||||
EnergyMonitor emon1;
|
||||
const byte current1Pin = A1; // ADC-PIN
|
||||
const byte voltage = 230; // Power voltage in Europe = 230 V
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
analogReadResolution(ADC_BITS); // activate 12 Bit resolution for our ESP32
|
||||
emon1.current(current1Pin, 8); // Pin and Calibration
|
||||
}
|
||||
|
||||
void loop() {
|
||||
double Irms = emon1.calcIrms(1480);
|
||||
Serial.print(Irms*voltage);
|
||||
Serial.print(" Watt - ");
|
||||
Serial.print(Irms);
|
||||
Serial.println(" Ampere");
|
||||
delay(1000);
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
/*
|
||||
Required libraries (Tools -> manage libraries)
|
||||
- EmonLib@1.1.0
|
||||
- PubSubClient@2.8.0
|
||||
- WiFi.h - esp32 Wifi support
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <EmonLib.h>
|
||||
#include <WiFi.h>
|
||||
#include <PubSubClient.h>
|
||||
#include "environment.h" // put your credentials and configuration in, here
|
||||
|
||||
// sensor
|
||||
EnergyMonitor emon1;
|
||||
const byte current1Pin = A1; // ADC-PIN
|
||||
const byte voltage = 230; // Power voltage in Europe = 230 V
|
||||
|
||||
// refrences from environment.h
|
||||
const char* ssid = secrect_ssid;
|
||||
const char* password = secret_password;
|
||||
const char* mqttServer = mqtt_server;
|
||||
const int mqttPort = mqtt_port;
|
||||
const char* mqttPrefix = mqtt_prefix;
|
||||
|
||||
// wifi and MQTT
|
||||
WiFiClient wifiClient;
|
||||
PubSubClient client(wifiClient);
|
||||
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Serial.setTimeout(500);
|
||||
setup_energy_sensor();
|
||||
setup_wifi();
|
||||
setup_mqtt();
|
||||
}
|
||||
|
||||
void setup_energy_sensor() {
|
||||
analogReadResolution(ADC_BITS); // activate 12 Bit resolution for our ESP32
|
||||
emon1.current(current1Pin, 8); // Pin and Calibration
|
||||
}
|
||||
|
||||
void setup_wifi() {
|
||||
delay(10);
|
||||
Serial.println();
|
||||
Serial.print("Connecting to ");
|
||||
Serial.println(ssid);
|
||||
WiFi.begin(ssid, password);
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
randomSeed(micros());
|
||||
Serial.println("");
|
||||
Serial.println("WiFi connected");
|
||||
Serial.println("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
}
|
||||
|
||||
void setup_mqtt() {
|
||||
client.setServer(mqttServer, mqttPort);
|
||||
// Loop until we're reconnected
|
||||
while (!client.connected()) {
|
||||
Serial.print("Attempting MQTT connection...");
|
||||
// Create a random client ID
|
||||
String clientId = "ESP32Client-";
|
||||
clientId += String(random(0xffff), HEX);
|
||||
// Attempt to connect
|
||||
if (client.connect(clientId.c_str())) {
|
||||
Serial.println("connected");
|
||||
//Once connected, publish an announcement...
|
||||
client.publish(concat(mqttPrefix, "/status"), "online");
|
||||
// ... and resubscribe
|
||||
} else {
|
||||
Serial.print("failed, rc=");
|
||||
Serial.print(client.state());
|
||||
Serial.println(" try again in 5 seconds");
|
||||
// Wait 5 seconds before retrying
|
||||
delay(5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
char* concat(const char* str1, const char* str2) {
|
||||
char* result;
|
||||
asprintf(&result, "%s%s", str1, str2);
|
||||
return result;
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// get values
|
||||
double irms = emon1.calcIrms(1480);
|
||||
double power = irms * voltage;
|
||||
|
||||
// debug, print out on serial
|
||||
Serial.print(power);
|
||||
Serial.print(" Watt - ");
|
||||
Serial.print(irms);
|
||||
Serial.println(" Ampere");
|
||||
|
||||
// convert double to char array for MQTT
|
||||
char powerArray[10];
|
||||
snprintf(powerArray, 10, "%f", power);
|
||||
char irmsArray[10];
|
||||
snprintf(irmsArray, 10, "%f", irms);
|
||||
|
||||
|
||||
// publish values
|
||||
client.publish(concat(mqttPrefix, "/watt"), powerArray);
|
||||
client.publish(concat(mqttPrefix, "/ampere"), irmsArray);
|
||||
|
||||
// wait a second
|
||||
delay(1000);
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
// Replace with your network credentials
|
||||
#define secrect_ssid "Guest"
|
||||
#define secret_password "guestguest"
|
||||
#define mqtt_server "192.168.1.233"
|
||||
#define mqtt_port 1883
|
||||
#define mqtt_prefix "/iot-platform/energy-monitor/test-device"
|
|
@ -1,151 +0,0 @@
|
|||
# Energy Monitor
|
||||
|
||||
Our energy monitor is based on the openenergymonitor.org project (Licence GNU GPL V3).
|
||||
|
||||
It uses our HelTec Wireless Stick. This - of course - can be replaced by a cheaper ESP32 module.
|
||||
|
||||
Power Measurement is done by a SCT013 clamp (100A:50mA).
|
||||
|
||||
## Build the hardware
|
||||
|
||||
### Used materials
|
||||
|
||||
* ESP32 module (e.g. Heltec Wireless Stick)
|
||||
* 3 x SCT-013-100 (100 A), see: http://openenergymonitor.org/emon/node/156
|
||||
* 6 x 10 kOhm Resistors 1/4 W
|
||||
* 1 x 22 Ohm Resistor 1/4 W (TODO: I am using a 47 Ohm Resistor to be replaced)
|
||||
* 3 x 10 uF Elko 10 V
|
||||
* 3 x 3,5 mm audio jack connector
|
||||
|
||||
### PinOut
|
||||
|
||||
![PinOut](https://resource.heltec.cn/download/Wireless_Stick_V3/HTIT-WS_V3.png "PinOut")
|
||||
|
||||
We use A1, A2 and A3 because they are free (most ADCs are already used on the HelTec Board)
|
||||
|
||||
### Sensors
|
||||
|
||||
The SCT-013 sensors are small current transformers (SCT). They have a ferromagnetic core that can be opened and in which we can enclose our conductor. This conductor is the primary winding and the secondary winding is fixed in the sensor and can have 2000 turns. This gives us a ratio of 1:2000 as an example.
|
||||
|
||||
When AC current flows through the conductor, a magnetic flux is generated in the ferromagnetic core, which in turn generates an electric current in the secondary winding.
|
||||
|
||||
I could not meassure "small" power consumptions (like a LED lamp or a light stripe, as the magnetix flux in the ferromagnet core seems to be too small).
|
||||
|
||||
![clamp on wire](./docs/images/clamp1.jpeg "clamp on a wire")
|
||||
|
||||
I was able to measure high loads (like a heater the can be switched between 1 kW and 2 kW).
|
||||
|
||||
![heater](./docs/images/example-heater.png "serial out of a heater")
|
||||
|
||||
Make sure the clamp is *always positioned towards the consumer*, otherwise it does *not* work. There is a small arrow on the case.
|
||||
|
||||
![point the clamp](./docs/images/clamp2.jpeg "point the clamp")
|
||||
|
||||
> Attention: I could not measure any meaningful values on the "cable". I had to go to the wire.
|
||||
|
||||
![cable](./docs/images/clamp3.jpeg "use the clamp on the wire, not on the cable")
|
||||
|
||||
### Breadboard
|
||||
|
||||
Let's start with a simple breadboard layout.
|
||||
|
||||
![Breadboard](./docs/images/breadboard.png "breakboard layout")
|
||||
![Photo of breadboard](./docs/images/photo-breadboard.jpeg "photo of breadboard")
|
||||
|
||||
To understand this, have a look at this plan:
|
||||
|
||||
![Plan](./docs/images/plan.png "plan")
|
||||
|
||||
R1 & R2 are a voltage divider that provides the 1.65 V source. We use 10 kΩ for mains powered monitors. If we want to run on batteries, we have to choose differnt ones (like 470 kΩ resistors to keep the power consumption to a minimum).
|
||||
|
||||
Capacitor C1 has a low reactance - a few hundred ohms - and provides a path for the alternating current to bypass the resistor. A value of 10 μF is suitable.
|
||||
|
||||
R3 is the burden resistor. Ideal burden would be 19 Ω. As this is not a common value, you could choose 18 Ω or 22 Ω (I am still using a 47 Ω restistor, that has to be replaced).
|
||||
|
||||
See the Fritzing file for [details](./energy-monitor/energy-monitor.fzz).
|
||||
|
||||
## Code
|
||||
|
||||
### Print to serial out
|
||||
|
||||
Start with a simple code that just prints the values. The code is quite simple, as we can use the existing *[EmonLib libary V1.1.0 by OpenEnergyMonitor](https://docs.openenergymonitor.org/electricity-monitoring/ct-sensors/)*.
|
||||
|
||||
[Check out the small amount of code to print the values to serial out.](./01-energy-monitor-serial-out/) This piece of code is based on on Thomas Edlinger's code for [Edi's Tech Lab](https://www.edistechlab.com).
|
||||
|
||||
The only interesting part is this line:
|
||||
|
||||
```C
|
||||
emon1.current(current1Pin, 8); // Pin and Calibration
|
||||
```
|
||||
|
||||
The [calibration](https://docs.openenergymonitor.org/electricity-monitoring/ctac/calibration.html) value "8" was done with a Fluke multimeter (and maybe a not so ideal burden resistor).
|
||||
|
||||
The code just prints the current power consumption to serial out:
|
||||
|
||||
```
|
||||
16:28:18.915 -> 2853.16 Watt - 12.41 Ampere
|
||||
16:28:19.998 -> 2854.63 Watt - 12.41 Ampere
|
||||
16:28:21.119 -> 2850.93 Watt - 12.40 Ampere
|
||||
16:28:22.207 -> 1702.19 Watt - 7.40 Ampere
|
||||
16:28:23.289 -> 400.62 Watt - 1.74 Ampere
|
||||
16:28:24.367 -> 94.42 Watt - 0.41 Ampere
|
||||
```
|
||||
### Post to MQTT
|
||||
|
||||
#### Boot up MQTT
|
||||
|
||||
First, boot your local server infrastructure:
|
||||
|
||||
```sh
|
||||
docker-compose --file software/container/docker-compose.yml up
|
||||
```
|
||||
|
||||
#### Credentials
|
||||
|
||||
To connect to your Wifi and access your MQTT server you have to add this to an `environment` [header file](./02-energy-monitor-mqtt/environment.h):
|
||||
|
||||
```C
|
||||
// Replace with your network credentials
|
||||
#define secrect_ssid "Guest"
|
||||
#define secret_password "guestguest"
|
||||
#define mqtt_server "192.168.2.103"
|
||||
#define mqtt_port 1883
|
||||
#define mqtt_prefix "/iot-platform/energy-monitor/test-device"
|
||||
```
|
||||
|
||||
The `mqtt_server` in tis example posts to my local IP adress. The Wifi network is a `Guest` network I just created for this test.
|
||||
|
||||
The `mqtt_prefix` should be different per device, as this is the topic prefix used to identify the device.
|
||||
|
||||
### Testing
|
||||
|
||||
You can subscribe to your local MQTT server and subscribe to all or just the interesting topics:
|
||||
|
||||
```sh
|
||||
mosquitto_sub -h localhost -t '#' -p 1883 #all
|
||||
mosquitto_sub -h localhost -t '/iot-platform/energy-monitor/test-device/ampere' -p 1883 #current
|
||||
mosquitto_sub -h localhost -t '/iot-platform/energy-monitor/test-device/watt' -p 1883 #power
|
||||
```
|
||||
|
||||
### Interesting code blocks
|
||||
|
||||
Posting to MQTT is quite simple. After setting up Wifi and connection to the MQTT server, it's just a few lines of code:
|
||||
|
||||
```C
|
||||
client.publish(concat(mqttPrefix, "/watt"), powerArray);
|
||||
client.publish(concat(mqttPrefix, "/ampere"), irmsArray);
|
||||
```
|
||||
|
||||
Have a look at the complete [example](./02-energy-monitor-mqtt/).
|
||||
|
||||
## Simulator
|
||||
|
||||
If you just need random inputs (without using the actual hardware), you can simply modify my short [shell script](./00-simulator/).
|
||||
|
||||
## Links
|
||||
|
||||
* A very comprehensive project to build an energy monitor can be found in the [ESP32 + ESPHome Open Source Energy Monitor project by Daniel BP](https://github.com/danpeig/ESP32EnergyMonitor).
|
||||
* A nice (German) [video tutorial can be found at Eddie's Techlab](https://edistechlab.com/sct013-sensor-zum-wechselstrom-messen/).
|
||||
* Have a look at the [complete documentation of the Open Energy Monitor project](https://docs.openenergymonitor.org/).
|
||||
* There is also a German [example project](http://www.technik-fan.de/index.php/Open_Energy_Monitor_mit_dem_ESP32) (that currently cannot be reached over TLS, so be careful before clicking this link).
|
||||
* MQTT and ESP32 is described in this article ["How to Connect ESP32 to MQTT Broker"](https://iotdesignpro.com/projects/how-to-connect-esp32-mqtt-broker).
|
Before Width: | Height: | Size: 232 KiB |
Before Width: | Height: | Size: 280 KiB |
Before Width: | Height: | Size: 212 KiB |
Before Width: | Height: | Size: 159 KiB |
Before Width: | Height: | Size: 151 KiB |
Before Width: | Height: | Size: 148 KiB |
Before Width: | Height: | Size: 122 KiB |
|
@ -1,100 +0,0 @@
|
|||
# Plant Monitor
|
||||
|
||||
## Existing project
|
||||
|
||||
We use our existing plant monitor for this project.
|
||||
|
||||
It can be found in this Git instance: [Plant sensor workshop](https://code.curious.bio/curious.bio/pflanzensensor-workshop)
|
||||
|
||||
## Tasmota
|
||||
|
||||
[Tasmota](https://tasmota.github.io/docs/) is an alternative Firmware for ESP8266. It's easy to use with it's graphical user interface.
|
||||
|
||||
You can flash Tasmota right from the browser using the [Tasmota Web Installer](https://tasmota.github.io/).
|
||||
|
||||
![Flash it](./docs/images/01-flash-1.png)
|
||||
|
||||
You have to connect your device through USB and select the right port. Exisisting firmware will be delete.
|
||||
|
||||
![Erase everything](./docs/images/02-flash-2.png)
|
||||
|
||||
After Tasmota has been flashed to your ESP, you can already set up your wifi.
|
||||
|
||||
![Set up Wifi](./docs/images/03-wifi.png)
|
||||
|
||||
After your device is connected to Wifi, you can switch over to the web UI of your device. Yes, your device now runs an embedded web server. There you can configure your device.
|
||||
|
||||
![Embedded web server](./docs/images/04-web-ui.png)
|
||||
|
||||
### Configure
|
||||
|
||||
Using this Web UI you can setup up your device: `Configure -> Configure Module`
|
||||
|
||||
Our Wemos D1 mini clone is a `generic device with 18 ports`. We have a DHT11 connected to D1 and an analogue measurement on A0.
|
||||
|
||||
![Basic configuration](./docs/images/05-configuration.png)
|
||||
|
||||
|
||||
After configuring it this way, we can see you data in the web UI.
|
||||
|
||||
![Flash it](./docs/images/06-overview.png)
|
||||
|
||||
### MQTT
|
||||
|
||||
Tasmota's main protocol is MQTT. You can setup MQTT under `Configuration -> MQTT`.
|
||||
|
||||
![MQTT](./docs/images/07-setup-mqtt.png)
|
||||
|
||||
To send data more frequent (nice for debugging) you have to change the telemetry period to a lower level (than 300 s / 5 min). I'd went for 10 s.
|
||||
|
||||
![Telemetry interval](./docs/images/08-configure-telemetry-interval.png)
|
||||
|
||||
Then our device will send data like this:
|
||||
|
||||
```
|
||||
{
|
||||
"Time": "2023-02-26T17:19:55",
|
||||
"ANALOG": {
|
||||
"A0": 6
|
||||
},
|
||||
"DHT11": {
|
||||
"Temperature": 19,
|
||||
"Humidity": 44,
|
||||
"DewPoint": 6.4
|
||||
},
|
||||
"TempUnit": "C"
|
||||
}
|
||||
|
||||
```
|
||||
You can subscribe to this MQTT event in Node-RED.
|
||||
|
||||
![Node-RED](./docs/images/08-node-red-overview.png)
|
||||
|
||||
There is a small converter function, so we only store the interesting values in InfluxDB.
|
||||
|
||||
```
|
||||
return {
|
||||
payload: {
|
||||
temperature: Number(msg.payload.DHT11.Temperature),
|
||||
humidity: Number(msg.payload.DHT11.Humidity)
|
||||
}
|
||||
};
|
||||
```
|
||||
We can query this data in InfluxDB.
|
||||
|
||||
![InfluxDB](./docs/images/09-influx.png)
|
||||
|
||||
This is our query.
|
||||
|
||||
```
|
||||
from(bucket: "plant")
|
||||
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|
||||
|> filter(fn: (r) => r["_measurement"] == "msg")
|
||||
|> filter(fn: (r) => r["_field"] == "temperature" or r["_field"] == "humidity")
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "mean")
|
||||
```
|
||||
|
||||
We can use this query to create a dashboard in Grafana.
|
||||
![InfluxDB](./docs/images/10-grafana.png)
|
||||
|
Before Width: | Height: | Size: 115 KiB |
Before Width: | Height: | Size: 122 KiB |
Before Width: | Height: | Size: 129 KiB |
Before Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 181 KiB |
Before Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 127 KiB |
Before Width: | Height: | Size: 113 KiB |
Before Width: | Height: | Size: 112 KiB |
Before Width: | Height: | Size: 456 KiB |
Before Width: | Height: | Size: 312 KiB |
|
@ -1,127 +0,0 @@
|
|||
# Shelly Plug (S)
|
||||
|
||||
Shelly Plugs S are quite cheap but relatively accurate to measure power consumptions less than 2.5 kW.
|
||||
|
||||
![Shelly Plug](https://shelly.hr/wp-content/uploads/2020/11/shelly_plug_s_1-1.jpg)
|
||||
|
||||
## Flash Tasmota
|
||||
|
||||
There's an OpenSource project to flash Tasmota on Shelly Plugs: [mg2x](https://github.com/arendst/mgos-to-tasmota)
|
||||
|
||||
Locate your Shellie's IP adress (in my case: 192.168.2.150) and update it "over the air" with the Tasmota firmware:
|
||||
|
||||
http://192.168.2.150/ota?url=http://ota.tasmota.com/tasmota/shelly/mg2tasmota-ShellyPlugS.zip
|
||||
|
||||
Your Shelly will return a JSON object that looks like that:
|
||||
|
||||
```
|
||||
{
|
||||
"status": "updating",
|
||||
"has_update": false,
|
||||
"new_version": "20230109-114426/v1.12.2-g32055ee",
|
||||
"old_version": "20230109-114426/v1.12.2-g32055ee"
|
||||
}
|
||||
```
|
||||
|
||||
After a while your Shelly Plug S should be flashed with Tasmota firmware.
|
||||
|
||||
> Just be patient. This took longer than five minutes in my DSL connected network.
|
||||
|
||||
The Shelly Plus S will create create a new Wifi.
|
||||
|
||||
![Tasmota Wifi](./docs/images/wifi.png)
|
||||
|
||||
Join that Wifi and configure the device: http://192.164.4.1/
|
||||
|
||||
![Join Wifi](./docs/images/configure-wifi.png)
|
||||
|
||||
You can configure it as a BlitzWolf SHP product.
|
||||
|
||||
Then it offers you power measurement and a programmable toogle.
|
||||
|
||||
![BlitzWolf](./docs/images/blitzwolf.png)
|
||||
|
||||
It should be configurable just like our [plant monitor](../plant-monitor/README.md).
|
||||
|
||||
Just enable MQTT and enter a shorter telemetry period.
|
||||
|
||||
![MQTT](./docs/images/mqtt.png) ![Telemetry period](./docs/images/telemetry-period.png)
|
||||
|
||||
It will post MQTT messages unter a topic `tele/tasmota_891E97/SENSOR` like this one:
|
||||
|
||||
```
|
||||
{
|
||||
"Time": "2023-02-27T16:45:07",
|
||||
"ENERGY": {
|
||||
"TotalStartTime": "2023-02-27T16:33:06",
|
||||
"Total": 0.004,
|
||||
"Yesterday": 0,
|
||||
"Today": 0.004,
|
||||
"Period": 0,
|
||||
"Power": 34,
|
||||
"ApparentPower": 44,
|
||||
"ReactivePower": 27,
|
||||
"Factor": 0.79,
|
||||
"Voltage": 253,
|
||||
"Current": 0.172
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We now can consume this messages in Node-RED, store them in InfluxDB and build a dashboard in Grafana.
|
||||
|
||||
### InfluxDB Bucket
|
||||
|
||||
I created a bucket called `shelly`in InfluxDB, so we can store the messages in this bucket.
|
||||
|
||||
### Node-RED
|
||||
|
||||
I create a usual flow in Node-RED. A MQTT node fetches the values.
|
||||
|
||||
![Node-RED](./docs/images/node-red.png)
|
||||
|
||||
The message is fed into a filter function to only store usefull information:
|
||||
|
||||
```
|
||||
return {
|
||||
payload: {
|
||||
power: Number(msg.payload.ENERGY.Power),
|
||||
voltage: Number(msg.payload.ENERGY.Voltage),
|
||||
current: Number(msg.payload.ENERGY.Current)
|
||||
}
|
||||
};
|
||||
````
|
||||
|
||||
The `payload` will be stored in InfluxDB in the bucket "shelly".
|
||||
|
||||
### InfluxDB Data Explorer
|
||||
|
||||
In Influx DB Data Explorer you can query the stored data.
|
||||
|
||||
![Data Explorer](./docs/images/data-explorer.png)
|
||||
|
||||
The query created by Data Explorer looks like that:
|
||||
|
||||
```
|
||||
from(bucket: "shelly")
|
||||
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|
||||
|> filter(fn: (r) => r["_measurement"] == "msg")
|
||||
|> filter(fn: (r) => r["_field"] == "power" or r["_field"] == "voltage" or r["_field"] == "current")
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "mean")
|
||||
```
|
||||
|
||||
### Grafana
|
||||
|
||||
Using this query you can crate a dashboard in Grafana.
|
||||
|
||||
![Grafana](./docs/images/grafana.png)
|
||||
|
||||
|
||||
## Links
|
||||
|
||||
* [Pinout for the ESP8266 based Shelly Plug-S Smart Plug
|
||||
](https://faulty.cloud/blog/shelly-plug-s-pinout)
|
||||
* [Youtube video: Upgrade Shelly Switches | Easy NO Soldering Smart Garage Door Opener](https://www.youtube.com/watch?v=_oRr8FZyyQ0)
|
||||
* [mg2x](https://github.com/arendst/mgos-to-tasmota)
|
||||
* [Shelly Plug S Power Monitoring Plug Tasmota template](https://templates.blakadder.com/shelly_plug_S.html)
|
Before Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 526 KiB |
Before Width: | Height: | Size: 556 KiB |
Before Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 358 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 168 KiB |
|
@ -676,5 +676,24 @@
|
|||
"file": "/data/node_modules/node-red-contrib-influxdb/influxdb.js"
|
||||
}
|
||||
}
|
||||
},
|
||||
"node-red-contrib-watt2kwh": {
|
||||
"name": "node-red-contrib-watt2kwh",
|
||||
"version": "0.1.2",
|
||||
"local": true,
|
||||
"user": true,
|
||||
"nodes": {
|
||||
"simpletime": {
|
||||
"name": "simpletime",
|
||||
"types": [
|
||||
"watt2kwh"
|
||||
],
|
||||
"enabled": true,
|
||||
"local": true,
|
||||
"user": false,
|
||||
"module": "node-red-contrib-watt2kwh",
|
||||
"file": "/data/node_modules/node-red-contrib-watt2kwh/watt2kwh.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -654,5 +654,27 @@
|
|||
"file": "/data/node_modules/node-red-dashboard/nodes/ui_spacer.js"
|
||||
}
|
||||
}
|
||||
},
|
||||
"node-red-contrib-influxdb": {
|
||||
"name": "node-red-contrib-influxdb",
|
||||
"version": "0.6.1",
|
||||
"local": true,
|
||||
"user": true,
|
||||
"nodes": {
|
||||
"influxdb": {
|
||||
"name": "influxdb",
|
||||
"types": [
|
||||
"influxdb",
|
||||
"influxdb out",
|
||||
"influxdb batch",
|
||||
"influxdb in"
|
||||
],
|
||||
"enabled": true,
|
||||
"local": true,
|
||||
"user": false,
|
||||
"module": "node-red-contrib-influxdb",
|
||||
"file": "/data/node_modules/node-red-contrib-influxdb/influxdb.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
# About
|
||||
|
||||
> This folder will be mounted into the Node-RED runtime. So be careful.
|
||||
|
||||
# Node-RED
|
||||
|
||||
If you boot up our tech stack using `docker-compose` you already have a Node-RED instance running on [your local machine](http://localhost:1880/).
|
||||
|
||||
Node-RED is an open-source, low-code, visual programming tool based on the concept of flow-based development. The idea behind it is to make it very easy to connect APIs, hardware devices, and anything else accessible over some type of network connection.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
Nodes are the important part of Node-Red. They are the building blocks when working with Node-Red. Nodes are triggered by either receiving a message object from a previous node or an external event like an MQTT event. The node processes the message or event and then passes it on to the next node.
|
||||
|
||||
A node can:
|
||||
* Inject: Starts a flow by injecting a message or a payload.
|
||||
* Change: Here you can do basic transformation or modification on the message object.
|
||||
* Debug: Can be used to help developing flows by sending messages to the side bar.
|
||||
* Switch: Here you can add logic (like sending the message to different nodes).
|
||||
* Function: Add custom JavaScript for uses cases where simple nodes do not do the trick.
|
||||
|
||||
Flows are an organized sequence of nodes. Let's do the "first steps" by creating a simple flow.
|
||||
|
||||
## Plugins
|
||||
|
||||
> The plugin folder is pushed into this Git repository and is mounted in Docker. Maybe we should use an own Docker file, instead.
|
||||
|
||||
Node-RED uses plugins (e.g. for InfluxDB or own Dashboard capabilites).
|
||||
|
||||
You can access the plugins in the right burger menu.
|
||||
|
||||
![Plugins](./docs/images/node-red-plugins.png)
|
||||
|
||||
## First steps
|
||||
|
||||
For debuging I already added Node-RED's own dashboard (sure, we are going to use Grafana, later).
|
||||
|
||||
![Overview](./docs/images/1-overview.png)
|
||||
|
||||
The dashboard should be visible on the righmost menu item in Node-RED.
|
||||
|
||||
![Dashboard item](./docs/images/dashboard.png)
|
||||
|
||||
In Node-RED you can add a MQQT node to receive values from the power monitor. As we run in `docker-compose`you don't have to use the IP address of our Eclipse Mosquitto sever, but you can simply use `mosquitto` as the host nome.
|
||||
|
||||
![MQTT Node](./docs/images/2-mqtt-node.png)git a
|
||||
|
||||
To simply display the values in a gauge (or chart) you can hook it up to a gauge node.
|
||||
|
||||
![Gauge Node](./docs/images/3-gauge-node.png)
|
||||
|
||||
In the dasboard section you have to create a tab. Inside this tab you have to create a group.
|
||||
|
||||
![Dashboard Settings](./docs/images/4-dashboard-node.png)
|
||||
|
||||
The tricky part is putting the gauges in the group. This is done in the gauge's settings (not in the dashboard's settings).
|
||||
|
||||
![Gauge Node](./docs/images/3-gauge-node.png)
|
||||
|
||||
You can view the dashboard in an (also mobile) web browser.
|
||||
|
||||
![Mobile view](./docs/images/5-dashboard.png)
|
||||
|
||||
Have a look at the flow also in [this repository](./00-dashboard-example/dashboard.json).
|
||||
|
||||
## InfluxDB
|
||||
|
||||
Already added to this project is [node-red-contrib-influxdb](https://flows.nodered.org/node/node-red-contrib-influxdb). You can use it's nodes to write and query data from an InfluxDB time series database. These nodes support both InfluxDB 1.x and InfluxDb 2.0 databases. At the time of this writing we are using [version 2.6 of InfluxDB on port 8086](http://admin:adminadmin@localhost:8086).
|
||||
|
||||
In Node-RED we will be passing the power consumption number through MQTT.
|
||||
|
||||
![Overview](./docs/images/influx-flow.png)
|
||||
|
||||
By default this will be passed as a string, so we need to create a function to convert it into a Number before storing it in InfluxDB.
|
||||
|
||||
Add a function node to the page and put the following code into the node:
|
||||
|
||||
```JavaScript
|
||||
msg.payload = Number(msg.payload)
|
||||
return msg;
|
||||
```
|
||||
|
||||
![Function](./docs/images/influx-function.png)
|
||||
|
||||
You can forward this message to InfluxDB.
|
||||
|
||||
![Influx Node](./docs/images/influx-node.png)
|
||||
|
||||
The `URL`of our InfluxDB is `http://influxdb:8086`. In InfluxDB you have to create a `token` to connect: [Load Data -> API Tokens](http://localhost:8086/orgs/721027680173bf2f/load-data/tokens).
|
||||
|
||||
![Influx Create Token](./docs/images/influx-create-token.png)
|
||||
|
||||
You can use this `token` to create a connection in Node-RED.
|
||||
|
||||
![Influx Connection](./docs/images/influx-connection.png)
|
||||
|
||||
Then the measurements should be visible in [Influx Data Explorer](http://localhost:8086/orgs/721027680173bf2f/data-explorer?bucket=test).
|
||||
|
||||
![Influx Data Explorer](./docs/images/influx-data-explorer.png)
|
||||
|
||||
As the data is now stored in Influx, [let's create a dashboard in Grafana](../dashboard/README.md).
|
||||
|
||||
# Links
|
||||
|
||||
* [IoT Made Easy with Node-RED and InfluxDB](https://www.influxdata.com/blog/iot-easy-node-red-influxdb/)
|
||||
* A great tutorial can be found at [microcontrollerlab.com](https://microcontrollerslab.com/esp32-mqtt-publish-multiple-sensor-readings-node-red/)
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 131 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 206 KiB |
Before Width: | Height: | Size: 198 KiB |
Before Width: | Height: | Size: 143 KiB |
Before Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 99 KiB |