ESP32-S3-BOX-3

hardware podporující Assist Probouzecí slovo pro hlasové ovládání Home Assistant
strjan
Pokročilý
Pokročilý
Příspěvky: 170
Registrován: 13. červenec 2023, 16:04
Dal poděkování: 12 poděkování
Dostal poděkování: 3 poděkování

Re: ESP32-S3-BOX-3

Příspěvek od strjan »

Tak to je pak jasne.
Ja jsem teda puvodne reagoval hlavne v kontextu tohohle esp32-s3-box-3. Ma dost vykonu na to, aby tenhle filr udelal sam? Jak pozici tech zdroju kalibrujes? atd.

kiklhorn
Moderátor
Moderátor
Příspěvky: 739
Registrován: 03. červenec 2021, 18:35
Dal poděkování: 84 poděkování
Dostal poděkování: 175 poděkování

Re: ESP32-S3-BOX-3

Příspěvek od kiklhorn »

Podle reálného chování s tím něco v té esp-adf/esp-dsp dělá. Na porovnání mám jednomikrofon, ReSpeaker (zatím ne na ESP32, ale v RPi satelitu) a možná ještě objednám třímikrofon ESP32-S3-Korvo-1
Vše co si přinesu domů je buď Shelly, nebo to skončí buď pod ESPhome nebo pod Zigbee2mqtt.
Ajťák co pamatuje BBS a OS/2 Warp a je mu jedno o jaký systém nebo síťařinu běží.
HA OS jako jedna z Proxmox VM na Odroid H3+/64GB https://github.com/tteck/Proxmox

Uživatelský avatar
Pete30
Moderátor
Moderátor
Příspěvky: 2901
Registrován: 30. září 2020, 20:33
Dal poděkování: 152 poděkování
Dostal poděkování: 319 poděkování

Re: ESP32-S3-BOX-3

Příspěvek od Pete30 »

Na esphome je nový fw pro esp32-s3-box 3, nezkoušel to někdo ?
https://github.com/esphome/firmware/blo ... box-3.yaml
Zatím si myslím že co máme zde bude lepší i když jsem se k tomu ještě nedostal (už ho mám doma), ale zatím ještě dodělávám openhasp display.
Pokud nejsem přítomen tak jsem na rybách ;)

kiklhorn
Moderátor
Moderátor
Příspěvky: 739
Registrován: 03. červenec 2021, 18:35
Dal poděkování: 84 poděkování
Dostal poděkování: 175 poděkování

Re: ESP32-S3-BOX-3

Příspěvek od kiklhorn »

Díky za odkaz, už konečně vím jak podstrčit custom audio board.
Jak jsem psal - já nakonec po neúspěšných pokusech šel cestou redefinice GPIO u s3-box.

