Dot Matrix Display 64x56 LEDs als Großanzeige (MAX7219)

Ich wollte meine Dotmatrix Module mal zum Leben erwecken. Also wieder mal eine Anzeige für Zeit und Temperatur gebastelt.
Dot Matrix 64x56 = 4096 LEDs 25x22cm ACHTUNG STROMAUFNAHME!!
SPI Bus 1 Dotmatrix rot 64x32 für die Uhrzeit und Wochentag - Helligkeit Tag/Nacht im Home Assistant einstellbar
SPI Bus 2 Dotmatrix gruen 64x24 für die Temperatur - Helligkeit Tag/Nacht im Home Assistant einstellbar
Umschaltung Helligkeit über Sonnenaufgang/Sonnenuntergang
CS und Clock mit Levelschifter (MAX7219 erfordert 5V),
MOSI ohne Levelschifter mit 10kOhm pulldown, CLK mit 10 kΩ‑Pull‑Down, CS (LOAD) mit einem 10 kΩ‑Pull‑Up
täglicher Reset = Vorbeugung Modulausfall

# Chip is ESP32-D0WDQ6 (revision v1.1) Berrybase NMCU-ESP32
# Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
# Crystal is 40MHz MAC: b0:b2:1c:ff:a1:c4
# Dot Matrix 64x56 = 4096 LEDs 25x22cm ACHTUNG STROMAUFNAHME!!
# SPI Bus 1 Dotmatrix rot 64x32 für die Uhrzeit und Wochentag - Helligkeit Tag/Nacht im Home Assistant einstellbar
# SPI Bus 2 Dotmatrix gruen 64x24 für die Temperatur - Helligkeit Tag/Nacht im Home Assistant einstellbar
# Umschaltung Helligkeit über Sonnenaufgang/Sonnenuntergang
# CS und Clock mit Levelschifter (MAX7219 erfordert 5V), 
# MOSI ohne Levelschifter mit 10kOhm pulldown, CLK mit 10 kΩ‑Pull‑Down, CS (LOAD) mit einem 10 kΩ‑Pull‑Up 
# täglicher Reset = Vorbeugung Modulausfall
# Juni 2025

esphome:
  name: 20matrix-esp32
  friendly_name: 20matrix-esp32
  
esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:
  level: ERROR 
 
# Enable Home Assistant API
api:
  encryption:
    key: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

ota:
  - platform: esphome
    password: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "20Matrix-Esp32 Fallback Hotspot"
    password: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

captive_portal:

spi: # Use VSPI # https://https://wolles-elektronikkiste.de/esp32-mit-arduino-code-programmieren
  - id: spi_bus_rot
    clk_pin: GPIO18 #18 (VSPI_CLK) gruen
    mosi_pin: GPIO23 #23 (VSPI_MOSI) orange
    # miso_pin: GPIO19 #19 (VSPI_MISO) nicht verwendet/notwendig

  - id: spi_bus_gruen
    clk_pin: GPIO14 #14 (HSPI_CLK) gruen/weiss
    mosi_pin: GPIO13 #13 (HSPI_MOSI) orange
    # miso_pin: GPIO19 #12 (HSPI_MISO) nicht verwendet/notwendig

