Hallo zusammen,
ich habe mir eine Tibber Anzeige gebaut mit einem ESP32 und einer TFT 2,8" Anzeige. Dazu habe ich einen Sockel für die Glasglocke gedruckt und eine Halterung für die Anzeige und den ESP32. Wenn Interesse besteht kann ich auch die STL Dateien zur Verfügung stellen. Es sind noch 2 Helfer notwendig sowie die Tibber Integration,
esphome:
name: groe-anzeige
friendly_name: Große Anzeige
esp32:
board: esp32dev
framework:
type: arduino
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "xxxx"
ota:
- platform: esphome
password: "b889c386e48214ad08640d1xxxxx"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Groe-Anzeige Fallback Hotspot"
password: "zzzz"
captive_portal:
# Zeitkomponente von Home Assistant einrichten
time:
- platform: homeassistant
id: homeassistant_time
globals:
- id: next_10_hours_prices
type: float[10]
initial_value: '{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}'
- id: next_10_hours_levels
type: std::vector<std::string>
initial_value: '{"", "", "", "", "", "", "", "", "", ""}'
- id: debug_mode
type: bool
initial_value: 'false'
# PWM output on GPIO26
output:
- platform: ledc
pin: GPIO26
id: pwm_output
frequency: 1000 Hz # Adjust frequency as needed
# Expose the PWM output as a light entity to Home Assistant
light:
- platform: monochromatic
name: "PWM Light"
output: pwm_output
default_transition_length: 0s # No transition on startup
gamma_correct: 1.0 # No gamma correction
restore_mode: ALWAYS_ON # Always restore last state
id: pwm_light
# Text sensor to read the input_number value from Home Assistant
text_sensor:
- platform: homeassistant
id: pwm_text_sensor
entity_id: input_number.pwm_hintergrund_beleuchtung
on_value:
then:
- lambda: |-
float pwm_value = atof(id(pwm_text_sensor).state.c_str()) / 100.0;
ESP_LOGD("pwm_control", "Received PWM value: %s, Converted: %.2f", id(pwm_text_sensor).state.c_str(), pwm_value);
id(pwm_output).set_level(pwm_value);
- platform: homeassistant
id: ha_next_10_hours_prices
entity_id: sensor.next_10_hours_prices
internal: true
- platform: homeassistant
id: ha_next_10_hours_levels
entity_id: sensor.next_10_hours_level
internal: true
sensor:
- platform: homeassistant
id: co2_sensor
entity_id: sensor.co2_sensor_co2_wert
- platform: homeassistant
id: bezug_w
entity_id: sensor.pv_bezug_w
internal: true
- platform: homeassistant
id: lieferung_w
entity_id: sensor.pv_lieferung_w
internal: true
- platform: homeassistant
id: gesamtverbrauch
entity_id: sensor.gesamtverbrauch_haus_w
internal: true
#----------------- Intervall -----------------
interval:
- interval: 60s
then:
- lambda: |-
// Get the sensor state as a string
std::string ha_sensor = id(ha_next_10_hours_prices).state;
// Log the sensor value
if (id(debug_mode)) ESP_LOGI("custom", "Next 10 hours prices: %s", ha_sensor.c_str());
// Split the string into individual prices
std::vector<float> prices;
size_t start = 0;
size_t end = ha_sensor.find(',');
while (end != std::string::npos) {
std::string price_str = ha_sensor.substr(start, end - start);
if (!price_str.empty()) {
prices.push_back(std::atof(price_str.c_str()));
}
start = end + 1;
end = ha_sensor.find(',', start);
}
std::string price_str = ha_sensor.substr(start, end - start);
if (!price_str.empty()) {
prices.push_back(std::atof(price_str.c_str()));
}
// Ensure the size of the array is 10
while (prices.size() < 10) {
prices.push_back(0.0);
}
if (prices.size() > 10) {
prices.resize(10);
}
// Store the prices in the global array
for (int i = 0; i < 10; i++) {
id(next_10_hours_prices)[i] = prices[i];
}
// Log the global array values
if (id(debug_mode)) {
for (int i = 0; i < 10; i++) {
ESP_LOGI("custom", "next_10_hours_prices[%d]: %f", i, id(next_10_hours_prices)[i]);
}
}
// Process next 10 hours levels
std::string ha_levels = id(ha_next_10_hours_levels).state;
if (id(debug_mode)) ESP_LOGI("custom", "Next 10 hours levels: %s", ha_levels.c_str());
std::vector<std::string> levels;
start = 0;
end = ha_levels.find(',');
while (end != std::string::npos) {
std::string level_str = ha_levels.substr(start, end - start);
if (!level_str.empty()) {
levels.push_back(level_str);
}
start = end + 1;
end = ha_levels.find(',', start);
}
std::string level_str = ha_levels.substr(start, end - start);
if (!level_str.empty()) {
levels.push_back(level_str);
}
while (levels.size() < 10) {
levels.push_back("");
}
if (levels.size() > 10) {
levels.resize(10);
}
for (int i = 0; i < 10; i++) {
id(next_10_hours_levels)[i] = levels[i];
}
// Log the levels array
if (id(debug_mode)) {
for (int i = 0; i < 10; i++) {
ESP_LOGI("custom", "next_10_hours_levels[%d]: %s", i, id(next_10_hours_levels)[i].c_str());
}
}
# Display-Komponente einrichten (ST7735)
spi:
clk_pin: 18
mosi_pin: 23
display:
- platform: ili9xxx
model: st7789v
dimensions:
height: 320
width: 240
# transform:
# swap_xy: true
# mirror_x: true
# color_order: bgr
# data_rate: 80MHz
reset_pin: GPIO13
cs_pin: GPIO04
dc_pin: GPIO14
update_interval: 10s
lambda: |-
it.fill(id(my_black));
// it.print(5, 260, id(large_font), id( my_white), TextAlign::TOP_LEFT, "0.18 €");
// Display the global CO2 Werte
int co2 = id(co2_sensor).state;
esphome::Color color;
if (id(co2_sensor).has_state()) {
int co2 = id(co2_sensor).state;
esphome::Color color;
if (co2 < 800) {
color = id(my_springgreen);
} else if (co2 < 1000) {
color = id(my_gold);
} else if (co2 < 1400) {
color = id(my_orange);
} else {
color = id(my_red);
}
it.printf(1, 0, id(my_font20), color, "CO2: %d ppm", co2);
} else {
it.printf(1, 0, id(my_font20), id(my_white), "CO2: N/A");
}
// Display the global Bezug or Lieferung values
if (id(bezug_w).state > 0) {
it.printf(1, 25, id(my_font20), id(red), "Bezug: %.0f W", id(bezug_w).state);
} else if (id(lieferung_w).state > 0) {
it.printf(1, 25, id(my_font20), id(green), "Lieferung: %.0f W", id(lieferung_w).state);
} else {
it.printf(1, 25, id(my_font20), id(my_white), "keine Daten");
}
if (id(bezug_w).state > 0) {
it.printf(1, 50, id(my_font20), id(my_orange), "Verbrauch Haus: %.0f W", id(gesamtverbrauch).state);
} else {
it.printf(1, 50, id(my_font20), id(green), "Verbrauch Haus: %.0f W", id(gesamtverbrauch).state);
}
if (id(debug_mode)) ESP_LOGI("main", " %s ", id(next_10_hours_levels)[0].c_str());
auto trim = [](std::string &str) {
str.erase(0, str.find_first_not_of(' '));
str.erase(str.find_last_not_of(' ') + 1);
};
float min_price = id(next_10_hours_prices)[0];
float max_price = id(next_10_hours_prices)[0];
for (int i = 0; i < 10; i++) {
float price = id(next_10_hours_prices)[i];
if (price < min_price) min_price = price;
if (price > max_price) max_price = price;
}
it.printf(5, 75, id(my_font), "Min: %.2f", min_price);
it.printf(90, 75, id(my_font), "Max: %.2f", max_price);
for (int i = 0; i < 10; i++) {
float price = id(next_10_hours_prices)[i];
int height = (int)(price * 400);
if (height > 200) height = 240;
int x = (i * 22) +5 ;
int y = 260 - height;
std::string level = id(next_10_hours_levels)[i].c_str();
trim(level);
if (id(debug_mode)) {
ESP_LOGI("custom", "Processing index %d: level: %s", i, level.c_str());
}
auto color = id(my_white); // Default to white
const char* color_name = "white";
if (level == "VERY_CHEAP") {
color = id(light_green);
color_name = "light green";
} else if (level == "CHEAP") {
color = id(green_yellow);
color_name = "green-yellow";
} else if (level == "NORMAL") {
color = id(yellow);
color_name = "yellow";
} else if (level == "EXPENSIVE") {
color = id(orange);
color_name = "orange";
} else if (level == "VERY_EXPENSIVE") {
color = id(red);
color_name = "red";
} else if (level == "EXTREMELY_EXPENSIVE") {
color = id(dark_red);
color_name = "dark red";
}
if (id(debug_mode)) {
ESP_LOGI("custom", "Drawing rectangle at x: %d, y: %d, height: %d, color: %s", x, y, height, color_name);
}
it.filled_rectangle(x, y, 18, height, color);
}
float current_price = id(next_10_hours_prices)[0];
std::string level = id(next_10_hours_levels)[0].c_str();
if (current_price != 0.0) {
if (id(debug_mode)) ESP_LOGI("main", "tibber_electricity_price");
auto color = id(my_white); // Default to white
const char* color_name = "white";
if (level == "VERY_CHEAP") {
color = id(light_green);
color_name = "light green";
} else if (level == "CHEAP") {
color = id(green_yellow);
color_name = "green-yellow";
} else if (level == "NORMAL") {
color = id(yellow);
color_name = "yellow";
} else if (level == "EXPENSIVE") {
color = id(orange);
color_name = "orange";
} else if (level == "VERY_EXPENSIVE") {
color = id(red);
color_name = "red";
} else if (level == "EXTREMELY_EXPENSIVE") {
color = id(dark_red);
color_name = "dark red";
}
if (id(debug_mode)) ESP_LOGI("main", " %12s ", id(next_10_hours_levels)[0].c_str() );
it.printf(20, 265, id(large_font), color, "%.2f€", current_price);
}
# Schriftart für die Anzeige des aktuellen Preises
font:
# - file: "arial.ttf"
# id: large_font
# size: 36
- file: "gfonts://Comfortaa"
id: large_font
size: 56
glyphs: |
!"%()+*=,-_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz€@
- file: "gfonts://Roboto"
id: my_font
size: 16
- file: "gfonts://Roboto"
id: my_font20
size: 20
# Define colors with percentages
color:
- id: light_green
red: 56.5%
green: 93.3%
blue: 56.5%
- id: green
red: 0%
green: 100%
blue: 0%
- id: green_yellow
red: 67.8%
green: 100.0%
blue: 18.4%
- id: yellow
red: 100.0%
green: 100.0%
blue: 0%
- id: orange
red: 100.0%
green: 64.7%
blue: 0%
- id: red
red: 100.0%
green: 0%
blue: 0%
- id: dark_red
red: 54.5%
green: 0%
blue: 0%
- id: my_black
red: 0%
green: 0%
blue: 0%
- id: my_white
red: 100%
green: 100%
blue: 100%
- id: my_springgreen
red: 0%
green: 100%
blue: 50%
- id: my_gold
red: 100%
green: 84%
blue: 0%
- id: my_orange
red: 100%
green: 65%
blue: 0%
- id: my_red
red: 100%
green: 0%
blue: 0%
Helfer: Template Sensor
Next 10 Hours Level
{% set now = now() %}
{% set levels_today = state_attr('sensor.tibber_electricity_price', 'today') %}
{% set levels_tomorrow = state_attr('sensor.tibber_electricity_price', 'tomorrow') %}
{% set combined_levels = levels_today + levels_tomorrow %}
{% set next_10_levels = combined_levels |
selectattr('startsAt', '>=', now.replace(minute=0, second=0, microsecond=0).isoformat()) | map(attribute='level') | list %}
{{ next_10_levels[:10] | join(', ') }}
sensor.next_10_hours_prices:
{% set now = now() %}
{% set prices_today = state_attr('sensor.tibber_electricity_price', 'today') %}
{% set prices_tomorrow = state_attr('sensor.tibber_electricity_price', 'tomorrow') %}
{% set combined_prices = prices_today + prices_tomorrow %}
{% set next_10_prices = combined_prices | selectattr('startsAt', '>=', now.replace(minute=0, second=0, microsecond=0).isoformat()) | map(attribute='total') | list %}
{{ next_10_prices[:10] | join(', ') }}