Trochu jsem doufal že Jesse už to má rozjeté proti aktuální verzi ESP-ADF/ESP-IDF ale v tom branchi žádnou aktivitu nevidím (https://github.com/esphome/esphome/tree ... 3-284-v2.6)

Aktuálně je jedno kterou verzi použijete.

Rozdíly jsou v omáčce kolem:
Jesse má taktovanou PSRAM na 80MHz a obrázky na displeji,
Já na rychlejších 120MHz (S3-box-3 to oficiálně umí) a výpis událostí.
Nebo kombinujte a tvořte vlastní...

V příští verzi yaml co sem dám bude lepší výpis událostí a pravděpodobně se odkážu s definicí desky také do Jesseho repa https://github.com/jesserockz/esp32-s3-box-3-board ať to v případě budoucích změn nemusím udržovat duplicitní.
Vše co si přinesu domů je buď Shelly, nebo to skončí buď pod ESPhome nebo pod Zigbee2mqtt.
Ajťák co pamatuje BBS a OS/2 Warp a je mu jedno o jaký systém nebo síťařinu běží.
HA OS jako jedna z Proxmox VM na Odroid H3+/64GB https://github.com/tteck/Proxmox

kiklhorn
Moderátor
Moderátor
Příspěvky: 739
Registrován: 03. červenec 2021, 18:35
Dal poděkování: 84 poděkování
Dostal poděkování: 175 poděkování

Re: ESP32-S3-BOX-3

Příspěvek od kiklhorn »

Další pracovní verze:
- board směrován na repo Jesse
- vypisování/nevypisování URL = přepínač
Snímek obrazovky 2023-11-18 052918.jpg
Výpisy předělány na terminálový výstup:
DSC_1863.JPG

Je to pracovní verze, neodmazávám text - předpokládám že po nějakém čase se něco zaplní (max velikost text pole? paměť? ale té je spousta...) a dojde k restartu ESP.

Kód: Vybrat vše

esphome:
  name: s3box3
  friendly_name: S3box3
  platformio_options:
    board_build.flash_mode: dio
  area: test room
  # libraries: [regex]

esp32:
  board: esp32s3box
  flash_size: 16MB
  framework:
    type: esp-idf
    sdkconfig_options:
      CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: "y"
      CONFIG_ESP32S3_DATA_CACHE_64KB: "y"
      CONFIG_ESP32S3_DATA_CACHE_LINE_64B: "y"
      CONFIG_AUDIO_BOARD_CUSTOM: "y"
      CONFIG_ESP32_S3_BOX_3_BOARD: "y"
    components:
      - name: esp32_s3_box_3_board
        source: github://jesserockz/esp32-s3-box-3-board@main
        refresh: 0s


# Enable logging
logger:
  hardware_uart: USB_SERIAL_JTAG
  level: DEBUG
  logs:
    component: ERROR

# Enable Home Assistant API
api:
  encryption:
    key: "jgVfSe0zIPTlfAEeM2zt5k2exvNL+6LK10sqsQ9qS3k="

ota:
  password: "94431478a5e4b99a956cb61f1a9dcda8"

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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "S3Box3 Fallback Hotspot"
    password: "sDHiUghr0220"

captive_portal:

time:
  - platform: homeassistant
    id: hatime
    timezone: Europe/Prague

# dashboard_import:
#   package_import_url: github://esphome/firmware/voice-assistant/esp32-s3-box.yaml@main



binary_sensor:
  - platform: gpio
    pin:
      number: GPIO1
      inverted: true
    name: "Mute"

  - platform: gpio
    pin:
      number: GPIO0
      mode: INPUT_PULLUP
      inverted: true
    name: Top Left Button
    disabled_by_default: true
    on_click:
      - if:
          condition:
            switch.is_off: use_wake_word
          then:
            - if:
                condition: voice_assistant.is_running
                then:
                  - voice_assistant.stop:
                  - script.execute: reset_led
                else:
                  - voice_assistant.start:
          else:
            - voice_assistant.stop
            - delay: 1s
            - script.execute: reset_led
            - script.wait: reset_led
            - voice_assistant.start_continuous:
  - platform: status
    id: api_connection
    filters:
      - delayed_on: 1s
    on_press:
      - if:
          condition:
            switch.is_on: use_wake_word
          then:
            - voice_assistant.start_continuous:
    on_release:
      - if:
          condition:
            switch.is_on: use_wake_word
          then:
            - voice_assistant.stop:

output:
  - platform: ledc
    pin: GPIO47
    id: backlight_output
    frequency: 19531Hz

light:
  - platform: monochromatic
    output: backlight_output
    name: LCD Backlight
    id: led
    restore_mode: ALWAYS_OFF
    disabled_by_default: true
    default_transition_length: 0s
    effects:
      - pulse:
          name: "Slow Pulse"
          transition_length: 250ms
          update_interval: 250ms
      - pulse:
          name: "Fast Pulse"
          transition_length: 50ms
          update_interval: 50ms

microphone:
  - platform: esp_adf
    id: box_mic

speaker:
  - platform: esp_adf
    id: box_speaker

voice_assistant:
  id: va
  microphone: box_mic
  speaker: box_speaker
  use_wake_word: true
  noise_suppression_level: 2
  auto_gain: 31dBFS
  volume_multiplier: 2.0
  vad_threshold: 3
  on_listening:
    - light.turn_on:
        id: led
        brightness: 100%
        effect: "Slow Pulse"
  on_tts_start:
    - light.turn_on:
        id: led
        brightness: 75%
        effect: "Slow Pulse"
    - text_sensor.template.publish:
        id: textline
        state: !lambda |
          return id(textline).state + "\n<black>" + id(hatime).now().strftime("%X") + " " + x;
  on_end:
    - delay: 100ms
    - wait_until:
        not:
          speaker.is_playing:
    - script.execute: reset_led
  on_error:
    - light.turn_on:
        id: led
        brightness: 50%
        effect: "Fast Pulse"
    - delay: 1s
    - script.execute: reset_led
    - script.wait: reset_led
    - lambda: |-
        if (code == "wake-provider-missing" || code == "wake-engine-missing") {
          id(use_wake_word).turn_off();
        }
        if (message == "Could not request start.") {
          id(restart_script).execute();
        } 
        if (message == "Unexpected error during wake-word-detection") {
          id(restart_script).execute();
        }
    - text_sensor.template.publish:
        id: textline
        state: !lambda |
          return id(textline).state + "\n<red>" + id(hatime).now().strftime("%X") + " " + code;
    - text_sensor.template.publish:
        id: textline
        state: !lambda |
          return id(textline).state + "\n<red>" + id(hatime).now().strftime("%X") + " " + message;
  on_stt_end:
    - text_sensor.template.publish:
        id: textline
        state: !lambda |
          return id(textline).state + "\n<green>" + id(hatime).now().strftime("%X") + " " + x;
  on_tts_end:
    - if:
        condition:
          switch.is_on: show_url
        then:
          - text_sensor.template.publish:
              id: textline
              state: !lambda |
                return id(textline).state + "\n<black>" + x;
  #       # state: !lambda return (strftime("%X", id(hatime).now())+" "+ message);
  #       # state: !lambda return ("Karel "+ x);
  #       # state: !lambda return strftime("%X", id(hatime).now().timestamp());
  #       # state: !lambda return id(hatime).now().timestamp_local().strftime("%X");
  #       state: !lambda return id(hatime).now().to_local_time_string();
  #       # state: !lambda return x;
  on_client_connected:
    - if:
        condition:
          switch.is_on: use_wake_word
        then:
          - voice_assistant.start_continuous:
          - script.execute: reset_led
  on_client_disconnected:
    - if:
        condition:
          switch.is_on: use_wake_word
        then:
          - voice_assistant.stop:
          - light.turn_off: led

script:
  - id: reset_led
    then:
      - if:
          condition:
            switch.is_on: use_wake_word
          then:
            - light.turn_on:
                id: led
                brightness: 25%
                effect: none
          else:
            - light.turn_off: led

  - id: restart_script
    then:
      - voice_assistant.stop
      - delay: 1s
      # - text_sensor.template.publish:
      #     id: line4
      #     state: " "
      # - text_sensor.template.publish:
      #     id: line5
      #     state: " "
      - voice_assistant.start_continuous

switch:
  - platform: template
    name: Use wake word
    id: use_wake_word
    optimistic: true
    restore_mode: RESTORE_DEFAULT_ON
    entity_category: config
    on_turn_on:
      - lambda: id(va).set_use_wake_word(true);
      - if:
          condition:
            not:
              - voice_assistant.is_running
          then:
            - voice_assistant.start_continuous
      - script.execute: reset_led
    on_turn_off:
      - voice_assistant.stop
      - lambda: id(va).set_use_wake_word(false);
      - script.execute: reset_led
  - platform: template
    restore_mode: RESTORE_DEFAULT_ON
    id: show_url
    name: Show URL
    optimistic: true
    entity_category: config

external_components:
  source: github://pr#5230
  # source: github://kiklhorn/esphome
  components: esp_adf
  refresh: 0s

esp_adf:
  board: esp32s3box3
  # board: esp32s3box

psram:
  mode: octal
  speed: 120MHz

font:
  - file: "gfonts://Roboto Condensed"
    glyphs: "?!%()+,-/\\_.:;°<>0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZŽŠČŘĎŤŇĚÁÍÉÓÚŮÝ abcdefghijklmnopqrstuvwxyzμžščřďťňáěíóúůý₂³\n"
    id: font1
    size: 20

text_sensor:
  - id: textline
    platform: template
    on_value:
      then:
        - component.update: my_display
  - id: line0
    platform: template
    on_value:
      then:
        - component.update: my_display
  # - platform: debug
  #   device:
  #     name: "Device Info"
  #   reset_reason:
  #     name: "Reset Reason"

# debug:
#   update_interval: 5s

# sensor:
#   - platform: debug
#     # free:
#     #   name: "Heap Free"
#     # fragmentation:
#     #   name: "Heap Fragmentation"
#     # block:
#     #   name: "Heap Max Block"
#     # loop_time:
#     #   name: "Loop Time"
#     psram:
#       name: "Free PSRAM"


spi:
  clk_pin: GPIO7
  mosi_pin: GPIO6

display:
  - platform: ili9xxx
    model: S3BOX
    update_interval: 300s
    id: my_display
    # backlight_pin: GPIO4 #tento parametr mohu vynechat, a na GPIO4 pověsit PWM a řídit jas
    cs_pin: GPIO5
    dc_pin: GPIO4
    # reset_pin: GPIO48 #Negation needed... ignore
    reset_pin:
      number: 48
      inverted: true

    # https://esphome.io/api/light__state_8cpp_source
    lambda: |-
      // 'batman', 32x13px
      const unsigned char batman [] PROGMEM = {
      0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xf9, 0xff, 0xfe, 0x3e, 0x7c, 0x7f, 0xf8, 0x3c, 0x3c, 0x1f, 
      0xf0, 0x1c, 0x38, 0x0f, 0xf0, 0x00, 0x00, 0x0f, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x07, 
      0xf0, 0x00, 0x00, 0x0f, 0xf0, 0xc4, 0x23, 0x0f, 0xf9, 0xfe, 0x7f, 0x9f, 0xfc, 0xfe, 0x7f, 0x3f, 
      0xff, 0xff, 0xff, 0xff
      };
      //it.image(0, 0, &batman);
      for(auto i = 0; i<240; i++) {it.horizontal_line(0,i,320, my_blue.fade_to_white(i));}
      it.rectangle(0, 0, it.get_width(), it.get_height(),color_red);
      it.printf(60, 9, id(font1), "ESP32-S3-BOX-3");
      int x = 2, yo = 43, offs = 34;
      it.strftime(it.get_width()-2, 10, id(font1), color_green, TextAlign::TOP_RIGHT, "%H:%M", id(hatime).now());
      auto ledcolor = Color(id(led).remote_values.get_red()*255, id(led).remote_values.get_green()*255, id(led).remote_values.get_blue()*255);
      bool ledstatus = id(led).remote_values.get_state();

      it.filled_circle(19, 19, 15, ledstatus ? ledcolor : color_black);
      // it.printf(30, 0, id(font1), "%s", id(line0).state.c_str());
      // it.printf(x, yo, id(font1), "%s", id(line1).state.c_str());
      // it.printf(x, yo=yo+offs, id(font1), "%s", id(line2).state.c_str());
      // it.printf(x, yo=yo+offs, id(font1), "%s", id(line3).state.c_str());
      // it.printf(x, yo=yo+offs, id(font1), color_red, "%s", id(line4).state.c_str());
      // it.printf(x, yo=yo+offs, id(font1), color_red, "%s", id(line5).state.c_str());

      // # https://esphome.io/api/classesphome_1_1display_1_1_display.html




      // ----------------------------  user defined parameters start here --------------------------
      std::string text = id(textline).state.c_str();
      auto used_font = id(font1);
      int line_spacing = -5, line_height = used_font->get_height(); // -5 is usable spacing for dense lines
      int first_line = 34; // "console" text output begins on "first_line". [pixels from top of the display]
      int last_line =  it.get_height() - line_height; //start of last text line (top left corner)
      int left_offset = 3, right_offset = 0;

      std::map<std::string, esphome::Color> colorCodes {  // if left side found in the text then use previously user defined color (or define color directly here) from this map
        {"<red>", color_red},
        {"<green>", color_green},
        {"<black>", color_black},
        {"<blue>", esphome::Color(0, 0, 255)},
        {"<white>", esphome::Color(255, 255, 255)},
        // add more color codes here
      };

      esphome::Color currentColor = color_black; // default color

      // ----------------------------  user defined parameters end --------------------------

      std::vector<std::string> words;
      std::vector<std::string> line;
      std::vector<std::string> lines;      
      //std::slre r1(R"(([^\\ ])\n)");
      //text = std::regex_replace(text, r1, "$1 \n"); // pokud není před \n mezera nebo \ doplň před \n mezeru
      //std::regex r2(R"(\n([^ ]))");
      //text = std::regex_replace(text, r2, "\n $1"); // pokud není před \n znak \ a zároveň za \n není mezera doplň za \n mezeru 

      for (size_t i = 0; i < text.size(); ++i) { // separate \n from letters around but keep \\n untouched
        if (text[i] == '\n') {
          if (i > 0 && text[i-1] != ' ' && (i < 2 || text[i-2] != '\\')) {
            text.insert(i, " ");
            ++i;
          }
          if (i+1 < text.size() && text[i+1] != ' ' && text[i] != '\\') {
            text.insert(i+1, " ");
            ++i;
          }
        }
      }

      auto cleanStringWidth = [&it, &colorCodes, &used_font](std::string input) -> int {
          for (const auto& code : colorCodes) {
              size_t foundPos = input.find(code.first);
              if (foundPos != std::string::npos) {
                  input.erase(foundPos, code.first.length());
              }
          }

          int x1, y1, width, height;
          it.get_text_bounds(0, 0, (input+" ").c_str(), used_font, esphome::display::TextAlign::TOP_LEFT, &x1, &y1, &width, &height);

          return width;
      };

      size_t pos = 0;
      std::string token;
      while ((pos = text.find(" ")) != std::string::npos) {
        token = text.substr(0, pos);
        words.push_back(token);
        text.erase(0, pos + 1);
      }
      words.push_back(text); // Add the last word

      int maxWidth = it.get_width() - left_offset - right_offset;
      int lineWidth = 0;
      std::string space = " ";

      for (const auto& w : words) {
          int x1, y1, width, height;

          if (w == "\n") { // Check if the current word is a line break - it§s allready whole words only
              std::string lineStr; // If it's a line break, start a new line
              for (const auto& word : line) {
                lineStr += word + " ";
              }
              lines.push_back(lineStr);
              line.clear();
              lineWidth = 0;
          } else {  //word isn§t a line break
          // If the word is too long, split it
              // ------------------------------------------------
              width = cleanStringWidth(w);
              if (width > maxWidth) { // If the word is longer than the max width
                    if (!line.empty()) { // handle the last line out of order
                        std::string lineStr;
                        for (const auto& word : line) {
                            lineStr += word + " ";
                        }
                        line.push_back(lineStr); // store the last line
                    }
                  std::string part; // Split the word into smaller parts
                  if (!line.empty()) { 
                    part = line.back(); line.clear();
                  }
                  // ESP_LOGE("custom", "part: %s, width: %d, lineWidth: %d", part.c_str(), width, lineWidth); //ok, this is the last, not full, line before long word
                  for (int i = 0; i < w.size(); i++) {
                      std::string nextPart = part + w[i];
                      if (lineWidth) {
                        width = lineWidth;
                        lineWidth = 0;
                        // ESP_LOGE("s3box3", "width: %d, lineWidth: %d, maxWidth: %d -----------------------------------------------------", width, lineWidth, maxWidth);
                      } else {
                        width = cleanStringWidth(nextPart);
                      }
                      // ESP_LOGE("s3box3", "nextPart): %s, width: %d, lineWidth: %d, maxWidth: %d", part.c_str(), width, lineWidth, maxWidth);
                      if (width > maxWidth) { // If adding another character would make the part too long
                          if (!part.empty()) { // Add the current part to the line and start a new part
                              lines.push_back(part);
                          }
                          part = w[i];
                      } else {
                          part += w[i]; // Add the character to the part
                      }
                  }
                  if (!part.empty()) { // Add the last part to the line
                      line.push_back(part); // continue then on the same line as the last part. If not desired use lines.push_back(part)
                      // ESP_LOGE("s3box3", "part): %s, width: %d, lineWidth: %d, maxWidth: %d", part.c_str(), width, lineWidth, maxWidth);
                      lineWidth = width;
                  }
              }
              //-------------------------------------------------  end of the long word handling
              else if (lineWidth + width <= maxWidth) {
                // ESP_LOGE("s3box3", "width: %d, lineWidth: %d, maxWidth: %d", width, lineWidth, maxWidth);
                  lineWidth += width;
                // ESP_LOGE("s3box3", "w: %s, width: %d, lineWidth: %d, maxWidth: %d", w.c_str(), width, lineWidth, maxWidth);
                  line.push_back(w);
              } else {  // concatenate all words in the line
                  std::string lineStr;
                  for (const auto& word : line) {
                      lineStr += word + " ";
                  }
                  // ESP_LOGE("s3box3", "lineStr: %s, width: %d, lineWidth: %d, maxWidth: %d", lineStr.c_str(), width, lineWidth, maxWidth);
                  lines.push_back(lineStr); // store the line
                  line.clear(); // start a new line
                  line.push_back(w);
                  lineWidth = width;
              }
          }
      }
 
      if (!line.empty()) { // handle the last line
          std::string lineStr;
          for (const auto& word : line) {
              lineStr += word + " ";
          }
          lines.push_back(lineStr); // store the last line
      }

      // now you know the number of lines before printing
      int line_count = lines.size();
      int start_line = 0;
      int max_lines_count = int((last_line+line_spacing-first_line)/(line_height+line_spacing)); //last line without spacing after
      if (line_count > max_lines_count) {
        start_line = line_count - max_lines_count -1;
      }



      // print the lines
      for (int i = start_line, j = 0; i < line_count; ++i, ++j) {
          int y = first_line + j * (int)(line_height + line_spacing);
          std::string& line = lines[i];
          
          size_t lastPos = 0;
          size_t nextPos = std::string::npos;
          int new_begin_position = 0;

          // Find the nearest color code
          for (const auto& code : colorCodes) {
              size_t pos = line.find(code.first, lastPos);
              if (pos != std::string::npos && (nextPos == std::string::npos || pos < nextPos)) {
                  nextPos = pos;
              }
          }

          while (nextPos != std::string::npos) {
              // Print the part of the line before the color code
              std::string textPiece = line.substr(lastPos, nextPos - lastPos);
              it.print(left_offset+new_begin_position, y, used_font, currentColor, textPiece.c_str());

              // Update position
              int x1, y1, width, height;
              it.get_text_bounds(0,0,textPiece.c_str(), used_font, esphome::display::TextAlign::TOP_LEFT, &x1, &y1, &width, &height);
              new_begin_position += width;

              // Update the color based on the color code
              for (const auto& code : colorCodes) {
                  if (line.compare(nextPos, code.first.size(), code.first) == 0) {
                      currentColor = code.second;
                      lastPos = nextPos + code.first.size();
                      break;
                  }
              }

              nextPos = std::string::npos;
              // Find the next nearest color code
              for (const auto& code : colorCodes) {
                  size_t pos = line.find(code.first, lastPos);
                  if (pos != std::string::npos && (nextPos == std::string::npos || pos < nextPos)) {
                      nextPos = pos;
                  }
              }
          }

          // Print the remainder of the line
          std::string textPiece = line.substr(lastPos);
          it.print(left_offset+new_begin_position, y, used_font, currentColor, textPiece.c_str());
      }

color:
  - id: color_red
    red: 1
    green: 0
    blue: 0
  - id: color_green
    red: 0
    green: 1
    blue: 0
  - id: color_black
    red: 0
    green: 0
    blue: 0
  - id: my_blue
    blue: 100%
  - id: my_red
    red: 100%
  - id: my_green
    green: 70%
  - id: my_white
    red: 100%
    blue: 100%
    green: 100%
  - id: my_yellow
    hex: ffff00


Vše co si přinesu domů je buď Shelly, nebo to skončí buď pod ESPhome nebo pod Zigbee2mqtt.
Ajťák co pamatuje BBS a OS/2 Warp a je mu jedno o jaký systém nebo síťařinu běží.
HA OS jako jedna z Proxmox VM na Odroid H3+/64GB https://github.com/tteck/Proxmox

kiklhorn
Moderátor
Moderátor
Příspěvky: 739
Registrován: 03. červenec 2021, 18:35
Dal poděkování: 84 poděkování
Dostal poděkování: 175 poděkování

Re: ESP32-S3-BOX-3

Příspěvek od kiklhorn »

Rozchodil jsem i touchpanel, zatím kromě červeného kroužku.
Použit pro pager stránek.

Kód: Vybrat vše

esphome:
  name: s3box3
  friendly_name: S3box3
  platformio_options:
    board_build.flash_mode: dio
  area: test room
  # libraries: [regex]

esp32:
  board: esp32s3box
  flash_size: 16MB
  framework:
    type: esp-idf
    sdkconfig_options:
      CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: "y"
      CONFIG_ESP32S3_DATA_CACHE_64KB: "y"
      CONFIG_ESP32S3_DATA_CACHE_LINE_64B: "y"
      CONFIG_AUDIO_BOARD_CUSTOM: "y"
      CONFIG_ESP32_S3_BOX_3_BOARD: "y"
    components:
      - name: esp32_s3_box_3_board
        source: github://jesserockz/esp32-s3-box-3-board@main
        refresh: 0s


# Enable logging
logger:
  hardware_uart: USB_SERIAL_JTAG
  # level: NONE
  level: DEBUG
  logs:
    component: ERROR

# Enable Home Assistant API
api:
  encryption:
    key: "jgVfSe0zIPTlfAEeM2zt5k2exvNL+6LK10sqsQ9qS3k="

ota:
  password: "94431478a5e4b99a956cb61f1a9dcda8"

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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "S3Box3 Fallback Hotspot"
    password: "sDHiUghr0220"

captive_portal:

time:
  - platform: homeassistant
    id: hatime
    timezone: Europe/Prague

# dashboard_import:
#   package_import_url: github://esphome/firmware/voice-assistant/esp32-s3-box.yaml@main



binary_sensor:
  - platform: touchscreen
    name: Top Half Touch Button
    id: top_half_button
    touchscreen_id: gt911_touchscreen
    internal: true
    x_min: 0
    x_max: 340
    y_min: 30
    y_max: 120
    on_click: 
      then:
        - number.increment: 
            id: list_offset
            cycle: False
  - platform: touchscreen
    name: Bottom Half Touch Button
    id: bottom_half_button
    touchscreen_id: gt911_touchscreen
    internal: true
    x_min: 0
    x_max: 340
    y_min: 121
    y_max: 240
    on_click: 
      then:
        - number.decrement: 
            id: list_offset
            cycle: False

  - platform: gpio
    pin:
      number: GPIO1
      inverted: true
    name: "Mute"

  - platform: gpio
    pin:
      number: GPIO0
      mode: INPUT_PULLUP
      inverted: true
    name: Top Left Button
    disabled_by_default: true
    on_click:
      - if:
          condition:
            switch.is_off: use_wake_word
          then:
            - if:
                condition: voice_assistant.is_running
                then:
                  - voice_assistant.stop:
                  - script.execute: reset_led
                else:
                  - voice_assistant.start:
          else:
            - voice_assistant.stop
            - delay: 1s
            - script.execute: reset_led
            - script.wait: reset_led
            - voice_assistant.start_continuous:
  - platform: status
    id: api_connection
    filters:
      - delayed_on: 3s
    on_press:
      - if:
          condition:
            switch.is_on: use_wake_word
          then:
            - voice_assistant.start_continuous:
    on_release:
      - if:
          condition:
            switch.is_on: use_wake_word
          then:
            - voice_assistant.stop:

output:
  - platform: ledc
    pin: GPIO47
    id: backlight_output
    frequency: 19531Hz

light:
  - platform: monochromatic
    output: backlight_output
    name: LCD Backlight
    id: led
    restore_mode: ALWAYS_OFF
    disabled_by_default: true
    default_transition_length: 0s
    effects:
      - pulse:
          name: "Slow Pulse"
          transition_length: 250ms
          update_interval: 250ms
      - pulse:
          name: "Fast Pulse"
          transition_length: 50ms
          update_interval: 50ms

microphone:
  - platform: esp_adf
    id: box_mic

speaker:
  - platform: esp_adf
    id: box_speaker

voice_assistant:
  id: va
  microphone: box_mic
  speaker: box_speaker
  use_wake_word: true
  noise_suppression_level: 2
  auto_gain: 31dBFS
  volume_multiplier: 2.0
  vad_threshold: 3
  on_listening:
    - light.turn_on:
        id: led
        brightness: 100%
        effect: "Slow Pulse"
  on_tts_start:
    - light.turn_on:
        id: led
        brightness: 75%
        effect: "Slow Pulse"
    - text_sensor.template.publish:
        id: textline
        state: !lambda |
          return id(textline).state + "\n<black>" + id(hatime).now().strftime("%X") + " " + x;
  on_end:
    - delay: 100ms
    - wait_until:
        not:
          speaker.is_playing:
    - script.execute: reset_led
  on_error:
    - light.turn_on:
        id: led
        brightness: 50%
        effect: "Fast Pulse"
    - delay: 1s
    - script.execute: reset_led
    - script.wait: reset_led
    - lambda: |-
        if (code == "wake-provider-missing" || code == "wake-engine-missing") {
          id(use_wake_word).turn_off();
        }
        if (message == "Could not request start.") {
          id(restart_script).execute();
        } 
        if (message == "Unexpected error during wake-word-detection") {
          id(restart_script).execute();
        }
    - text_sensor.template.publish:
        id: textline
        state: !lambda |
          return id(textline).state + "\n<red>" + id(hatime).now().strftime("%X") + " " + code;
    - text_sensor.template.publish:
        id: textline
        state: !lambda |
          return id(textline).state + "\n<red>" + id(hatime).now().strftime("%X") + " " + message;
  on_stt_end:
    - text_sensor.template.publish:
        id: textline
        state: !lambda |
          return id(textline).state + "\n<green>" + id(hatime).now().strftime("%X") + " " + x;
  on_tts_end:
    - if:
        condition:
          switch.is_on: show_url
        then:
          - text_sensor.template.publish:
              id: textline
              state: !lambda |
                return id(textline).state + "\n<black>" + x;
  #       # state: !lambda return (strftime("%X", id(hatime).now())+" "+ message);
  #       # state: !lambda return ("Karel "+ x);
  #       # state: !lambda return strftime("%X", id(hatime).now().timestamp());
  #       # state: !lambda return id(hatime).now().timestamp_local().strftime("%X");
  #       state: !lambda return id(hatime).now().to_local_time_string();
  #       # state: !lambda return x;
  on_client_connected:
    - if:
        condition:
          switch.is_on: use_wake_word
        then:
          - voice_assistant.start_continuous:
          - script.execute: reset_led
  on_client_disconnected:
    - if:
        condition:
          switch.is_on: use_wake_word
        then:
          - voice_assistant.stop:
          - light.turn_off: led

script:
  - id: reset_led
    then:
      - if:
          condition:
            switch.is_on: use_wake_word
          then:
            - light.turn_on:
                id: led
                brightness: 25%
                effect: none
          else:
            - light.turn_off: led

  - id: restart_script
    then:
      - voice_assistant.stop
      - delay: 1s
      # - text_sensor.template.publish:
      #     id: line4
      #     state: " "
      # - text_sensor.template.publish:
      #     id: line5
      #     state: " "
      - voice_assistant.start_continuous

switch:
  - platform: restart
    name: "Restart $device_name"
  - platform: safe_mode
    name: "Restart (Safe Mode) $device_name"  
  - platform: template
    name: Use wake word
    id: use_wake_word
    optimistic: true
    restore_mode: RESTORE_DEFAULT_ON
    entity_category: config
    on_turn_on:
      - lambda: id(va).set_use_wake_word(true);
      - if:
          condition:
            not:
              - voice_assistant.is_running
          then:
            - voice_assistant.start_continuous
      - script.execute: reset_led
    on_turn_off:
      - voice_assistant.stop
      - lambda: id(va).set_use_wake_word(false);
      - script.execute: reset_led
  - platform: template
    restore_mode: RESTORE_DEFAULT_ON
    id: show_url
    name: Show URL
    optimistic: true
    entity_category: config

external_components:
  - source: github://pr#5230
  # - source: github://kiklhorn/esphome
    components: esp_adf
    refresh: 0s
  - source: github://kiklhorn/components
    components: gt911
    refresh: 0s
  # - source: 
  #     type: local
  #     path: components
  #   components: gt911

i2c:
  id: i2cbus
  sda: GPIO8
  scl: GPIO18
  scan: True
  frequency: 400kHz

# [18:13:55][I][i2c.idf:077]: Results from i2c bus scan:
# [18:13:55][I][i2c.idf:083]: Found i2c device at address 0x18 ES8311 Audio Controller
# [18:13:55][I][i2c.idf:083]: Found i2c device at address 0x40 ES7210 MIC ADC // ATECC608A-TNGTLSU-B ??? 7bit
# [18:13:55][I][i2c.idf:083]: Found i2c device at address 0x5D ??? Touchscreen CTP_2.0inch ???  GT911!!!  ATECC608A-TNGTLSU-B
# [18:13:55][I][i2c.idf:083]: Found i2c device at address 0x68 ICM-42607-P 6 axis IMU
  # define BSP_I2C_EXPAND_SCL      (GPIO_NUM_40)
  # define BSP_I2C_EXPAND_SDA      (GPIO_NUM_41)

  # define BSP_RADAR_OUT_IO        (GPIO_NUM_21)
  # define BSP_IR_CTRL_GPIO        (GPIO_NUM_44)
  # define BSP_IR_TX_GPIO          (GPIO_NUM_39)
  # define BSP_IR_RX_GPIO          (GPIO_NUM_38)

touchscreen:
  - platform: gt911
    display: my_display
    id: gt911_touchscreen
    interrupt_pin: GPIO3
    # on_touch:
    #   then:
    #   - text_sensor.template.publish:
    #       id: textline
    #       state: !lambda |
    #         return id(textline).state + "\n<white>" + id(hatime).now().strftime("%X") + " Touch " + " X: " + std::to_string(touch.x) + " Y: " + std::to_string(touch.y); //# x,y,id,state


# external_components:
#   - source:
#       type: local
#       path: my_components
#     components: [gt911]
# https://github.com/jesserockz/m5paper-esphome/tree/main/components/gt911
# https://components.espressif.com/components/espressif/esp_lcd_touch_gt911
# touchscreen:
#   - platform: gt911
#     #display: 
#     id: gt911_touchscreen
#     interrupt_pin: GPIO36
#     on_touch:
#       - logger.log:
#           format: Touch at (%d, %d)
#           args: [touch.x, touch.y]

esp_adf:
  board: esp32s3box3
  # board: esp32s3box

psram:
  mode: octal
  speed: 120MHz

font:
  - file: "gfonts://Roboto Condensed"
    glyphs: "?!%()+,-/\\_.:;°<>0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZŽŠČŘĎŤŇĚÁÍÉÓÚŮÝ abcdefghijklmnopqrstuvwxyzμžščřďťňáěíóúůý₂³\n"
    id: font1
    size: 20

text_sensor:
  - id: textline
    platform: template
    on_value:
      then:
        - component.update: my_display

number:
  - platform: template
    id: list_offset
    optimistic: True
    step: 1
    min_value: 0
    max_value: 1000
    on_value: 
      then:
        - component.update: my_display
    initial_value: 0
    restore_value: False


  # - platform: debug
  #   device:
  #     name: "Device Info"
  #   reset_reason:
  #     name: "Reset Reason"

# debug:
#   update_interval: 5s

# sensor:
#   - platform: debug
#     # free:
#     #   name: "Heap Free"
#     # fragmentation:
#     #   name: "Heap Fragmentation"
#     # block:
#     #   name: "Heap Max Block"
#     # loop_time:
#     #   name: "Loop Time"
#     psram:
#       name: "Free PSRAM"


spi:
  clk_pin: GPIO7
  mosi_pin: GPIO6

display:
  - platform: ili9xxx
    model: S3BOX
    rotation: 0
    update_interval: 30s
    id: my_display
    # backlight_pin: GPIO4 #tento parametr mohu vynechat, a na GPIO4 pověsit PWM a řídit jas
    cs_pin: GPIO5
    dc_pin: GPIO4
    # reset_pin: GPIO48 #Negation needed...
    reset_pin:
      number: 48
      inverted: true

    # https://esphome.io/api/light__state_8cpp_source
    lambda: |-
      // 'batman', 32x13px
      const unsigned char batman [] PROGMEM = {
      0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xf9, 0xff, 0xfe, 0x3e, 0x7c, 0x7f, 0xf8, 0x3c, 0x3c, 0x1f, 
      0xf0, 0x1c, 0x38, 0x0f, 0xf0, 0x00, 0x00, 0x0f, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x07, 
      0xf0, 0x00, 0x00, 0x0f, 0xf0, 0xc4, 0x23, 0x0f, 0xf9, 0xfe, 0x7f, 0x9f, 0xfc, 0xfe, 0x7f, 0x3f, 
      0xff, 0xff, 0xff, 0xff
      };
      //it.image(0, 0, &batman);
      int gradient_max = 190; double gradient_step = double(gradient_max)/it.get_height();
      for(auto i = 0; i<it.get_height(); i++) {it.horizontal_line(0,i,it.get_width(), my_blue.fade_to_white(i*gradient_step));}
      it.rectangle(0, 0, it.get_width(), it.get_height(),color_red);
      it.printf(60, 9, id(font1), "ESP32-S3-BOX-3");
      int x = 2, yo = 43, offs = 34;
      it.strftime(it.get_width()-2, 10, id(font1), color_green, TextAlign::TOP_RIGHT, "%H:%M", id(hatime).now());
      auto ledcolor = Color(id(led).remote_values.get_red()*255, id(led).remote_values.get_green()*255, id(led).remote_values.get_blue()*255);
      bool ledstatus = id(led).remote_values.get_state();

      it.filled_circle(19, 19, 15, ledstatus ? ledcolor : color_black);
      it.printf(217, 9, id(font1), int(id(list_offset).state) ? "P: -%d" : " ", int(id(list_offset).state));
      // it.printf(x, yo, id(font1), "%s", id(line1).state.c_str());
      // it.printf(x, yo=yo+offs, id(font1), "%s", id(line2).state.c_str());
      // it.printf(x, yo=yo+offs, id(font1), "%s", id(line3).state.c_str());
      // it.printf(x, yo=yo+offs, id(font1), color_red, "%s", id(line4).state.c_str());
      // it.printf(x, yo=yo+offs, id(font1), color_red, "%s", id(line5).state.c_str());

      // # https://esphome.io/api/classesphome_1_1display_1_1_display.html




      // ----------------------------  user defined parameters start here --------------------------
      std::string text = id(textline).state.c_str();
      auto used_font = id(font1);
      int line_spacing = -5, line_height = used_font->get_height(); // -5 is usable spacing for dense lines
      int first_line = 34; // "console" text output begins on "first_line". [pixels from top of the display]
      int last_line =  it.get_height() - line_height; //start of last text line (top left corner)
      int left_offset = 3, right_offset = 0;

      std::map<std::string, esphome::Color> colorCodes {  // if left side found in the text then use previously user defined color (or define color directly here) from this map
        {"<red>", color_red},
        {"<green>", color_green},
        {"<black>", color_black},
        {"<blue>", esphome::Color(0, 0, 255)},
        {"<white>", esphome::Color(255, 255, 255)},
        // add more color codes here
      };

      esphome::Color currentColor = color_black; // default color

      // ----------------------------  user defined parameters end --------------------------

      std::vector<std::string> words;
      std::vector<std::string> line;
      std::vector<std::string> lines;      
      //std::slre r1(R"(([^\\ ])\n)");
      //text = std::regex_replace(text, r1, "$1 \n"); // pokud není před \n mezera nebo \ doplň před \n mezeru
      //std::regex r2(R"(\n([^ ]))");
      //text = std::regex_replace(text, r2, "\n $1"); // pokud není před \n znak \ a zároveň za \n není mezera doplň za \n mezeru 

      for (size_t i = 0; i < text.size(); ++i) { // separate \n from letters around but keep \\n untouched
        if (text[i] == '\n') {
          if (i > 0 && text[i-1] != ' ' && (i < 2 || text[i-2] != '\\')) {
            text.insert(i, " ");
            ++i;
          }
          if (i+1 < text.size() && text[i+1] != ' ' && text[i] != '\\') {
            text.insert(i+1, " ");
            ++i;
          }
        }
      }

      auto cleanStringWidth = [&it, &colorCodes, &used_font](std::string input) -> int {
          for (const auto& code : colorCodes) {
              size_t foundPos = input.find(code.first);
              if (foundPos != std::string::npos) {
                  input.erase(foundPos, code.first.length());
              }
          }

          int x1, y1, width, height;
          it.get_text_bounds(0, 0, (input+" ").c_str(), used_font, esphome::display::TextAlign::TOP_LEFT, &x1, &y1, &width, &height);

          return width;
      };

      size_t pos = 0;
      std::string token;
      while ((pos = text.find(" ")) != std::string::npos) {
        token = text.substr(0, pos);
        words.push_back(token);
        text.erase(0, pos + 1);
      }
      words.push_back(text); // Add the last word

      int maxWidth = it.get_width() - left_offset - right_offset;
      int lineWidth = 0;
      std::string space = " ";

      for (const auto& w : words) {
          int x1, y1, width, height;

          if (w == "\n") { // Check if the current word is a line break - it§s allready whole words only
              std::string lineStr; // If it's a line break, start a new line
              for (const auto& word : line) {
                lineStr += word + " ";
              }
              lines.push_back(lineStr);
              line.clear();
              lineWidth = 0;
          } else {  //word isn§t a line break
          // If the word is too long, split it
              // ------------------------------------------------
              width = cleanStringWidth(w);
              if (width > maxWidth) { // If the word is longer than the max width
                    if (!line.empty()) { // handle the last line out of order
                        std::string lineStr;
                        for (const auto& word : line) {
                            lineStr += word + " ";
                        }
                        line.push_back(lineStr); // store the last line
                    }
                  std::string part; // Split the word into smaller parts
                  if (!line.empty()) { 
                    part = line.back(); line.clear();
                  }
                  // ESP_LOGE("custom", "part: %s, width: %d, lineWidth: %d", part.c_str(), width, lineWidth); //ok, this is the last, not full, line before long word
                  for (int i = 0; i < w.size(); i++) {
                      std::string nextPart = part + w[i];
                      if (lineWidth) {
                        width = lineWidth;
                        lineWidth = 0;
                        // ESP_LOGE("s3box3", "width: %d, lineWidth: %d, maxWidth: %d -----------------------------------------------------", width, lineWidth, maxWidth);
                      } else {
                        width = cleanStringWidth(nextPart);
                      }
                      // ESP_LOGE("s3box3", "nextPart): %s, width: %d, lineWidth: %d, maxWidth: %d", part.c_str(), width, lineWidth, maxWidth);
                      if (width > maxWidth) { // If adding another character would make the part too long
                          if (!part.empty()) { // Add the current part to the line and start a new part
                              lines.push_back(part);
                          }
                          part = w[i];
                      } else {
                          part += w[i]; // Add the character to the part
                      }
                  }
                  if (!part.empty()) { // Add the last part to the line
                      line.push_back(part); // continue then on the same line as the last part. If not desired use lines.push_back(part)
                      // ESP_LOGE("s3box3", "part): %s, width: %d, lineWidth: %d, maxWidth: %d", part.c_str(), width, lineWidth, maxWidth);
                      lineWidth = width;
                  }
              }
              //-------------------------------------------------  end of the long word handling
              else if (lineWidth + width <= maxWidth) {
                // ESP_LOGE("s3box3", "width: %d, lineWidth: %d, maxWidth: %d", width, lineWidth, maxWidth);
                  lineWidth += width;
                // ESP_LOGE("s3box3", "w: %s, width: %d, lineWidth: %d, maxWidth: %d", w.c_str(), width, lineWidth, maxWidth);
                  line.push_back(w);
              } else {  // concatenate all words in the line
                  std::string lineStr;
                  for (const auto& word : line) {
                      lineStr += word + " ";
                  }
                  // ESP_LOGE("s3box3", "lineStr: %s, width: %d, lineWidth: %d, maxWidth: %d", lineStr.c_str(), width, lineWidth, maxWidth);
                  lines.push_back(lineStr); // store the line
                  line.clear(); // start a new line
                  line.push_back(w);
                  lineWidth = width;
              }
          }
      }
 
      if (!line.empty()) { // handle the last line
          std::string lineStr;
          for (const auto& word : line) {
              lineStr += word + " ";
          }
          lines.push_back(lineStr); // store the last line
      }

      // now you know the number of lines before printing
      int line_count = lines.size();
      int start_line = 0;
      int max_lines_count = int((last_line+line_spacing-first_line)/(line_height+line_spacing)); //last line without spacing after
      if (line_count > max_lines_count) {
        start_line = line_count - max_lines_count -1;
        start_line = start_line - id(list_offset).state * (max_lines_count - 1);
        // start_line = std::max(0, start_line);
        if (start_line < 0) {
          start_line = 0;
          id(list_offset).state = id(list_offset).state - 1;
          // auto call = id(list_offset).make_call();
          // call.number_decrement(false);
          // call.perform();
        }
      }

      // print the lines
      for (int i = start_line, j = 0; i < line_count; ++i, ++j) {
          int y = first_line + j * (int)(line_height + line_spacing);
          std::string& line = lines[i];
          
          size_t lastPos = 0;
          size_t nextPos = std::string::npos;
          int new_begin_position = 0;

          // Find the nearest color code
          for (const auto& code : colorCodes) {
              size_t pos = line.find(code.first, lastPos);
              if (pos != std::string::npos && (nextPos == std::string::npos || pos < nextPos)) {
                  nextPos = pos;
              }
          }

          while (nextPos != std::string::npos) {
              // Print the part of the line before the color code
              std::string textPiece = line.substr(lastPos, nextPos - lastPos);
              it.print(left_offset+new_begin_position, y, used_font, currentColor, textPiece.c_str());

              // Update position
              int x1, y1, width, height;
              it.get_text_bounds(0,0,textPiece.c_str(), used_font, esphome::display::TextAlign::TOP_LEFT, &x1, &y1, &width, &height);
              new_begin_position += width;

              // Update the color based on the color code
              for (const auto& code : colorCodes) {
                  if (line.compare(nextPos, code.first.size(), code.first) == 0) {
                      currentColor = code.second;
                      lastPos = nextPos + code.first.size();
                      break;
                  }
              }

              nextPos = std::string::npos;
              // Find the next nearest color code
              for (const auto& code : colorCodes) {
                  size_t pos = line.find(code.first, lastPos);
                  if (pos != std::string::npos && (nextPos == std::string::npos || pos < nextPos)) {
                      nextPos = pos;
                  }
              }
          }

          // Print the remainder of the line
          std::string textPiece = line.substr(lastPos);
          it.print(left_offset+new_begin_position, y, used_font, currentColor, textPiece.c_str());
      }

color:
  - id: color_red
    red: 1
    green: 0
    blue: 0
  - id: color_green
    red: 0
    green: 1
    blue: 0
  - id: color_black
    red: 0
    green: 0
    blue: 0
  - id: my_blue
    blue: 100%
  - id: my_red
    red: 100%
  - id: my_green
    green: 70%
  - id: my_white
    red: 100%
    blue: 100%
    green: 100%
  - id: my_yellow
    hex: ffff00

Vše co si přinesu domů je buď Shelly, nebo to skončí buď pod ESPhome nebo pod Zigbee2mqtt.
Ajťák co pamatuje BBS a OS/2 Warp a je mu jedno o jaký systém nebo síťařinu běží.
HA OS jako jedna z Proxmox VM na Odroid H3+/64GB https://github.com/tteck/Proxmox

kiklhorn
Moderátor
Moderátor
Příspěvky: 739
Registrován: 03. červenec 2021, 18:35
Dal poděkování: 84 poděkování
Dostal poděkování: 175 poděkování

Re: ESP32-S3-BOX-3

Příspěvek od kiklhorn »

Než se přesunu zpět k hodinkám tak bych rád dodělal pár drobností na displeji.

Aktuální chování je že pokud není zapnutý wake word tak zhasne displej.

Rád bych upravil stavový řádek - hodiny, virtuální LED (udělám zas barevnou jak M5Stack), wifi a baterie je mi jasná

Jakou ikonou indikovat čekání na wake word? A vůbec všechny on_něco automatizace https://esphome.io/components/voice_assistant.html

ikony zde: https://pictogrammers.com/library/mdi/
Vše co si přinesu domů je buď Shelly, nebo to skončí buď pod ESPhome nebo pod Zigbee2mqtt.
Ajťák co pamatuje BBS a OS/2 Warp a je mu jedno o jaký systém nebo síťařinu běží.
HA OS jako jedna z Proxmox VM na Odroid H3+/64GB https://github.com/tteck/Proxmox

Uživatelský avatar
Pete30
Moderátor
Moderátor
Příspěvky: 2901
Registrován: 30. září 2020, 20:33
Dal poděkování: 152 poděkování
Dostal poděkování: 319 poděkování

Re: ESP32-S3-BOX-3

Příspěvek od Pete30 »

Ohledně ikon asi by se hodilo jestli tě dobře chápu, ale je to jen moje představa, třeba bude mít někdo lepší nápad

Kód: Vybrat vše

icon: mdi:account-voice
icon: mdi:account-voice-off
Jinak díky že se tomu věnuješ :like: já teď nemám vůbec čas a ještě se snažím dodělat co tu mám rozhrabané
Pokud nejsem přítomen tak jsem na rybách ;)

