Add project files
This commit is contained in:
42
README.md
Normal file
42
README.md
Normal file
@ -0,0 +1,42 @@
|
||||
# ESPHome Energy Meter
|
||||
|
||||
These are the source files for my ESPHome Energy Meter project, a battery-powered device that reads electricity consumption data from a digital electricity meter and sends the data to a Home Assistant server (or any other MQTT-compatible platform) at configurable intervals.
|
||||
|
||||
The basic idea is to regularly read the SML data transmitted by the electricity meter via its infrared LED, decode the data, and then send the decoded values to an MQTT server via WiFi. These values can then be integrated into Home Assistant using the MQTT integration.
|
||||
|
||||
## 📋 Required Components
|
||||
|
||||
- ESP-01 microcontroller
|
||||
- Attiny85 microcontroller
|
||||
- BPW40 infrared phototransistor
|
||||
- LM358P op amp
|
||||
- 2x 1MΩ resistor
|
||||
- 1x 2KΩ resistor
|
||||
|
||||
The additional Attiny85 microcontroller is required because the ESP-01 does not have a built-in brownout detector. Because of this, the ESP does not boot properly when the battery voltage drops below a certain threshold, causing it to overheat. Additionally, the ESP-01 does not support waking up from deep sleep without an external interrupt, unless I manually bridge two tiny contacts on the chip itself.
|
||||
|
||||
In my case, I also needed an op amp because the electricity meter is behind a plastic window, and the signal captured by the phototransistor was not strong enough for the ESP to reliably read the data without any amplification.
|
||||
|
||||
## 🧩 Overview
|
||||
|
||||
The Attiny85 is flashed with the firmware found in the `attiny_firmware/` directory. Its only job is to turn on the ESP-01 every few minutes (the default is 5 minutes) using the ESP's CH-PD pin, make sure the ESP boots up properly (`GPIO 3` is set to HIGH) and then turn the ESP back off if it fails to set `GPIO 3` to HIGH within 200ms or if the ESP is done.
|
||||
|
||||
The ESP-01 is flashed with the [ESPHome](https://esphome.io) firmware (using the [`energy-meter.yaml`](energy-meter.yaml) configuration file). During startup, it sets `GPIO 1` to LOW to notify the Attiny that it has successfully booted. Then, it connects to WiFi, waits for an [SML](https://de.wikipedia.org/wiki/Smart_Message_Language) data transmission from the smart meter on the `UART RX` port, publishes the states to MQTT, and sets `GPIO 1` back to HIGH to signal to the Attiny that it is done. The ESP also measures and sends its own input voltage, which can be used to estimate the remaining battery capacity.
|
||||
|
||||
As a power supply, I am using a battery pack of three rechargeable AA batteries. With a five-minute update interval, the batteries last about one month before needing to be recharged.
|
||||
|
||||
## 🔬 Schematic
|
||||
|
||||

|
||||
|
||||
([KiCad Schematic](schematic/energy-meter-schematic.kicad_sch), [PDF Version](schematic/energy-meter-schematic.pdf))
|
||||
|
||||
## 📦 3D-Printed Case
|
||||
|
||||
The case was designed in [FreeCAD](https://www.freecad.org). Both the [FreeCAD project file](case/energy-meter-case.FCStd) and a [`.3mf` file](case/energy-meter-case.3mf) are available.
|
||||
|
||||
## 🖼️ Photos
|
||||
|
||||

|
||||
|
||||

|
1
assets/energy-meter-schematic.svg
Normal file
1
assets/energy-meter-schematic.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 55 KiB |
BIN
assets/photo_assembled.webp
Normal file
BIN
assets/photo_assembled.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 773 KiB |
BIN
assets/photo_mounted.webp
Normal file
BIN
assets/photo_mounted.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
5
attiny_firmware/.gitignore
vendored
Normal file
5
attiny_firmware/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
10
attiny_firmware/.vscode/extensions.json
vendored
Normal file
10
attiny_firmware/.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"platformio.platformio-ide"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"ms-vscode.cpptools-extension-pack"
|
||||
]
|
||||
}
|
39
attiny_firmware/include/README
Normal file
39
attiny_firmware/include/README
Normal file
@ -0,0 +1,39 @@
|
||||
|
||||
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 usual convention is to give header files names that end with `.h'.
|
||||
It is most portable to use only letters, digits, dashes, and underscores in
|
||||
header file names, and at most one dot.
|
||||
|
||||
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
|
46
attiny_firmware/lib/README
Normal file
46
attiny_firmware/lib/README
Normal file
@ -0,0 +1,46 @@
|
||||
|
||||
This directory is intended for project specific (private) libraries.
|
||||
PlatformIO will compile them to static libraries and link into executable file.
|
||||
|
||||
The source code of each library should be placed in an own separate directory
|
||||
("lib/your_library_name/[here are source files]").
|
||||
|
||||
For example, see a structure of the following two libraries `Foo` and `Bar`:
|
||||
|
||||
|--lib
|
||||
| |
|
||||
| |--Bar
|
||||
| | |--docs
|
||||
| | |--examples
|
||||
| | |--src
|
||||
| | |- Bar.c
|
||||
| | |- Bar.h
|
||||
| | |- library.json (optional, 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
|
||||
|
||||
and a contents of `src/main.c`:
|
||||
```
|
||||
#include <Foo.h>
|
||||
#include <Bar.h>
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
PlatformIO Library Dependency Finder will find automatically dependent
|
||||
libraries scanning project source files.
|
||||
|
||||
More information about PlatformIO Library Dependency Finder
|
||||
- https://docs.platformio.org/page/librarymanager/ldf.html
|
36
attiny_firmware/platformio.ini
Normal file
36
attiny_firmware/platformio.ini
Normal file
@ -0,0 +1,36 @@
|
||||
; 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:attiny85]
|
||||
platform = atmelavr
|
||||
board = attiny85
|
||||
framework = arduino
|
||||
upload_protocol = custom
|
||||
upload_port = /dev/ttyACM0
|
||||
upload_speed = 19200
|
||||
upload_flags =
|
||||
-C
|
||||
${platformio.packages_dir}/tool-avrdude/avrdude.conf
|
||||
-p
|
||||
$BOARD_MCU
|
||||
-P
|
||||
$UPLOAD_PORT
|
||||
-b
|
||||
$UPLOAD_SPEED
|
||||
-c
|
||||
stk500v1
|
||||
upload_command = avrdude $UPLOAD_FLAGS -U flash:w:$SOURCE:i
|
||||
lib_deps = connornishijima/TinySnore@^1.0.1
|
||||
|
||||
monitor_speed=115200
|
||||
board_build.f_cpu=1000000L
|
||||
board_fuses.lfuse = 0x62
|
||||
board_fuses.hfuse = 0xD7
|
||||
board_fuses.efuse = 0xFF
|
78
attiny_firmware/src/main.cpp
Normal file
78
attiny_firmware/src/main.cpp
Normal file
@ -0,0 +1,78 @@
|
||||
#include "tinysnore.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
// Define to enable serial debug
|
||||
// messages on pin 1
|
||||
#define debug
|
||||
|
||||
// Pin connected to the ESP-01's CH_PD pin (Pin 3)
|
||||
#define esp_enable_pin 4
|
||||
// Pin connected to the ESP-01's feedback pin (UTXD / Pin 1)
|
||||
#define esp_status_pin 3
|
||||
|
||||
// Wake up the ESP-01 every 5 minutes
|
||||
const long update_interval = 1000L * 60L * 5L;
|
||||
// Cut power to the ESP-01 if the update takes longer than 30 seconds
|
||||
const long update_timeout = 1000L * 30L;
|
||||
|
||||
#ifdef debug
|
||||
#include <SoftwareSerial.h>
|
||||
SoftwareSerial Debug(PB0, PB1);
|
||||
#endif
|
||||
|
||||
void setup() {
|
||||
#ifdef debug
|
||||
Debug.begin(9600);
|
||||
Debug.println("-> Reset");
|
||||
#endif
|
||||
pinMode(esp_enable_pin, OUTPUT);
|
||||
pinMode(esp_status_pin, INPUT_PULLUP);
|
||||
}
|
||||
|
||||
unsigned long runESP() {
|
||||
// Reset the ESP
|
||||
digitalWrite(esp_enable_pin, LOW);
|
||||
snore(100);
|
||||
digitalWrite(esp_enable_pin, HIGH);
|
||||
// Wait 0.2s for the ESP to boot up
|
||||
snore(200);
|
||||
// Wait until either the status pin gets pulled HIGH
|
||||
// by the ESP or `update_timeout` milliseconds have passed
|
||||
unsigned long elapsed_time_ms = 0;
|
||||
while (digitalRead(esp_status_pin) == LOW &&
|
||||
elapsed_time_ms < update_timeout) {
|
||||
snore(100);
|
||||
elapsed_time_ms += 100;
|
||||
}
|
||||
// Turn off the ESP
|
||||
digitalWrite(esp_enable_pin, LOW);
|
||||
|
||||
// Return time the mcu was asleep
|
||||
return 100L + 200L + elapsed_time_ms;
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// Store the current time
|
||||
unsigned long start_time = millis();
|
||||
#ifdef debug
|
||||
Debug.print("Start time: ");
|
||||
Debug.println(start_time);
|
||||
Debug.println("-> Running ESP");
|
||||
#endif
|
||||
// Run the ESP and store the sleep time
|
||||
long elapsed_asleep = runESP();
|
||||
// Calculate elapsed time the mcu was awake
|
||||
long elapsed_awake = millis() - start_time;
|
||||
// Calculate the total elapsed time
|
||||
long delta_time = elapsed_awake + elapsed_asleep;
|
||||
// Sleep until next interval
|
||||
long sleep_time = max(0, update_interval - delta_time);
|
||||
#ifdef debug
|
||||
Debug.println("-> ESP Done");
|
||||
Debug.print("Took: ");
|
||||
Debug.println(delta_time);
|
||||
Debug.print("-> Sleeping for ");
|
||||
Debug.println(sleep_time);
|
||||
#endif
|
||||
snore(sleep_time);
|
||||
}
|
11
attiny_firmware/test/README
Normal file
11
attiny_firmware/test/README
Normal file
@ -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
|
BIN
case/energy-meter-case.3mf
Normal file
BIN
case/energy-meter-case.3mf
Normal file
Binary file not shown.
BIN
case/energy-meter-case.FCStd
Normal file
BIN
case/energy-meter-case.FCStd
Normal file
Binary file not shown.
156
energy-meter.yaml
Normal file
156
energy-meter.yaml
Normal file
@ -0,0 +1,156 @@
|
||||
esphome:
|
||||
name: energy-meter
|
||||
friendly_name: energy-meter
|
||||
on_boot:
|
||||
- priority: 600.0
|
||||
then:
|
||||
- output.turn_off: watchdog_state
|
||||
- delay:
|
||||
seconds: 30
|
||||
- if:
|
||||
condition:
|
||||
lambda: "return !id(ota_running);"
|
||||
then:
|
||||
- output.turn_on: watchdog_state
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
|
||||
# Stop the device from sleeping while ota is running
|
||||
globals:
|
||||
- id: ota_running
|
||||
type: bool
|
||||
initial_value: "false"
|
||||
restore_value: False
|
||||
|
||||
# Disable safe mode so the resets from
|
||||
# the Attiny watchdog don't trigger safe mode
|
||||
safe_mode:
|
||||
disabled: true
|
||||
|
||||
# Enable logging
|
||||
logger:
|
||||
baud_rate: 0
|
||||
level: INFO
|
||||
|
||||
mqtt:
|
||||
discovery: true
|
||||
# Set your MQTT broker IP here!
|
||||
broker: 192.168.1.2
|
||||
birth_message: # Empty - this prevents status reporting as unavailabile in HA
|
||||
will_message: # Empty - this prevents status reporting as unavailabile in HA
|
||||
on_message:
|
||||
topic: energy-meter/sensor/current_power/state
|
||||
then:
|
||||
- delay:
|
||||
seconds: 1
|
||||
- if:
|
||||
condition:
|
||||
lambda: "return !id(ota_running);"
|
||||
then:
|
||||
- output.turn_on: watchdog_state
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
password: !secret ota_password
|
||||
on_begin:
|
||||
then:
|
||||
- globals.set:
|
||||
id: ota_running
|
||||
value: "true"
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
domain: ".lan"
|
||||
fast_connect: true
|
||||
|
||||
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||
ap:
|
||||
ssid: "Energy-Meter Fallback Hotspot"
|
||||
password: !secret hotspot_password
|
||||
|
||||
captive_portal:
|
||||
|
||||
web_server:
|
||||
port: 80
|
||||
|
||||
uart:
|
||||
id: uart_bus
|
||||
rx_pin:
|
||||
# RXD0 on ESP-01
|
||||
number: 3
|
||||
inverted: false
|
||||
baud_rate: 9600
|
||||
data_bits: 8
|
||||
parity: NONE
|
||||
stop_bits: 1
|
||||
debug:
|
||||
|
||||
output:
|
||||
# Communication with Attiny85 watchdog
|
||||
- platform: gpio
|
||||
id: watchdog_state
|
||||
pin:
|
||||
number: 1
|
||||
|
||||
sml:
|
||||
id: mysml
|
||||
uart_id: uart_bus
|
||||
|
||||
sensor:
|
||||
- platform: sml
|
||||
retain: False
|
||||
name: "Total energy"
|
||||
sml_id: mysml
|
||||
obis_code: "1-0:1.8.0"
|
||||
unit_of_measurement: kWh
|
||||
accuracy_decimals: 4
|
||||
device_class: energy
|
||||
state_class: total_increasing
|
||||
filters:
|
||||
- multiply: 0.0001
|
||||
|
||||
- platform: sml
|
||||
retain: False
|
||||
name: "Total energy (Tarif 1)"
|
||||
sml_id: mysml
|
||||
obis_code: "1-0:1.8.1"
|
||||
unit_of_measurement: kWh
|
||||
accuracy_decimals: 4
|
||||
device_class: energy
|
||||
state_class: total_increasing
|
||||
filters:
|
||||
- multiply: 0.0001
|
||||
|
||||
- platform: sml
|
||||
retain: False
|
||||
name: "Total energy (Tarif 2)"
|
||||
sml_id: mysml
|
||||
obis_code: "1-0:1.8.2"
|
||||
unit_of_measurement: kWh
|
||||
accuracy_decimals: 4
|
||||
device_class: energy
|
||||
state_class: total_increasing
|
||||
filters:
|
||||
- multiply: 0.0001
|
||||
|
||||
- platform: sml
|
||||
retain: False
|
||||
name: "Current power"
|
||||
sml_id: mysml
|
||||
obis_code: "1-0:16.7.0"
|
||||
unit_of_measurement: W
|
||||
accuracy_decimals: 4
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
|
||||
- platform: adc
|
||||
pin: VCC
|
||||
icon: "mdi:battery"
|
||||
name: "Battery Voltage"
|
||||
|
||||
# The ESP's deep sleep is not being used, but adding it
|
||||
# may prevent the device from showing as offline in Home Assistant
|
||||
deep_sleep:
|
||||
sleep_duration: 5min
|
2799
schematic/energy-meter-schematic.kicad_sch
Normal file
2799
schematic/energy-meter-schematic.kicad_sch
Normal file
File diff suppressed because it is too large
Load Diff
BIN
schematic/energy-meter-schematic.pdf
Normal file
BIN
schematic/energy-meter-schematic.pdf
Normal file
Binary file not shown.
Reference in New Issue
Block a user