Tibber aktuelle Anzeige mit Esphome

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(', ') }}
5 „Gefällt mir“

Cool
Mich würde die Ansteuerung des Display interessieren.

2 „Gefällt mir“

Nette Idee mit dem Gehäuse. Diese CYDs sind echt praktisch. Die „Cheap Yellow Displays“ haben den ESP32 gleich onboard. Bin kürzlich bei Aliexpress zufällig darauf gestoßen und hab mir eins für 93 Cent (Willkommensangebot) bestellt. War positiv überrascht, wie einfach man über ESPHome Daten von anderen Sensoren von Home Assistant abgreifen und anzeigen kann. Ich zeige damit die Temperatur und Luftfeuchtigkeit Aussen und Innen an, sowie den CO2 Level.

2 „Gefällt mir“

Hast Du mal nen Link, was Du meinst mit cyd , denn ich hab so nix gefunden, was passte, jedenfalls lag der Preis hier bei 15 bis 30€ und nicht bei 5€ oder 1€ Willkommensgruß.

Wofür steht cyd ?
cyan display

An sich würde ich gern ein OLED oder white paper Display dafür nutzen, braucht halt große Fonds für Senioren, die auf tibber umgestiegen sind und den Preis nicht mit Lupe suchen können.

Danke

CYD = Cheap Yellow Display

Das Angebot hatte ich damals bei mydealz entdeckt.

https://www.mydealz.de/share-deal-from-app/2391325

Github:
https://github.com/witnessmenow/ESP32-Cheap-Yellow-Display

1 „Gefällt mir“

toll,
ich hab sogar einen der in den Projekten erwähnten Bambulab Printer, so dass das irgendwie passen dürfte. Scheint wohl mal Zeit zu sein, dass bei Gelegenheit in den Deals im Bundle mitzunehmen.

Bestelle eh alle 2 Wochen irgendwas, da füllt so was gerne das Paket oder Bundle mit auf

DANKE