Uživatelský avatar
Pete30
Moderátor
Moderátor
Příspěvky: 2901
Registrován: 30. září 2020, 20:33
Dal poděkování: 152 poděkování
Dostal poděkování: 319 poděkování

Re: ESP32-S3-BOX-3

Příspěvek od Pete30 »

Pro ty kteří nechtějí nebo si netroufají hrabat se v kódu yaml :link:
https://esphome.io/projects/
Pokud nejsem přítomen tak jsem na rybách ;)

kiklhorn
Moderátor
Moderátor
Příspěvky: 739
Registrován: 03. červenec 2021, 18:35
Dal poděkování: 84 poděkování
Dostal poděkování: 175 poděkování

Re: ESP32-S3-BOX-3

Příspěvek od kiklhorn »

Popral jsem se s ikonami z google, takže stále není nutný žádný externí soubor
Ikony zleva doprava: Led jak u M5 verze, síla wifi, api připojení, jestli je zapnutý wake word, jestli voice assistant běží, jestli je režim continuous. případně kolik je nalistováno stran od konce výpisu, a hodiny.

Wake word a výpis URL se zapíná na straně HA, možná ještě dodělám přepínátko rovnou na dotykový displej.

Ikony odsud: https://fonts.google.com/icons?icon.pla ... al+Symbols
Glyphs = code point doplněný zleva o \U0000
Více v kódu.