display:
  - platform: max7219digit
    spi_id: spi_bus_gruen
    id: Dotmatrix_gruen
    cs_pin: GPIO15 # (HSPI_CS = Chip Select) gelb/weiss
    num_chips: 24
    num_chip_lines: 3
    chip_lines_style: snake
    # intensity: 2 nicht aktiv, wird im HA mit Sonnenstand eingestellt
    update_interval: 5s
    lambda: |-
      // Helligkeit setzen
      id(Dotmatrix_gruen).intensity(id(current_brightness_temp));
      it.clear();

      if (!isnan(id(mw_temp_aussen).state)) { // warten auf Wert von HA
        float value = id(mw_temp_aussen).state;
        int whole = abs((int)value);  // z.B. 12
        int tens = whole / 10;        // 1
        int ones = whole % 10;        // 2
        int fraction = abs(int((value - (int)value) * 10));  // 7
        bool is_negative = value < 0;

        int y = -8; // Höhe Temperaturanzeige global
        // Vorzeichen darstellen
        if (is_negative) {
          it.filled_rectangle(0, 12, 8, 3);  // kleiner Block
        }
        // Zehnerstelle Temp (nur anzeigen wenn >= 10)
        if (tens > 0) {
          it.print(11, y, id(big_clock_font), to_string(tens).c_str());
        }
        // Einerstelle Temp
        it.print(26, y, id(big_clock_font), to_string(ones).c_str());
        
        // Nachkommastelle Temp
        it.print(44, y, id(big_clock_font), to_string(fraction).c_str());
        
        // Komma als Grafik
        it.line(40, 21, 42, 21); //oberste Zeile
        it.line(39, 22, 41, 22);
        it.line(38, 23, 40, 23);
        
      } else {
        // Nur wenn Wert ungültig ist – z. B. beim Start
        it.draw_pixel_at(10, 10, COLOR_ON);  // dezentes Zeichen
        // Oder: it.print(0, 0, id(small_font), "...");
      }  

  - platform: max7219digit
    spi_id: spi_bus_rot
    id: Dotmatrix_rot
    cs_pin: GPIO05 # (VSPI_CS = Chip Select) gelb
    num_chips: 32
    num_chip_lines: 4
    chip_lines_style: snake
    # intensity: 2 nicht aktiv, wird im HA und Sonnenstand eingestellt
    update_interval: 1s
    lambda: |-
      // Helligkeit setzen
      id(Dotmatrix_rot).intensity(id(current_brightness));
      it.clear();
      auto now = id(esptime).now();
      if (!now.is_valid()) {
        it.print(0, -3, id(pixelmix8), "....");
        return;
      }
      int y = 1;  // vertikale Feinjustierung (0= oben 10px frei, unten 2px 2= oben 12px frei, unten 0px)
      char buf[2] = {0};
      // H1 Stunden 10er Stelle
      buf[0] = '0' + (now.hour / 10);
      it.print(0, y, id(big_clock_font), buf);
      // H2 Stunden 1er Stelle
      buf[0] = '0' + (now.hour % 10);
      it.print(14, y, id(big_clock_font), buf);

      // Doppelpunkt (blinkend)
      if (now.second % 2 == 0) {
        it.filled_circle(31, 17, 2);  // oberer Punkt
        it.filled_circle(31, 27, 2);  // unterer Punkt
      }
      // M1 Minuten 10er Stelle
      buf[0] = '0' + (now.minute / 10);
      it.print(36, y, id(big_clock_font), buf);
      // M2 Minuten 1er Stelle
      buf[0] = '0' + (now.minute % 10);
      it.print(50, y, id(big_clock_font), buf);
      
      // ======================= Wochentag auf Deutsch
      const char* tage_deutsch[] = {"SONNTAG", "MONTAG", "DIENSTAG", "MITTWOCH", "DONNERSTAG", "FREITAG", "SAMSTAG"};
      char tag[12] = "";
      uint8_t dow = now.day_of_week;
      if (dow >= 1 && dow <= 7) {
        snprintf(tag, sizeof(tag), "%s", tage_deutsch[dow - 1]);
      }
      // Wochentag mittig oben anzeigen
      int text_width = strlen(tag) * 6;  // ca. 6 Pixel pro Zeichen
      int x_pos = (64 - text_width) / 2;
      it.print(x_pos, 0, id(pixelmix8), tag); // 0,0 ist links oben, bei Y=0 bleibt die oberste Reihe frei, bei Y=-1 ganz oben

number:
  - platform: homeassistant
    name: "Helligkeit Tag Uhrzeit"
    id: brightness_day
    entity_id: input_number.matrix_uhr_tag  # muss in HA existieren ist als Helfer Zahlenwert Eingabe angelegt

  - platform: homeassistant
    name: "Helligkeit Nacht Uhrzeit"
    id: brightness_night
    entity_id: input_number.matrix_uhr_nacht # muss in HA existieren ist als Helfer Zahlenwert Eingabe angelegt

  - platform: homeassistant
    name: "Helligkeit Nacht Temperatur"
    id: temperature_night
    entity_id: input_number.matrix_temperatur_nacht # muss in HA existieren ist als Helfer Zahlenwert Eingabe angelegt

  - platform: homeassistant
    name: "Helligkeit Tag Temperatur"
    id: temperature_day
    entity_id: input_number.matrix_temperatur_tag # muss in HA existieren ist als Helfer Zahlenwert Eingabe angelegt


