Умный дом на Home Assistant и 3D печать.
 
Telegram-канал FoxJoy
Почта: dm@foxfoy.ru
WiFi колонка на esp32-s3 для Home Assistant

WiFi колонка на esp32-s3 для Home Assistant

Была идея сделать в доме по этажам аудио оповещение по различным автоматизациям, или включать музыку или потоковое Интернет-радио. Идея с блютуз мне не зашла, в том числе и потому, что такие устройства просто иногда отваливались из HA и приходилось через командную строку их снюхивать. Ещё были мысли в последствии сделать Мультирум и WiFi колонки как раз под всё это хорошо подходили. Я пошёл простым путём сделал вариант WiFi колонки на ESP32-S3-N16R8 + PCM5102A. И к выходу подключил пару активных колонок для ПК.

Второй вариант тоже ESP32-S3-N16R8 + ЦАП I2S PCM5102A, но так как у меня были 2 пассивных колонки типа S30, то задействовал плату двухканального усилителя XH-M543, класс D, 2*120 Вт. Усилитель неплох по звуку, не шумит, не дорогой. Конечно же 120 Вт он не выдаст, но ватт около тридцати на канал наверное может, при соответствующем питании.

Всё это можно разместить в корпусе распечатанном на 3D принтере, а можно и засунуть в какую ни будь китайскую колонку. Кому как удобнее.

Прошивал в ESPHome.
Пример прошивки, в которой с помощью ИИ удалось запустить PSRAM и работу процессора на 240Mhz (по умолчанию почему-то работал на 160) н у и плюс некоторые улучшалочки и датчики, которые смело можно удалить - они нужны были только для тестов:

# Колонка 3й этаж 192.168.1.55 

esphome:
  name: esp32-s3-3
  on_boot:
    priority: 900
    then:
      - lambda: |-
          setCpuFrequencyMhz(240);
          ESP_LOGI("main", "Частота процессора принудительно установлена: %d MHz", getCpuFrequencyMhz());
  friendly_name: ESP32-S3-3
  platformio_options:
    board_build.arduino.memory_type: opi_opi
    board_build.flash_mode: opi
    board_build.f_cpu: 240000000L  # Поднимаем до 240МГц (стандарт S3)
    build_flags:
      - -DBOARD_HAS_PSRAM
      - -DARDUINO_USB_CDC_ON_BOOT=1
      # Увеличиваем размер буфера приема WiFi (в байтах)
      - -DCONFIG_WL_DYNAMIC_BUFFER_SIZE=512
      - -DCONFIG_LWIP_WND_STEADY=1024
esp32:
  board: esp32-s3-devkitc-1
  variant: esp32s3
  flash_size: 16MB
  framework:
    type: arduino
# Настройка PSRAM с указанием режима Octal (для R8)
psram:
  mode: octal
debug:
# Enable Home Assistant API
api:
  encryption:
    key: "Ваш ключ"
ota:
  - platform: esphome
    password: "Ваш пароль"
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  power_save_mode: NONE
  fast_connect: true
  # Вместо threshold используем принудительное отключение роуминга
  # Это заставит ESP32 сидеть на одной точке и не "оглядываться"
  enable_on_boot: true
  reboot_timeout: 0s
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Esp32-S3-3 Fallback Hotspot"
    password: "Пароль"
# Настройка I2S выхода
i2s_audio:
  id: my_i2s
  i2s_lrclk_pin: GPIO18   # LRCLK 
  i2s_bclk_pin: GPIO17    # BCLK 

# Настройка воспроизведения аудио
media_player:
  - platform: i2s_audio
    name: "ESPHome PCM5102A Media Player-3"  # Название медиаплеера
    id: media_player_pcm5102a3               # ID медиаплеера
    dac_type: external                       # Указываем, что используется внешний DAC
    i2s_dout_pin: GPIO16                     # DOUT (Data Output)
    i2s_audio_id: my_i2s
    mode: stereo                             # Режим работы: стерео
    # Вместо buffer_size здесь используются настройки HTTP-потока:
    on_play:
      - lambda: |-
          ESP_LOGI("main", "Плеер запущен, используется PSRAM буфер");
# Веб-сервер и датчики
web_server:
  port: 80