Kód: Vybrat vše

esphome:
  name: s3box3
  friendly_name: S3box3
  platformio_options:
    board_build.flash_mode: dio
  area: test room
  # libraries: [regex]

esp32:
  board: esp32s3box
  flash_size: 16MB
  framework:
    type: esp-idf
    sdkconfig_options:
      CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: "y"
      CONFIG_ESP32S3_DATA_CACHE_64KB: "y"
      CONFIG_ESP32S3_DATA_CACHE_LINE_64B: "y"
      CONFIG_AUDIO_BOARD_CUSTOM: "y"
      CONFIG_ESP32_S3_BOX_3_BOARD: "y"
    components:
      - name: esp32_s3_box_3_board
        source: github://jesserockz/esp32-s3-box-3-board@main
        refresh: 0s


# Enable logging
logger:
  hardware_uart: USB_SERIAL_JTAG
  # level: NONE
  level: DEBUG
  logs:
    component: ERROR
    sensor: WARN

# Enable Home Assistant API
api:
  encryption:
    key: "jgVfSe0zIPTlfAEeM2zt5k2exvNL+6LK10sqsQ9qS3k="
  id: haapi

ota:
  password: "94431478a5e4b99a956cb61f1a9dcda8"

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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "S3Box3 Fallback Hotspot"
    password: "sDHiUghr0220"