globals:
  - id: current_brightness
    type: int
    restore_value: true
    initial_value: '0'
  - id: current_brightness_temp
    type: int
    restore_value: true
    initial_value: '0'  
    
interval:
  - interval: 1min
    then:
      - lambda: |-
          // Hole aktuelle Zeit als "hh:mm"
          auto now = id(esptime).now();
          if (!now.is_valid()) return;
          
          // Sonnenaufgang/-untergang als hh:mm Strings
          std::string sunrise = std::string(id(sonnenaufgang).state.c_str());
          std::string sunset = std::string(id(sonnenuntergang).state.c_str());

          // Vergleiche aktuelle Zeit
          std::string current_time = 
            (now.hour < 10 ? "0" : "") + std::to_string(now.hour) + ":" +
            (now.minute < 10 ? "0" : "") + std::to_string(now.minute);

          if (current_time >= sunrise && current_time < sunset) {
            // Tag: setze Helligkeit auf brightness_day
            id(current_brightness) = (int)id(brightness_day).state;
            id(current_brightness_temp) = (int)id(temperature_day).state;
          } else {
            // Nacht: setze Helligkeit auf brightness_night
            id(current_brightness) = (int)id(brightness_night).state;
            id(current_brightness_temp) = (int)id(temperature_night).state;
          }
switch:
  - platform: restart
    id: restart_switch
    internal: true

  - platform: template
    name: "Letzter Neustart durch Zeitplan"
    id: dummy_restart_log
    restore_mode: ALWAYS_OFF
    optimistic: true
    entity_category: diagnostic
    internal: false  # Wenn true, erscheint er gar nicht in Home Assistant
    
time:
  - platform: homeassistant
    id: esptime
    on_time:
      - seconds: 0
        minutes: 2
        hours: 4   # "*" wäre jede Stunde
        then:
          - switch.turn_on: dummy_restart_log
          - delay: 200ms
          - switch.turn_off: dummy_restart_log
          - logger.log: "Stündlicher Neustart"
          - switch.toggle: restart_switch

text_sensor:
  - platform: homeassistant
    id: sonnenaufgang
    entity_id: sensor.sonnenaufgang
    internal: true
  - platform: homeassistant
    id: sonnenuntergang
    entity_id: sensor.sonnenuntergang
    internal: true

sensor:
  - platform: wifi_signal # Reports the WiFi signal strength/RSSI in dB
    name: "WiFi Signal dB"
    id: wifi_signal_db
    update_interval: 60s
    entity_category: "diagnostic"
  - platform: copy # Reports the WiFi signal strength in %
    source_id: wifi_signal_db
    name: "wifi_signal_Prozent"
    id: wifi_signal_Prozent
    filters:
      - lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
    unit_of_measurement: "Signal %"
    entity_category: "diagnostic"
    device_class: ""
  - platform: homeassistant
    id: mw_temp_aussen
    entity_id: sensor.wetterstation_temperatur_aussen
    

font:
  - file: "OpenSans-CondBold.ttf" # ideal für grosse Ziffern
    id: big_clock_font
    size: 28 # !! ergibt eine Ziffer mit 20 Pixel Höhe ??!!
    glyphs: "0123456789-,:"
  - file: "pixelmix.ttf" # ideal für Wochentag
    id: pixelmix8
    size: 8
  
  # - file: "unscii-8.ttf" Schrift zu breit
  # - file: "5x7.bdf" Schrift zu klein
    
    

Sieht cool aus.
Frage: hinter der Temperatur weigert sich die Matrix ein °C anzuzeigen?

Nein, wäre auch möglich aber für die Platzaufteilung habe ich es weg gelassen. Das ° wäre noch möglich, C für Celsius halte ich für entbehrlich. 8 Pixel links brauche ich für das Minus Zeichen.

1 „Gefällt mir“

Variante mit ° Zeichen, werde ich so lassen, ist optisch auch kein Brüller aber eine Einheit sollte doch angegeben sein.
Für das C habe ich keinen Platz mehr

1 „Gefällt mir“