captive_portal:
# Это заставит систему более агрессивно использовать память для сетевых запросов
http_request:
  useragent: esphome/radio
  timeout: 15s
# Текстовый сенсор для проверки статуса
text_sensor:
  - platform: template
    name: "PSRAM Status"
    lambda: |-
      if (psramFound()) {
        return {"Active (8MB OPI)"};
      } else {
        return {"Not Found / Disabled"};
      }
    update_interval: 60s
logger:
  level: DEBUG
# Датчик уровня сигнала Wi-Fi
sensor:
  - platform: wifi_signal
    name: "PCM5102A Wi-Fi Signal"
    id: pcm5102a_wifi_rssi
    update_interval: 320s
  - platform: debug
    free:
      name: "Heap Free"
    psram:
      name: "PSRAM Free Space"
  - platform: debug
    free:
      name: "Heap Min Free"
    psram:
      name: "PSRAM Min Free"
  - platform: template
    name: "Actual CPU Speed"
    unit_of_measurement: "MHz"
    lambda: |-
      return (float)ESP.getCpuFreqMHz();
    update_interval: 10s
binary_sensor:
  - platform: status
    name: "Device Status" 

Без этих, небольших мытарств, колонка не работала нормально - заикалась даже на потоках 192 - 256 kBits. Возможно у меня в прошивке не совсем всё правильно и красиво, но оно работает отлично! 

Пример простой карточки для HA - отправка потока Интернет-радио:


type: entities 

title: Колонка Зал
entities:
  - entity: media_player.esp32_s3_esphome_pcm5102a_media_player
    name: ESP32 Media Player
    icon: mdi:radio
    show_state: true
  - type: button
    name: Set Default Volume (0.60)
    tap_action:
      action: call-service
      service: media_player.volume_set
      service_data:
        entity_id: media_player.esp32_s3_esphome_pcm5102a_media_player
        volume_level: 0.6
    icon: mdi:volume-high
  - type: button
    name: Play European Hit Radio
    tap_action:
      action: call-service
      service: media_player.play_media
      service_data:
        entity_id: media_player.esp32_s3_esphome_pcm5102a_media_player
        media_content_id: http://stream.europeanhitradio.com:8000/Stream_25.mp3
        media_content_type: audio/mpeg
      sequence:
        - service: media_player.volume_set
          data:
            entity_id: media_player.esp32_s3_esphome_pcm5102a_media_player
            volume_level: 0.6
        - service: media_player.play_media
          data:
            entity_id: media_player.esp32_s3_esphome_pcm5102a_media_player
            media_content_id: http://stream.europeanhitradio.com:8000/Stream_25.mp3
            media_content_type: audio/mpeg
    icon: mdi:play
  - type: button
    name: Play 3FM (Netherlands)
    tap_action:
      action: call-service
      service: media_player.play_media
      service_data:
        entity_id: media_player.esp32_s3_esphome_pcm5102a_media_player
        media_content_id: http://icecast.omroep.nl/3fm-bb-mp3
        media_content_type: audio/mpeg
      sequence:
        - service: media_player.volume_set
          data:
            entity_id: media_player.esp32_s3_esphome_pcm5102a_media_player
            volume_level: 0.6
        - service: media_player.play_media
          data:
            entity_id: media_player.esp32_s3_esphome_pcm5102a_media_player
            media_content_id: http://icecast.omroep.nl/3fm-bb-mp3
            media_content_type: audio/mpeg
    icon: mdi:play
  - type: button
    name: SomaFM Covers
    tap_action:
      action: call-service
      service: media_player.play_media
      service_data:
        entity_id: media_player.esp32_s3_esphome_pcm5102a_media_player
        media_content_id: http://ice1.somafm.com/covers-128-mp3
        media_content_type: audio/mpeg
      sequence:
        - service: media_player.volume_set
          data:
            entity_id: media_player.esp32_s3_esphome_pcm5102a_media_player
            volume_level: 0.6
        - service: media_player.play_media
          data:
            entity_id: media_player.esp32_s3_esphome_pcm5102a_media_player
            media_content_id: http://ice1.somafm.com/covers-128-mp3
            media_content_type: audio/mpeg
    icon: mdi:play
  - type: button
    name: Шоколад 98FM
    tap_action:
      action: call-service
      service: media_player.play_media
      service_data:
        entity_id: media_player.esp32_s3_esphome_pcm5102a_media_player
        media_content_id: https://choco.hostingradio.ru:10010/fm
        media_content_type: audio/mpeg
      sequence:
        - service: media_player.volume_set
          data:
            entity_id: media_player.esp32_s3_esphome_pcm5102a_media_player
            volume_level: 0.6
        - service: media_player.play_media
          data:
            entity_id: media_player.esp32_s3_esphome_pcm5102a_media_player
            media_content_id: https://choco.hostingradio.ru:10010/fm
            media_content_type: audio/mpeg
    icon: mdi:play
  - type: button
    name: Wow Music Cover
    tap_action:
      action: call-service
      service: media_player.play_media
      service_data:
        entity_id: media_player.esp32_s3_esphome_pcm5102a_media_player
        media_content_id: https://stream.vyshka24.ru/wowcover
        media_content_type: audio/mpeg
      sequence:
        - service: media_player.volume_set
          data:
            entity_id: media_player.esp32_s3_esphome_pcm5102a_media_player
            volume_level: 0.6
        - service: media_player.play_media
          data:
            entity_id: media_player.esp32_s3_esphome_pcm5102a_media_player
            media_content_id: https://stream.vyshka24.ru/wowcover
            media_content_type: audio/mpeg
    icon: mdi:play
  - type: button
    name: BossaNova
    tap_action:
      action: call-service
      service: media_player.play_media
      service_data:
        entity_id: media_player.esp32_s3_esphome_pcm5102a_media_player
        media_content_id: http://strm112.1.fm/bossanova_mobile_mp3
        media_content_type: audio/mpeg
      sequence:
        - service: media_player.volume_set
          data:
            entity_id: media_player.esp32_s3_esphome_pcm5102a_media_player
            volume_level: 0.6
        - service: media_player.play_media
          data:
            entity_id: media_player.esp32_s3_esphome_pcm5102a_media_player
            media_content_id: http://strm112.1.fm/bossanova_mobile_mp3
            media_content_type: audio/mpeg
    icon: mdi:play
  - type: button
    name: Stop Playback
    tap_action:
      action: call-service
      service: media_player.media_stop
      service_data:
        entity_id: media_player.esp32_s3_esphome_pcm5102a_media_player
    icon: mdi:stop