captive_portal:

time:
  - platform: homeassistant
    id: hatime
    timezone: Europe/Prague

# dashboard_import:
#   package_import_url: github://esphome/firmware/voice-assistant/esp32-s3-box.yaml@main

substitutions:
    rgbdin: GPIO41 #RGB LEDka
    device_name: "s3box3"

binary_sensor:
  - platform: touchscreen
    name: Top Half Touch Button
    id: top_half_button
    touchscreen_id: gt911_touchscreen
    internal: true
    x_min: 0
    x_max: 340
    y_min: 30
    y_max: 120
    on_click: 
      then:
        - number.increment: 
            id: list_offset
            cycle: False
  - platform: touchscreen
    name: Bottom Half Touch Button
    id: bottom_half_button
    touchscreen_id: gt911_touchscreen
    internal: true
    x_min: 0
    x_max: 340
    y_min: 121
    y_max: 240
    on_click: 
      then:
        - number.decrement: 
            id: list_offset
            cycle: False

  - platform: gpio
    pin:
      number: GPIO1
      inverted: true
    name: "Mute"

  - platform: gpio
    pin:
      number: GPIO0
      mode: INPUT_PULLUP
      inverted: true
    name: Top Left Button
    disabled_by_default: true
    on_click:
      - if:
          condition:
            switch.is_off: use_wake_word
          then:
            - if:
                condition: voice_assistant.is_running
                then:
                  - voice_assistant.stop:
                  - script.execute: reset_led
                else:
                  - voice_assistant.start:
          else:
            - voice_assistant.stop
            - delay: 1s
            - script.execute: reset_led
            - script.wait: reset_led
            - voice_assistant.start_continuous:
      - component.update: my_display

  - platform: status
    id: api_connection
    filters:
      - delayed_on: 3s
    on_press:
      - if:
          condition:
            switch.is_on: use_wake_word
          then:
            - voice_assistant.start_continuous:
    on_release:
      - if:
          condition:
            switch.is_on: use_wake_word
          then:
            - voice_assistant.stop:

output:
  - platform: ledc
    pin: GPIO47
    id: backlight_output
    frequency: 19531Hz

light:
  - platform: monochromatic
    output: backlight_output
    name: LCD Backlight
    id: led
    restore_mode: ALWAYS_OFF
    disabled_by_default: true
    default_transition_length: 0s
    effects:
      - pulse:
          name: "Slow Pulse"
          transition_length: 250ms
          update_interval: 250ms
      - pulse:
          name: "Fast Pulse"
          transition_length: 50ms
          update_interval: 50ms
  - platform: esp32_rmt_led_strip
    id: rgbled
    name: None
    disabled_by_default: true
    entity_category: config
    pin: $rgbdin
    default_transition_length: 0s
    chipset: SK6812
    num_leds: 1
    rgb_order: grb
    rmt_channel: 0
    effects:
      - pulse:
          transition_length: 250ms
          update_interval: 250ms
    on_state: 
      then:
        - component.update: my_display

microphone:
  - platform: esp_adf
    id: box_mic

speaker:
  - platform: esp_adf
    id: box_speaker

voice_assistant:
  id: va
  microphone: box_mic
  speaker: box_speaker
  use_wake_word: true
  noise_suppression_level: 2
  auto_gain: 31dBFS
  volume_multiplier: 2.0
  vad_threshold: 3
  on_listening:
    - light.turn_on:
        id: led
        brightness: 100%
        effect: "Slow Pulse"
  on_tts_start:
    - light.turn_on:
        id: led
        brightness: 75%
        effect: "Slow Pulse"
    - light.turn_on:
        id: rgbled
        blue: 0%
        red: 0%
        green: 100%
        brightness: 100%
        effect: pulse        
    - text_sensor.template.publish:
        id: textline
        state: !lambda |
          return id(textline).state + "\n<black>" + id(hatime).now().strftime("%X") + " " + x;
  on_end:
    - delay: 100ms
    - wait_until:
        not:
          speaker.is_playing:
    - script.execute: reset_led
  on_error:
    - light.turn_on:
        id: led
        brightness: 50%
        effect: "Fast Pulse"
    - light.turn_on:
        id: rgbled
        blue: 0%
        red: 100%
        green: 0%
        brightness: 100%
        effect: none
    - delay: 1s
    - script.execute: reset_led
    - script.wait: reset_led
    - lambda: |-
        if (code == "wake-provider-missing" || code == "wake-engine-missing") {
          id(use_wake_word).turn_off();
        }
        if (message == "Could not request start.") {
          id(restart_script).execute();
        } 
        if (message == "Unexpected error during wake-word-detection") {
          id(restart_script).execute();
        }
    - text_sensor.template.publish:
        id: textline
        state: !lambda |
          return id(textline).state + "\n<red>" + id(hatime).now().strftime("%X") + " " + code;
    - text_sensor.template.publish:
        id: textline
        state: !lambda |
          return id(textline).state + "\n<red>" + id(hatime).now().strftime("%X") + " " + message;
  on_stt_end:
    - text_sensor.template.publish:
        id: textline
        state: !lambda |
          return id(textline).state + "\n<green>" + id(hatime).now().strftime("%X") + " " + x;
  on_tts_end:
    - if:
        condition:
          switch.is_on: show_url
        then:
          - text_sensor.template.publish:
              id: textline
              state: !lambda |
                return id(textline).state + "\n<black>" + x;
  #       # state: !lambda return (strftime("%X", id(hatime).now())+" "+ message);
  #       # state: !lambda return ("Karel "+ x);
  #       # state: !lambda return strftime("%X", id(hatime).now().timestamp());
  #       # state: !lambda return id(hatime).now().timestamp_local().strftime("%X");
  #       state: !lambda return id(hatime).now().to_local_time_string();
  #       # state: !lambda return x;
  on_client_connected:
    - if:
        condition:
          switch.is_on: use_wake_word
        then:
          - voice_assistant.start_continuous:
          - script.execute: reset_led
  on_client_disconnected:
    - if:
        condition:
          switch.is_on: use_wake_word
        then:
          - voice_assistant.stop:
          - light.turn_off: led

script:
  - id: reset_led
    then:
      - if:
          condition:
            switch.is_on: use_wake_word
          then:
            - light.turn_on:
                id: led
                brightness: 25%
                effect: none
            - light.turn_on:
                id: rgbled
                blue: 100%
                red: 100%
                green: 0%
                brightness: 100%
                effect: none
          else:
            - light.turn_off: rgbled
            - light.turn_on:
                id: led
                brightness: 25%
                effect: none

  - id: restart_script
    then:
      - voice_assistant.stop
      - delay: 1s
      # - text_sensor.template.publish:
      #     id: line4
      #     state: " "
      # - text_sensor.template.publish:
      #     id: line5
      #     state: " "
      - voice_assistant.start_continuous

switch:
  - platform: restart
    name: "Restart $device_name"
  - platform: safe_mode
    name: "Restart (Safe Mode) $device_name"  
  - platform: template
    name: "Use wake word"
    id: use_wake_word
    optimistic: true
    restore_mode: RESTORE_DEFAULT_ON
    entity_category: config
    on_turn_on:
      - lambda: id(va).set_use_wake_word(true);
      - if:
          condition:
            not:
              - voice_assistant.is_running
          then:
            - voice_assistant.start_continuous
      - script.execute: reset_led
    on_turn_off:
      - voice_assistant.stop
      - lambda: id(va).set_use_wake_word(false);
      - script.execute: reset_led
  - platform: template
    restore_mode: RESTORE_DEFAULT_ON
    id: show_url
    name: Show URL
    optimistic: true
    entity_category: config

external_components:
  - source: github://pr#5230
  # - source: github://kiklhorn/esphome
    components: esp_adf
    refresh: 0s
  - source: github://kiklhorn/components
    components: gt911
    refresh: 0s
  # - source: 
  #     type: local
  #     path: components
  #   components: gt911

i2c:
  id: i2cbus
  sda: GPIO8
  scl: GPIO18
  scan: True
  frequency: 400kHz