Ну и понятно, что на это устройство можно отправить музыку из локального хранилища или TTS.

Пример автоматизации отправки mp3 файла и TTS (в нём вначале устанавливает нужную громкость):

alias: Время 12:00 Оповещение 

description: ""
triggers:
  - trigger: time
    at: "12:00:00"
conditions: []
actions:
  - action: media_player.volume_set
    target:
      entity_id:
        - media_player.esp32_s3_esphome_pcm5102a_media_player
    data:
      volume_level: 0.4
  - action: media_player.play_media
    target:
      entity_id: media_player.esp32_s3_esphome_pcm5102a_media_player
    data:
      media:
        media_content_id: media-source://media_source/local/mpd/media/airport-bell.mp3
        media_content_type: audio/mpeg
        metadata:
          title: airport-bell.mp3
          thumbnail: null
          media_class: music
          children_media_class: null
          navigateIds:
            - {}
            - media_content_type: app
              media_content_id: media-source://media_source
            - media_content_type: ""
              media_content_id: media-source://media_source/local/mpd
            - media_content_type: ""
              media_content_id: media-source://media_source/local/mpd/media
  - delay:
      hours: 0
      minutes: 0
      seconds: 4
      milliseconds: 0
  - action: tts.google_translate_say
    metadata: {}
    data:
      cache: true
      entity_id: media_player.esp32_s3_esphome_pcm5102a_media_player
      message: Время 12:00. Шевелите булками! Время идёт.
mode: single


Третий вариант - было интересно попробовать собрать колонку по проекту Ёрадио. И да, она у меня тоже получилась, пока в самом минимальном варианте, без экрана и энкодера. Но правильные детали под этот проект уже заказаны и скоро я возьмусь за сборку. А пока готовится 3D модель корпуса.

Ну и наверное понятно, что кроме как проигрывать радио-поток, она может и всё остальное - на неё можно отправить TTS или проигрывать локальные файлы, то есть полностью задействовать в автоматизациях.
Позже напишу и покажу что из этого получилось.