# [18:13:55][I][i2c.idf:077]: Results from i2c bus scan:
# [18:13:55][I][i2c.idf:083]: Found i2c device at address 0x18 ES8311 Audio Controller
# [18:13:55][I][i2c.idf:083]: Found i2c device at address 0x40 ES7210 MIC ADC // ATECC608A-TNGTLSU-B ??? 7bit
# [18:13:55][I][i2c.idf:083]: Found i2c device at address 0x5D ??? Touchscreen CTP_2.0inch ???  GT911!!!  ATECC608A-TNGTLSU-B
# [18:13:55][I][i2c.idf:083]: Found i2c device at address 0x68 ICM-42607-P 6 axis IMU
  # define BSP_I2C_EXPAND_SCL      (GPIO_NUM_40)
  # define BSP_I2C_EXPAND_SDA      (GPIO_NUM_41)

  # define BSP_RADAR_OUT_IO        (GPIO_NUM_21)
  # define BSP_IR_CTRL_GPIO        (GPIO_NUM_44)
  # define BSP_IR_TX_GPIO          (GPIO_NUM_39)
  # define BSP_IR_RX_GPIO          (GPIO_NUM_38)

touchscreen:
  - platform: gt911
    display: my_display
    id: gt911_touchscreen
    interrupt_pin: GPIO3
    # on_touch:
    #   then:
    #   - text_sensor.template.publish:
    #       id: textline
    #       state: !lambda |
    #         return id(textline).state + "\n<white>" + id(hatime).now().strftime("%X") + " Touch " + " X: " + std::to_string(touch.x) + " Y: " + std::to_string(touch.y); //# x,y,id,state


# external_components:
#   - source:
#       type: local
#       path: my_components
#     components: [gt911]
# https://github.com/jesserockz/m5paper-esphome/tree/main/components/gt911
# https://components.espressif.com/components/espressif/esp_lcd_touch_gt911
# touchscreen:
#   - platform: gt911
#     #display: 
#     id: gt911_touchscreen
#     interrupt_pin: GPIO36
#     on_touch:
#       - logger.log:
#           format: Touch at (%d, %d)
#           args: [touch.x, touch.y]

esp_adf:
  board: esp32s3box3
  # board: esp32s3box

psram:
  mode: octal
  speed: 120MHz

font:
  - id: font1
    size: 20
    file: "gfonts://Roboto Condensed"
    glyphs: "?!%()+,-/\\_.:;°<>0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZŽŠČŘĎŤŇĚÁÍÉÓÚŮÝ abcdefghijklmnopqrstuvwxyzμžščřďťňáéěíóúůý₂³\n"

  - id: ikony
    size: 35
    # file: "gfonts://Material Icons"
    file: 'gfonts://Material+Symbols+Outlined'
    # mdi-icons google - https://fonts.google.com/icons?selected=Material+Symbols+Outlined:network_wifi_1_bar:FILL@0;wght@400;GRAD@0;opsz@24&icon.query=network+wifi+&icon.platform=web
    glyphs: [
      "\U0000e157", # link
      "\U0000e16f", # link off
      "\U0000e91f", # record voice over
      "\U0000e94a", # voice over off
      "\U0000f0b0", # signal wifi 0 bar
      "\U0000ebe4", # network wifi 1 bar
      "\U0000ebd6", # network wifi 2 bar
      "\U0000ebe1", # network wifi 3 bar
      "\U0000e1ba", # network wifi
      "\U0000e1d8", # signal wifi 4 bar
      "\U0000eb94", # ABC
      "\U0000ebea", # spatial tracking
      "\U0000eb3d", # all inclusive
      "\U0000e566", # directions run
      "\U0000e9b1", # hail
      "\U0000f8e2", # man 3
      "\U0000e84e", # accessibility

    ]

    # mdi-icons, no google font
    # glyphs: [
    #   '\U0000e425', # mdi-timer"
    #   '\U0000F05CB', # 󰗋 mdi-account-voice
    #   󰻔, # F0ED4, mdi-account-voice-off
    #   󱂛, # api
    #   󱉗, # api-off

    #   # Wifi
    #   '󰤯', # F092F mdi-wifi-strength-outline
    #   '󰤟', # F091F mdi-wifi-strength-1
    #   '󰤢', # F0922 mdi-wifi-strength-2
    #   '󰤥', # F0925 mdi-wifi-strength-3
    #   '󰤨', # F0928 mdi-wifi-strength-4

    #   ]

text_sensor:
  - id: textline
    platform: template
    on_value:
      then:
        - component.update: my_display

number:
  - platform: template
    id: list_offset
    optimistic: True
    step: 1
    min_value: 0
    max_value: 1000
    on_value: 
      then:
        - component.update: my_display
    initial_value: 0
    restore_value: False


  # - platform: debug
  #   device:
  #     name: "Device Info"
  #   reset_reason:
  #     name: "Reset Reason"

# debug:
#   update_interval: 5s

sensor:
  - platform: wifi_signal
    id: wifisignal
    name: "WiFi Signal Sensor"
    update_interval: 5s # default 60s, neni nejlepší nápad nechat internal:false a nízký update interval - zbytečný provoz mqtt
    internal: true
    on_value: 
      then:
        - component.update: my_display

spi:
  clk_pin: GPIO7
  mosi_pin: GPIO6

display:
  - platform: ili9xxx
    model: S3BOX
    rotation: 0
    update_interval: 30s
    id: my_display
    # backlight_pin: GPIO4 #tento parametr mohu vynechat, a na GPIO4 pověsit PWM a řídit jas
    cs_pin: GPIO5
    dc_pin: GPIO4
    # reset_pin: GPIO48 #Negation needed...
    reset_pin:
      number: 48
      inverted: true

    # https://esphome.io/api/light__state_8cpp_source
    lambda: |-
      // 'batman', 32x13px
      const unsigned char batman [] PROGMEM = {
      0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xf9, 0xff, 0xfe, 0x3e, 0x7c, 0x7f, 0xf8, 0x3c, 0x3c, 0x1f, 
      0xf0, 0x1c, 0x38, 0x0f, 0xf0, 0x00, 0x00, 0x0f, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x07, 
      0xf0, 0x00, 0x00, 0x0f, 0xf0, 0xc4, 0x23, 0x0f, 0xf9, 0xfe, 0x7f, 0x9f, 0xfc, 0xfe, 0x7f, 0x3f, 
      0xff, 0xff, 0xff, 0xff
      };
      //it.image(0, 0, &batman);

      int x,y, yo, offs;
      int gradient_max = 190; double gradient_step = double(gradient_max)/it.get_height();
      for(auto i = 0; i<it.get_height(); i++) {it.horizontal_line(0,i,it.get_width(), my_blue.fade_to_white(i*gradient_step));}
      it.rectangle(0, 0, it.get_width(), it.get_height(),color_red);
      // it.printf(80, 9, id(font1), "ESP32-S3-BOX-3");
      //it.printf(45,9, id(ikony), id(use_wake_word).state ? "󰗋" : "󰻔" );
      //it.printf(60,9, id(ikony), id(haapi).is_connected() ? "󱂛" : "󱉗");
      
      
      // it.printf(45,9, id(ikony), color_red, id(use_wake_word).state ? "\U0000e91f" : "\U0000e94a" );
      // it.printf(60,9, id(ikony), color_red, "%s", id(haapi).is_connected() ? "\U0000e157" : "\U0000e16f" );
      // it.printf(60,9, id(ikony), color_red, id(haapi).is_connected() ? "\U0000e157" : "\U0000e16f" );

      // WiFi Signal Strenght 
      x = 40, y = 0;
      if(id(wifisignal).has_state()) {
        // it.printf(0,10, id(ikony), " %.0f db", id(wifisignal).state);
        if (id(wifisignal).state >= -45) {
            //Excellent
            it.print(x, y, id(ikony), TextAlign::TOP_LEFT, "\U0000e1d8");
        } else if (id(wifisignal).state >= -50) {
            //Excellent
            it.print(x, y, id(ikony), TextAlign::TOP_LEFT, "\U0000e1ba");            
          //  ESP_LOGI("WiFi", "Exellent");
        } else if (id(wifisignal).state  >= -60) {
            //Good
            it.print(x, y, id(ikony), TextAlign::TOP_LEFT, "\U0000ebe1");
          //  ESP_LOGI("WiFi", "Good");
        } else if (id(wifisignal).state  >= -67) {
            //Fair
            it.print(x, y, id(ikony), TextAlign::TOP_LEFT, "\U0000ebd6");
          //  ESP_LOGI("WiFi", "Fair");
        } else if (id(wifisignal).state  >= -70) {
            //Weak
            it.print(x, y, id(ikony), TextAlign::TOP_LEFT, "\U0000ebe4");
          //  ESP_LOGI("WiFi", "Weak");
        } else {
            //Unlikely working signal
            it.print(x, y, id(ikony), TextAlign::TOP_LEFT, "\U0000f0b0");
          //  ESP_LOGI("WiFi", "Unlikely");
        }
      }      
      
      x=75, y=0;
      if (id(haapi).is_connected()) {
        it.print(x,y, id(ikony), color_white, "\U0000e157" );
      } else {
        it.print(x,y, id(ikony), color_red, "\U0000e16f" );
      }

      x=110, y=0;
      if (id(use_wake_word).state) {
        it.print(x,y, id(ikony), color_white, "\U0000e91f" );
      } else {
        it.print(x,y, id(ikony), color_red, "\U0000e94a" );
      }

      x=145, y=0;
      if (id(va).is_running()) {
        it.print(x,y, id(ikony), color_white, "\U0000e566" );
      } else {
        it.print(x,y, id(ikony), color_red, "\U0000e84e" );
      }

      x=170, y=0;
      if (id(va).is_continuous()) {
        it.print(x,y, id(ikony), color_white, "\U0000eb3d" );
      } else {
        it.print(x,y, id(ikony), color_red, "\U0000eb3d" );
      }
      /*
      "\U0000eb3d", # all inclusive
      "\U0000e566", # directions run
      "\U0000e9b1", # hail
      "\U0000f8e2", # man 3
      "\U0000e84e", # accessibility
      */
      it.strftime(it.get_width()-2, 10, id(font1), color_green, TextAlign::TOP_RIGHT, "%H:%M", id(hatime).now());
      auto ledcolor = Color(id(rgbled).remote_values.get_red()*255, id(rgbled).remote_values.get_green()*255, id(rgbled).remote_values.get_blue()*255);
      bool ledstatus = id(rgbled).remote_values.get_state();

      it.filled_circle(19, 19, 15, ledstatus ? ledcolor : color_black);
      it.printf(217, 9, id(font1), int(id(list_offset).state) ? "P: -%d" : " ", int(id(list_offset).state));


      x = 2, yo = 43, offs = 34;
      // it.printf(x, yo, id(font1), "%s", id(line1).state.c_str());
      // it.printf(x, yo=yo+offs, id(font1), "%s", id(line2).state.c_str());
      // it.printf(x, yo=yo+offs, id(font1), "%s", id(line3).state.c_str());
      // it.printf(x, yo=yo+offs, id(font1), color_red, "%s", id(line4).state.c_str());
      // it.printf(x, yo=yo+offs, id(font1), color_red, "%s", id(line5).state.c_str());

      // # https://esphome.io/api/classesphome_1_1display_1_1_display.html

      // ----------------------------  user defined parameters start here --------------------------
      std::string text = id(textline).state.c_str();
      auto used_font = id(font1);
      int line_spacing = -5, line_height = used_font->get_height(); // -5 is usable spacing for dense lines
      int first_line = 34; // "console" text output begins on "first_line". [pixels from top of the display]
      int last_line =  it.get_height() - line_height; //start of last text line (top left corner)
      int left_offset = 3, right_offset = 0;

      std::map<std::string, esphome::Color> colorCodes {  // if left side found in the text then use previously user defined color (or define color directly here) from this map
        {"<red>", color_red},
        {"<green>", color_green},
        {"<black>", color_black},
        {"<blue>", esphome::Color(0, 0, 255)},
        {"<white>", esphome::Color(255, 255, 255)},
        // add more color codes here
      };

      esphome::Color currentColor = color_black; // default color

      // ----------------------------  user defined parameters end --------------------------

      std::vector<std::string> words;
      std::vector<std::string> line;
      std::vector<std::string> lines;      
      //std::slre r1(R"(([^\\ ])\n)");
      //text = std::regex_replace(text, r1, "$1 \n"); // pokud není před \n mezera nebo \ doplň před \n mezeru
      //std::regex r2(R"(\n([^ ]))");
      //text = std::regex_replace(text, r2, "\n $1"); // pokud není před \n znak \ a zároveň za \n není mezera doplň za \n mezeru 

      for (size_t i = 0; i < text.size(); ++i) { // separate \n from letters around but keep \\n untouched
        if (text[i] == '\n') {
          if (i > 0 && text[i-1] != ' ' && (i < 2 || text[i-2] != '\\')) {
            text.insert(i, " ");
            ++i;
          }
          if (i+1 < text.size() && text[i+1] != ' ' && text[i] != '\\') {
            text.insert(i+1, " ");
            ++i;
          }
        }
      }

      auto cleanStringWidth = [&it, &colorCodes, &used_font](std::string input) -> int {
          for (const auto& code : colorCodes) {
              size_t foundPos = input.find(code.first);
              if (foundPos != std::string::npos) {
                  input.erase(foundPos, code.first.length());
              }
          }

          int x1, y1, width, height;
          it.get_text_bounds(0, 0, (input+" ").c_str(), used_font, esphome::display::TextAlign::TOP_LEFT, &x1, &y1, &width, &height);

          return width;
      };

      size_t pos = 0;
      std::string token;
      while ((pos = text.find(" ")) != std::string::npos) {
        token = text.substr(0, pos);
        words.push_back(token);
        text.erase(0, pos + 1);
      }
      words.push_back(text); // Add the last word

      int maxWidth = it.get_width() - left_offset - right_offset;
      int lineWidth = 0;
      std::string space = " ";

      for (const auto& w : words) {
          int x1, y1, width, height;

          if (w == "\n") { // Check if the current word is a line break - it§s allready whole words only
              std::string lineStr; // If it's a line break, start a new line
              for (const auto& word : line) {
                lineStr += word + " ";
              }
              lines.push_back(lineStr);
              line.clear();
              lineWidth = 0;
          } else {  //word isn§t a line break
          // If the word is too long, split it
              // ------------------------------------------------
              width = cleanStringWidth(w);
              if (width > maxWidth) { // If the word is longer than the max width
                    if (!line.empty()) { // handle the last line out of order
                        std::string lineStr;
                        for (const auto& word : line) {
                            lineStr += word + " ";
                        }
                        line.push_back(lineStr); // store the last line
                    }
                  std::string part; // Split the word into smaller parts
                  if (!line.empty()) { 
                    part = line.back(); line.clear();
                  }
                  // ESP_LOGE("custom", "part: %s, width: %d, lineWidth: %d", part.c_str(), width, lineWidth); //ok, this is the last, not full, line before long word
                  for (int i = 0; i < w.size(); i++) {
                      std::string nextPart = part + w[i];
                      if (lineWidth) {
                        width = lineWidth;
                        lineWidth = 0;
                        // ESP_LOGE("s3box3", "width: %d, lineWidth: %d, maxWidth: %d -----------------------------------------------------", width, lineWidth, maxWidth);
                      } else {
                        width = cleanStringWidth(nextPart);
                      }
                      // ESP_LOGE("s3box3", "nextPart): %s, width: %d, lineWidth: %d, maxWidth: %d", part.c_str(), width, lineWidth, maxWidth);
                      if (width > maxWidth) { // If adding another character would make the part too long
                          if (!part.empty()) { // Add the current part to the line and start a new part
                              lines.push_back(part);
                          }
                          part = w[i];
                      } else {
                          part += w[i]; // Add the character to the part
                      }
                  }
                  if (!part.empty()) { // Add the last part to the line
                      line.push_back(part); // continue then on the same line as the last part. If not desired use lines.push_back(part)
                      // ESP_LOGE("s3box3", "part): %s, width: %d, lineWidth: %d, maxWidth: %d", part.c_str(), width, lineWidth, maxWidth);
                      lineWidth = width;
                  }
              }
              //-------------------------------------------------  end of the long word handling
              else if (lineWidth + width <= maxWidth) {
                // ESP_LOGE("s3box3", "width: %d, lineWidth: %d, maxWidth: %d", width, lineWidth, maxWidth);
                  lineWidth += width;
                // ESP_LOGE("s3box3", "w: %s, width: %d, lineWidth: %d, maxWidth: %d", w.c_str(), width, lineWidth, maxWidth);
                  line.push_back(w);
              } else {  // concatenate all words in the line
                  std::string lineStr;
                  for (const auto& word : line) {
                      lineStr += word + " ";
                  }
                  // ESP_LOGE("s3box3", "lineStr: %s, width: %d, lineWidth: %d, maxWidth: %d", lineStr.c_str(), width, lineWidth, maxWidth);
                  lines.push_back(lineStr); // store the line
                  line.clear(); // start a new line
                  line.push_back(w);
                  lineWidth = width;
              }
          }
      }
 
      if (!line.empty()) { // handle the last line
          std::string lineStr;
          for (const auto& word : line) {
              lineStr += word + " ";
          }
          lines.push_back(lineStr); // store the last line
      }

      // now you know the number of lines before printing
      int line_count = lines.size();
      int start_line = 0;
      int max_lines_count = int((last_line+line_spacing-first_line)/(line_height+line_spacing)); //last line without spacing after
      if (line_count > max_lines_count) {
        start_line = line_count - max_lines_count -1;
        start_line = start_line - id(list_offset).state * (max_lines_count - 1);
        // start_line = std::max(0, start_line);
        if (start_line < 0) {
          start_line = 0;
          id(list_offset).state = id(list_offset).state - 1;
          // auto call = id(list_offset).make_call();
          // call.number_decrement(false);
          // call.perform();
        }
      }

      // print the lines
      for (int i = start_line, j = 0; i < line_count; ++i, ++j) {
          int y = first_line + j * (int)(line_height + line_spacing);
          std::string& line = lines[i];
          
          size_t lastPos = 0;
          size_t nextPos = std::string::npos;
          int new_begin_position = 0;

          // Find the nearest color code
          for (const auto& code : colorCodes) {
              size_t pos = line.find(code.first, lastPos);
              if (pos != std::string::npos && (nextPos == std::string::npos || pos < nextPos)) {
                  nextPos = pos;
              }
          }

          while (nextPos != std::string::npos) {
              // Print the part of the line before the color code
              std::string textPiece = line.substr(lastPos, nextPos - lastPos);
              it.print(left_offset+new_begin_position, y, used_font, currentColor, textPiece.c_str());

              // Update position
              int x1, y1, width, height;
              it.get_text_bounds(0,0,textPiece.c_str(), used_font, esphome::display::TextAlign::TOP_LEFT, &x1, &y1, &width, &height);
              new_begin_position += width;

              // Update the color based on the color code
              for (const auto& code : colorCodes) {
                  if (line.compare(nextPos, code.first.size(), code.first) == 0) {
                      currentColor = code.second;
                      lastPos = nextPos + code.first.size();
                      break;
                  }
              }

              nextPos = std::string::npos;
              // Find the next nearest color code
              for (const auto& code : colorCodes) {
                  size_t pos = line.find(code.first, lastPos);
                  if (pos != std::string::npos && (nextPos == std::string::npos || pos < nextPos)) {
                      nextPos = pos;
                  }
              }
          }

          // Print the remainder of the line
          std::string textPiece = line.substr(lastPos);
          it.print(left_offset+new_begin_position, y, used_font, currentColor, textPiece.c_str());
      }

color:
  - id: color_red
    red: 1
    green: 0
    blue: 0
  - id: color_green
    red_int: 0
    green_int: 255
    blue_int: 0
  - id: color_black
    red: 0
    green: 0
    blue: 0
  - id: my_blue
    blue: 100%
  - id: my_red
    red: 100%
  - id: my_green
    green: 70%
  - id: color_white
    red: 100%
    blue: 100%
    green: 100%
  - id: my_yellow
    hex: ffff00

Vše co si přinesu domů je buď Shelly, nebo to skončí buď pod ESPhome nebo pod Zigbee2mqtt.
Ajťák co pamatuje BBS a OS/2 Warp a je mu jedno o jaký systém nebo síťařinu běží.
HA OS jako jedna z Proxmox VM na Odroid H3+/64GB https://github.com/tteck/Proxmox

Odpovědět

Zpět na „HW pro Probouzecí slovo“