wizmo2 / TAudio-Case

Wizmo Projects Page
7 stars 1 forks source link

Working configuration.yaml? #1

Open NonaSuomy opened 1 year ago

NonaSuomy commented 1 year ago

Hi is there a working version of the configuration.yaml for the current HomeAssistant?

wizmo2 commented 1 year ago

Give me a couple of days and I'll work on it. I think there are some better ways to do this now

NonaSuomy commented 1 year ago

Did they integrate your light stuff as well to master or just the DAC stuff, would I still need to use your code base to get the LED's working? Does the microphone work, and do you know if there is a manual way to input the LMS server's IP address into the esp32 so it knows where it is? My taudio on wifi and LMS docker are on two different subnets

wizmo2 commented 1 year ago

The led control is still an open PR, so you would need my code base for that. I'm a few releases behind. If you have an issue ,I can look at remerging.

Never tried it, but check out the squeezelite command-line options for setting the LMS IP address (Accessible in the Audio/Optional settings). From a quick google, try -s . Otherwise, search the support page on the squeeze forum. I'm sure this has come up before.

I don't believe the project supports audio-in or the microphone. As a LMS client, there is no real requirement. I think I did look at one point, but the quality pretty much sucked.

NonaSuomy commented 1 year ago

Ok, that would be great if you could do a remerge. Was there anything stopping them from merging your PR? can you link to it for me?

Thank you -s worked like a charm.

I just thought it would be cool if you could pass the mic into Home Assistant for audio controls (Alexa / Goog / or the open source projects) understandable though if LMS doesn't support that.

wizmo2 commented 1 year ago

I've updated the configuration.yaml file. It is only an example, and some of the entries may need to be pasted into other files (see notes).

wizmo2 commented 1 year ago

As mentioned, I wasn't too impressed with the mic on this device. I too, was looking at voice control. In the end, I concluded it did not make sense. If your going to use google or alexa, then you might as well use one of their devices. If you're going down the open source solutions, then you'll need something more powerful than a ESP32. I played around with Rhasspy and the 4-mic array, but again was not impressed.

I'll look at remerging when I have time, although there is not much new, and none relates to the t-audio platform. I did up a compiled release in my builds folder if you want to give it a try.

NonaSuomy commented 1 year ago

You’re awesome! Thanks will wait for that re-merge do you have a link to the past PR I can’t seem to find it. Wanted to see if they said anything about it etc? Maybe if resubmitted I could beg them to merge it.

I currently use the Alexa integration with HA and a Vector robot he has a 4 mic array on his back I believe not sure what they do differently but seems to work. https://www.digitaldreamlabs.com/products/vector-robot Got him for $80 on Amazon when the prior company was going under but too expensive now.

NonaSuomy commented 1 year ago

Is your binary compiling against the 4.3 branch?

NonaSuomy commented 1 year ago

Hope this helps, automations section was missing "trigger:" at the top and if you put input_text in its own yaml file you need to put quotes around the message: "{{ states('input_text.alert_trigger') }}"

configuration.yaml

# Loads default set of integrations. Do not remove.
default_config:

automation: !include automations.yaml
script: !include scripts.yaml
scene: !include scenes.yaml
input_text: !include input_text.yaml
input_number: !include input_number.yaml
notify: !include notify.yaml
light: !include light.yaml

light.yaml

# Squeezelite-esp32 T-Audio LED strip
- platform: template
  lights:
   taudio_good:
     friendly_name: "Notify Good"
     turn_on:
       service: squeezebox.call_method
       data:
         entity_id: media_player.squeezelite_taudio
         command: dmx
         parameters:
           - '0,80,0,0,80,0,0,80,0,0,80,0,0,80,0,0,80,0'
     turn_off:
       service: squeezebox.call_method
       data:
         entity_id: media_player.squeezelite_taudio
         command: dmx
         parameters:
           - '0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0'
   taudio_warn:
     friendly_name: "Notify Warn"
     turn_on:
       service: squeezebox.call_method
       data:
         entity_id: media_player.squeezelite_taudio
         command: dmx
         parameters:
           - '60,20,0,60,20,0,60,20,0,60,20,0,60,20,0'
           - '7'
     turn_off:
       service: squeezebox.call_method
       data:
         entity_id: media_player.squeezelite_taudio
         command: dmx
         parameters:
           - '0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0'
           - '7'
   taudio_alert:
     friendly_name: "Notify Alert"
     turn_on:
       service: squeezebox.call_method
       data:
         entity_id: media_player.squeezelite_taudio
         command: dmx
         parameters:
           - '80,0,0,80,0,0,80,0,0,80,0,0,80,0,0,80,0,0,80,0,0'
           - '12'
     turn_off:
       service: squeezebox.call_method
       data:
         entity_id: media_player.squeezelite_taudio
         command: dmx
         parameters:
           - '0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0'
           - '12'
   taudio_all:
      friendly_name: "TAudio Color"
      level_template: "{{states('input_number.taudio_l') | int(0)}}"
      value_template: "{{states('input_number.taudio_l') | int(0) > 0}}"
      color_template: "({{states('input_number.taudio_h') | int(0)}}, {{states('input_number.taudio_s') | int(0)}})"
      turn_on:
        - service: input_number.set_value
          data:
            entity_id: input_number.taudio_l
            value: '{{ state_attr("input_number.taudio_l","initial")|float(0) }}'
        - service: script.taudio_set
      turn_off:
        - service: input_number.set_value
          data:
            entity_id: input_number.taudio_l
            value: "0"
        - service: script.taudio_set
      set_level:
        - service: input_number.set_value
          data:
            value: "{{ brightness| float(0) }}"
            entity_id: input_number.taudio_l
        - service: script.taudio_set
      set_color:
        - service: input_number.set_value
          data:
            value: "{{ h }}"
            entity_id: input_number.taudio_h
        - service: input_number.set_value
          data:
            value: "{{ s }}"
            entity_id: input_number.taudio_s
        - service: script.taudio_set

automations.yaml

# Squeezelite-esp32 T-Audio Example TTS notify using an input text helper
- id: announce_to_speaker
  alias: TAudio Announce
  description: ''
  trigger:
  - platform: state
    entity_id: input_text.alert_trigger
  action:
  - service: squeezebox.call_method
    data:
      command: show
      parameters:
      - 'line1: HA Announce'
      - 'line2: {{ states("input_text.alert_trigger") }}'
      - 'font: light'
      - 'duration: 30'
    entity_id: media_player.squeezelite_taudio
  - service: notify.tts_announce
    data:
     message: "{{ states('input_text.alert_trigger') }}"
  mode: single
# Squeezelite-esp32 T-Audio the following automation uses text helpers to search and play music
- id: taudio_load_albums
  alias: taudio_load_albums
  description: ''
  trigger:
  - platform: state
    entity_id: input_text.taudio_play_albums
  condition: []
  action:
  - service: squeezebox.call_method
    data:
      entity_id: media_player.squeezelite_taudio
      command: playlist
      parameters:
        - loadtracks
        - 'album.titlesearch={{ states(''input_text.taudio_play_albums'') }}'
  mode: single
- id: taudio_load_artists
  alias: taudio_load_artists
  description: ''
  trigger:
  - platform: state
    entity_id: input_text.taudio_play_artists
  condition: []
  action:
  - service: squeezebox.call_method
    data:
      entity_id: media_player.squeezelite_taudio
      command: playlist
      parameters:
        - loadtracks
        - 'contributor.namesearch={{ states(''input_text.taudio_play_artists'') }}'
  mode: single
- id: taudio_load_songs
  alias: taudio_load_songs
  description: ''
  trigger:
  - platform: state
    entity_id: input_text.taudio_play_songs
  condition: []
  action:
  - service: squeezebox.call_method
    data:
      entity_id: media_player.squeezelite_taudio
      command: playlist
      parameters:
        - loadtracks
        - 'track.titlesearch={{ states(''input_text.taudio_play_songs'') }}'
  mode: single

scripts.yaml

# Squeezelite-ESP32 T-Audio Shared function to simplify template lights
taudio_set:
  alias: TAudio Set RGB Color
  sequence:
    - service: squeezebox.call_method
      data:
        entity_id: media_player.squeezelite_taudio
        command: dmx
        parameters:
          - '{% set gain = state_attr("light.taudio_all", "brightness")|int(0) %}{% for n in range(19) %}{% if gain == 0 %}0,0,0,{% else %}{% set rgb = state_attr("light.taudio_all", "rgb_color") %}{{ (rgb[0]|int(0) * gain / 255)|round  }},{{ (rgb[1]|int(0) * gain / 255)|round(0) }},{{ (rgb[2]|int(0) * gain / 255)|round(0) }},{% endif %}{% endfor %}'
          - "0"
# Squeezelite-esp32 T-Audio used to flash lights in service calls
taudio_alert_flash:
  alias: taudio_alert_flash
  sequence:
    repeat:
      count: 10
      sequence:
        - service: light.turn_on
          data:
            entity_id: light.taudio_alert
        - delay: '00:00:01'
        - service: light.turn_off
          data:
            entity_id: light.taudio_alert
        - delay: '00:00:03'

input_text.yaml

# Squeezelite-ESP32 T-Audio input_text
taudio_play_songs:
  name: "Play Songs"
taudio_play_artists:
  name: "Play Artist"
taudio_play_albums:
  name: "Play Album"
alert_trigger:
  name: "Alert Trigger Source"

input_number.yaml

# Squeezelite-ESP32 T-Audio input_number
taudio_h:
  name: TAudio Hue
  min: 0
  max: 360
taudio_s:
  name: TAudio Saturation
  min: 0
  max: 100
taudio_l:
  name: TAudio Level
  initial: 30
  min: 0
  max: 255
  step: 1

notify.yaml

# Squeezelite-ESP32 T-Audio a Text-To-Speech platform is required for the announcement to speaker example
- platform: tts
  name: tts_announce
  tts_service: tts.google_say
  media_player: media_player.squeezelite_taudio
wizmo2 commented 1 year ago

Sorry - missed the last post. Just updated merged with the master-cmake branch. Have not looked at the 4.3 as yet,. If it does get released as the default branch, I can taker a look, although there is no immediate need on the t-audio.

I just moved my server to the linux docker. It was a pain to get the plugin updated to enable the LED control features. I ended up having to manually update the files on the server. (which I think I had to do on the Windows integration too).

Thanks for the feedback. I'll update the examples.

NonaSuomy commented 1 year ago

Even though you said the mic is not of great quality any chance you can add the wm8978 to the new audio code for home assistant - esphome? Seems to support media stuff a lot nicer than the squeeze stuff with HA.

https://github.com/esphome/media-players/blob/main/m5stack-atom-echo.yaml

They were first using the arduino framework then switched to esp-idf framework for esphome. https://github.com/espressif/esp-adf/pull/33/files

https://github.com/esphome/esphome/tree/dev/esphome/components/i2s_audio

https://esphome.io/components/media_player/i2s_audio.html

https://rc.home-assistant.io/projects/thirteen-usd-voice-remote/

Example of another dac with i2c control for esphome. https://github.com/esphome/esphome/pull/3552

Example YAML setup for muse-luxe

https://github.com/esphome/media-players/blob/main/raspiaudio-muse-luxe.yaml

Microphone example YAML

https://github.com/esphome/media-players/blob/main/m5stack-atom-echo.yaml

https://github.com/esphome/firmware/blob/91f781174ada31bd124c9ab5ef6f642720c3cb60/voice-assistant/m5stack-atom-echo.yaml

This is the echo YAML but pins swapped for the T-Audio 1.6 board. https://github.com/LilyGO/TTGO-TAudio

Original Atom Echo YAML:

https://github.com/esphome/firmware/blob/91f781174ada31bd124c9ab5ef6f642720c3cb60/voice-assistant/m5stack-atom-echo.yaml

YAML could look something like this:

# Substitute these words into the YAML below.
substitutions:
  name: "t-audio-001"
  friendly_name: "T-Audio-001"

# ESPHome Setup.
esphome:
  name: "${name}"
  friendly_name: "${friendly_name}"
  min_version: 2023.5.0

# Board and Framework Setup.
esp32:
  board: esp-wrover-kit
  framework:
    type: arduino

# Enable logging.
logger:

# Enable and set up the Home Assistant API.
api:
  encryption:
    key: !secret encryption_key005

# Enable OTA updates.
ota:
  password: !secret ota_pass005

# Enable and set up WiFi.
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  use_address: !secret use_address_wifi005

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "${friendly_name} Fallback Hotspot"
    password: !secret fallbackhotspot005

# Enable captive portal if WiFi fails to connect.
captive_portal:

# Enable WebUI for direct control.
web_server:

# Configure the switch for restarting ESP32 from the WebUI.
switch:
  - platform: restart
    name: "${friendly_name} Restart"

# The improv_serial component in ESPHome implements the open Improv standard 
# for configuring Wi-Fi on an ESPHome device by using a serial connection to 
# the device, eg. USB.
improv_serial:

# Setup I2C pins for WM8978 communication.
i2c:
  sda: GPIO19
  scl: GPIO18

# Enable and set up the I2S audio pins.
i2s_audio:
  # Left / Right Clock or Word Select or Frame Sync.
  i2s_lrclk_pin: GPIO25
  # Bit Clock or Continuous Serial Clock [SCK].
  i2s_bclk_pin: GPIO33
  # Missing this pin setup?
  # Master Clock (typically 256 x LRCLK).
  #i2s_mclk_pin: 0

# Enable and set up the I2S microphone.
microphone:
  - platform: i2s_audio
    id: wm8978_microphone
    # Digital IN for the microphone.
    i2s_din_pin: GPIO27
    adc_type: external
    pdm: false

# Enable and set up the media player for TTS files to play.
media_player:
  - platform: i2s_audio
    name: "${friendly_name} I2SAudio"
    id: wm8978_audio
    dac_type: external
    # I2S Digital Out for speaker(s).
    i2s_dout_pin: GPIO26
    mode: stereo
    mute_pin:
      number: GPIO21
      inverted: true

# Enable and set up the WM8978 DAC.
# Need a wm8978 component.
#wm8978:

# Enable voice assistant.
voice_assistant:
  microphone: wm8978_microphone
  on_start:
    - light.turn_on:
        id: led
        blue: 100%
        red: 0%
        green: 0%
        effect: none
  on_tts_start:
    - light.turn_on:
        id: led
        blue: 0%
        red: 0%
        green: 100%
        effect: none
  on_tts_end:
    - media_player.play_media: !lambda return x;
    - light.turn_on:
        id: led
        blue: 0%
        red: 0%
        green: 100%
        effect: none
    - media_player.play_media: !lambda return x;
  on_end:
    - delay: 1s
    - wait_until:
        not:
          media_player.is_playing: wm8978_audio
    - light.turn_off: led
  on_error:
    - light.turn_on:
        id: led
        blue: 0%
        red: 100%
        green: 0%
        effect: none
    - delay: 1s
    - light.turn_off: led

# The T-Audio Battery IC IP5306 is I2C so this code won't work.
#sensor:
#  - platform: adc
#    pin: GPIO??
#    name: Battery
#    icon: "mdi:battery-outline"
#    device_class: voltage
#    state_class: measurement
#    entity_category: diagnostic
#    unit_of_measurement: V
#    update_interval: 15s
#    accuracy_decimals: 3
#    attenuation: 11db
#    raw: true
#    filters:
#      - multiply: 0.00173913 # 2300 -> 4, for attenuation 11db, based on Olivier's code
#      - exponential_moving_average:
#          alpha: 0.2
#          send_every: 2
#      - delta: 0.002

# Enable and set up the volume and microphone capture buttons.
binary_sensor:
  - platform: gpio
    pin:
      number: GPIO34
      inverted: true
      mode:
        input: true
        pullup: true
    name: Volume Up
    on_click:
      - media_player.volume_up: wm8978_audio
  - platform: gpio
    pin:
      number: GPIO36
      inverted: true
      mode:
        input: true
        pullup: true
    name: Volume Down
    on_click:
      - media_player.volume_down: wm8978_audio
  - platform: gpio
    pin:
      number: GPIO39
      inverted: true
      mode:
        input: true
        pullup: true
    name: Action
    on_multi_click:
      - timing:
          - ON FOR AT MOST 350ms
          - OFF FOR AT LEAST 10ms
        then:
          - media_player.toggle: wm8978_audio
      - timing:
          - ON FOR AT LEAST 350ms
        then:
          - voice_assistant.start:
      - timing:
          - ON FOR AT LEAST 350ms
          - OFF FOR AT LEAST 10ms
        then:
          - voice_assistant.stop:

# Enable and set up the 19 addressable LED's on the T-Audio 1.6.
light:
  - platform: fastled_clockless
    id: led
    name: "${friendly_name} LED(s)"
    disabled_by_default: true
    entity_category: config
    pin: GPIO22
    default_transition_length: 0s
    chipset: WS2812B
    num_leds: 19
    rgb_order: GRB
    rmt_channel: 0
    effects:
      - pulse:
          transition_length: 250ms
          update_interval: 250ms

It basically works (Button and LED haha) besides the wm8978 initialization code missing to turn off SOFTMUTE and enable the right and left audio channels.

Testing the button press (Doesn't detect audio from the microphone):

ESPHome version 2023.4.2 compiled on May  1 2023, 23:03:49
[23:04:23][D][api.connection:961]: Home Assistant 2023.5.0b5 (10.0.1.42): Connected successfully
[23:04:44][D][binary_sensor:036]: 'T-Audio-001 Button': Sending state ON
[23:04:44][D][voice_assistant:065]: Requesting start...
[23:04:44][D][voice_assistant:045]: Starting...
[23:04:44][D][voice_assistant:083]: Assist Pipeline running
[23:04:45][D][binary_sensor:036]: 'T-Audio-001 Button': Sending state OFF
[23:04:45][D][voice_assistant:073]: Signaling stop...
[23:04:53][E][voice_assistant:145]: Error: stt-no-text-recognized - No text recognized

Test sending a TTS wav file from Piper Docker in Home Assistant Docker with Wyoming Protocol (Doesn't output any sound).

[23:25:31][D][media_player:059]: 'T-Audio-001' - Setting
[23:25:31][D][media_player:066]:   Media URL: http://10.0.1.42:8123/api/tts_proxy/7b031c3d673c84477529b838252c3609e5be34b8_en-us_a877e2b3bf_tts.piper.wav

A bunch of docker-compose YAML I use for Home Assistant, Faster-Whisper, Piper, etc. https://github.com/NonaSuomy/nonasuomy.github.io/blob/master/_posts/2022-10-15-docker.md

Seems they hardcode GPIO0 for the clock.

https://github.com/esphome/esphome/pull/3552/commits/b1ad48c8240707eae18e4b7b9266c3c3ee01b239

namespace es8388 {

void ES8388Component::setup() {
  PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);
  WRITE_PERI_REG(PIN_CTRL, READ_PERI_REG(PIN_CTRL) & 0xFFFFFFF0);

https://github.com/esphome/esphome/tree/fcf0761ba3ed1647b19ecbcb6ddcd7e2f5c0932c/esphome/components/es8388

NonaSuomy commented 1 year ago

ESPHome i2s_audio code was based on arduino i2s library image

wizmo2 commented 1 year ago

Not sure when I'll have a chance to look at it, but it may get on the todo list at sometime.

If anyone else wanted to give it a try, the main challenge is that the 2-byte I2s commands on the WM are non standard, so standard setup script interfaces don't typically work.

NonaSuomy commented 1 year ago

Ok, I attempted it with putting these files under esphome/config/custom_components/wm8978/ and the above yamls How would you go about getting this to actually work with 2-byte I2S commands? What would you add from the references wm code at the bottom pasted below?

This compiles but I'm not really sure that I even got the WM initialized properly with what I modified.

init.py

import esphome.codegen as cg
import esphome.config_validation as cv

from esphome.components import i2c
from esphome.const import CONF_ID

wm8978_ns = cg.esphome_ns.namespace("wm8978")
wm8978Component = wm8978_ns.class_("wm8978Component", cg.Component, i2c.I2CDevice)

CONFIG_SCHEMA = (
    cv.Schema({cv.GenerateID(): cv.declare_id(wm8978Component)})
    .extend(i2c.i2c_device_schema(0x1A))
    .extend(cv.COMPONENT_SCHEMA)
)

async def to_code(config):
    var = cg.new_Pvariable(config[CONF_ID])
    await cg.register_component(var, config)
    await i2c.register_i2c_device(var, config)

wm8978_component.cpp

#include "wm8978_component.h"
#include "esphome/core/hal.h"

#include <soc/io_mux_reg.h>

namespace esphome {
namespace wm8978 {

void wm8978Component::setup() {
  PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);
  WRITE_PERI_REG(PIN_CTRL, READ_PERI_REG(PIN_CTRL) & 0xFFFFFFF0);

  // reset
  //this->write_byte(0x00, {0x80},);
  //this->write_byte(0x00, {0x00}, 1);
  // R1, MIC enable
  this->write_byte(1, 27);
  // R2, ROUT1, LOUT1 enable headphone
  this->write_byte(2, 27);
  // R3, LOUT2, LOUT2 enable speaker
  this->write_byte(3, 108);
  // R6, MCLK is provided externally
  this->write_byte(6, 0);
  // R43, INVROUT2 reverse, drive the speaker
  this->write_byte(43, 16);
  // R47, setting, PGABOOSTL, left channel MIC gets 20 times gain
  this->write_byte(47, 256);
  // R48, setting, PGABOOSTR, right channel MIC gets 20 times gain
  this->write_byte(48, 256);
  // R49, TSDEN, enable overheating protection
  this->write_byte(49, 2);
  // R10, SOFTMUTE off, 128x sampling, best SNR
  this->write_byte(10, 8);
  // R14, ADC 128x sampling rate and enable high pass filter (3.7Hz cut-off)
  this->write_byte(14, 8 | 256);

  // mute
  //this->write_byte(0x19, 0x04);
  // powerup
  //this->write_byte(0x01, 0x50);
  //this->write_byte(0x02, 0x00);
  // worker mode
  //this->write_byte(0x08, 0x00);
  // DAC powerdown
  //this->write_byte(0x04, 0xC0);
  // vmidsel/500k ADC/DAC idem
  //this->write_byte(0x00, 0x12);

  // i2s 16 bits
  //this->write_byte(0x17, 0x18);
  // sample freq 256
  //this->write_byte(0x18, 0x02);
  // LIN2/RIN2 for mixer
  //this->write_byte(0x26, 0x00);
  // left DAC to left mixer
  //this->write_byte(0x27, 0x90);
  // right DAC to right mixer
  //this->write_byte(0x2A, 0x90);
  // DACLRC ADCLRC idem
  //this->write_byte(0x2B, 0x80);
  //this->write_byte(0x2D, 0x00);
  // DAC volume max
  //this->write_byte(0x1B, 0x00);
  //this->write_byte(0x1A, 0x00);

  // ADC poweroff
  //this->write_byte(0x03, 0xFF);
  // ADC amp 24dB
  //this->write_byte(0x09, 0x88);
  // LINPUT1/RINPUT1
  //this->write_byte(0x0A, 0x00);
  // ADC mono left
  //this->write_byte(0x0B, 0x02);
  // i2S 16b
  //this->write_byte(0x0C, 0x0C);
  // MCLK 256
  //this->write_byte(0x0D, 0x02);
  // ADC Volume
  //this->write_byte(0x10, 0x00);
  //this->write_byte(0x11, 0x00);
  // ALC OFF
  //this->write_byte(0x03, 0x09);
  //this->write_byte(0x2B, 0x80);

  //this->write_byte(0x02, 0xF0);
  //delay(1);
  //this->write_byte(0x02, 0x00);
  // DAC power-up LOUT1/ROUT1 enabled
  //this->write_byte(0x04, 0x30);
  //this->write_byte(0x03, 0x00);
  // DAC volume max
  //this->write_byte(0x2E, 0x1C);
  //this->write_byte(0x2F, 0x1C);
  // unmute
  //this->write_byte(0x19, 0x00);
}
}  // namespace wm8978
}  // namespace esphome

wm8978_component.h

#pragma once

#include "esphome/components/i2c/i2c.h"
#include "esphome/core/component.h"

namespace esphome {
namespace wm8978 {

class wm8978Component : public Component, public i2c::I2CDevice {
 public:
  void setup() override;

  float get_setup_priority() const override { return setup_priority::LATE - 1; }
};

}  // namespace wm8978
}  // namespace esphome

Files used for ref

WM8978.cpp

#include <stdio.h>
#include <Arduino.h>
#include <Wire.h>
#include "WM8978.h"

// WM8978 register value buffer zone (total 58 registers 0 to 57), occupies 116 bytes of memory
// Because the IIC WM8978 operation does not support read operations, so save all the register values in the local
// Write WM8978 register, synchronized to the local register values, register read, register directly back locally stored value.
// Note: WM8978 register value is 9, so use uint16_t storage.

static uint16_t REGVAL_TBL[58] =
{
  0X0000, 0X0000, 0X0000, 0X0000, 0X0050, 0X0000, 0X0140, 0X0000,
  0X0000, 0X0000, 0X0000, 0X00FF, 0X00FF, 0X0000, 0X0100, 0X00FF,
  0X00FF, 0X0000, 0X012C, 0X002C, 0X002C, 0X002C, 0X002C, 0X0000,
  0X0032, 0X0000, 0X0000, 0X0000, 0X0000, 0X0000, 0X0000, 0X0000,
  0X0038, 0X000B, 0X0032, 0X0000, 0X0008, 0X000C, 0X0093, 0X00E9,
  0X0000, 0X0000, 0X0000, 0X0000, 0X0003, 0X0010, 0X0010, 0X0100,
  0X0100, 0X0002, 0X0001, 0X0001, 0X0039, 0X0039, 0X0039, 0X0039,
  0X0001, 0X0001
};

//WM8978 write register.
//reg: register address.
//val: the value to be written to the register.
//Return value:0, success;
//other, error code.
uint8_t WM8978::Write_Reg(uint8_t reg, uint16_t val)
{
  char buf[2];
  buf[0] = (reg << 1) | ((val >> 8) & 0X01);
  buf[1] = val & 0XFF;
  Wire.beginTransmission(WM8978_ADDR); // Send data to the slave with device number 4
  Wire.write((const uint8_t*)buf, 2);
  Wire.endTransmission();    // Stop sending.
  REGVAL_TBL[reg] = val; // Save register value to local
  return 0;
}

//WM8978 init
// Return value: 0, initialization is normal.
// other, error code
uint8_t WM8978::Init(void)
{
  uint8_t res;
  res = Write_Reg(0, 0);  // Soft reset WM8978.
  if (res)return 1;     // Failed to send command, WM8978 is abnormal
  // The following are general settings.
  Write_Reg(1, 0X1B); //R1,MICEN is set to 1 (MIC enable), BIASEN is set to 1 (simulator work), VMIDSEL[1:0] is set to: 11 (5K)
  Write_Reg(2, 0X1B0); //R2,ROUT1,LOUT1 output enable (headphone can work), BOOSTENR, BOOSTENL enable
  Write_Reg(3, 0X6C); //R3,LOUT2,ROUT2 output enable (speaker work), RMIX, LMIX enable
  Write_Reg(6, 0);    //R6,MCLK is provided externally
  Write_Reg(43, 1 << 4);  //R43, INVROUT2 reverse, drive the speaker
  Write_Reg(47, 1 << 8);  //R47 setting, PGABOOSTL, left channel MIC gets 20 times gain
  Write_Reg(48, 1 << 8);  //R48 setting, PGABOOSTR, right channel MIC 20 times gain
  Write_Reg(49, 1 << 1);  //R49,TSDEN, enable overheating protection
  Write_Reg(10, 1 << 3);  //R10,SOFTMUTE off, 128x sampling,best SNR
  Write_Reg(14, 1 << 3 | 1 << 8);  //R14,ADC 128x sampling rate and enable high pass filter (3.7Hz cut-off)
  return 0;
}

// WM8978 read register
// Reads the value  of the local register buffer zone
// reg: Register Address
// Return Value: Register value
uint16_t WM8978::Read_Reg(uint8_t reg)
{
  return REGVAL_TBL[reg];
}
// WM8978 DAC/ADC configuration
// adcen:adc enable(1)/disable(0)
// dacen:dac enable(1)/disable(0)
void WM8978::cfgADDA(uint8_t dacen, uint8_t adcen)
{
  uint16_t regval;
  regval = WM8978::Read_Reg(3); // Read R3
  if (dacen)regval |= 3 << 0;   // Set the lowest 2 bits of R3 to 1, enable DACR&DACL
  else regval &= ~(3 << 0);     // Clear the lowest 2 bits of R3, close DACR&DACL.
  Write_Reg(3, regval);         // Set R3
  regval = WM8978::Read_Reg(2); // Read R2
  if (adcen)regval |= 3 << 0;   // Set the lower 2 bits of R2 to 1, enable ADCR&ADCL
  else regval &= ~(3 << 0);     // Clear the lowest 2 bits of R2 to zero, turn off ADCR&ADCL.
  Write_Reg(2, regval);         // Set R2
}
// WM8978 input channel configuration
// micen: MIC enable(1)/disable(0)
// lineinen: Line In enable(1)/disable(0)
// auxen: Aux enable(1)/disable(0)
void WM8978::cfgInput(uint8_t micen, uint8_t lineinen, uint8_t auxen)
{
  uint16_t regval;
  regval = WM8978::Read_Reg(2); // Read R2
  if (micen)regval |= 3 << 2;   // Enable INPPGAENR, INPPGAENL (PGA amplification of MIC)
  else regval &= ~(3 << 2);     // Close INPPGAENR, INPPGAENL.
  Write_Reg(2, regval);         // Set R2

  regval = WM8978::Read_Reg(44);        // Read R44
  if (micen)regval |= 3 << 4 | 3 << 0;  // Enable LIN2INPPGA, LIP2INPGA, RIN2INPPGA, RIP2INPGA.
  else regval &= ~(3 << 4 | 3 << 0);    // Close LIN2INPPGA, LIP2INPGA, RIN2INPPGA, RIP2INPGA.
  Write_Reg(44, regval);                // Set R44

  if (lineinen)WM8978::setLINEINgain(5); // LINE-IN 0dB gain
  else WM8978::setLINEINgain(0);         // Close LINE-IN
  if (auxen)WM8978::setAUXgain(7);       // AUX 6dB gain
  else WM8978::setAUXgain(0);            // Close AUX input
}
// WM8978 output configuration
// dacen: DAC output (playback) enable(1)/disable(0)
// bpsen: Bypass output (recording, including MIC, LINE-IN, AUX, etc) enable(1)/disable(0)
void WM8978::cfgOutput(uint8_t dacen, uint8_t bpsen)
{
  uint16_t regval = 0;
  if (dacen) regval |= 1 << 0;  // DAC output enable
  if (bpsen)
  {
    regval |= 1 << 1;   // BYPASS enabled
    regval |= 5 << 2;   // 0dB gain
  }
  Write_Reg(50, regval); // R50 setting
  Write_Reg(51, regval); // R51 setting
}
// WM8978 MIC gain setting (excluding BOOST's 20dB, MIC-->ADC input gain)
// Gain: 0~63, corresponding to -12dB~35.25dB, 0.75dB/Step
void WM8978::setMICgain(uint8_t gain)
{
  gain &= 0X3F;
  Write_Reg(45, gain);          // R45, left channel PGA setting
  Write_Reg(46, gain | 1 << 8); // R46, right channel PGA setting
}
// WM8978 L2/R2 (that is, Line-In) gain setting (L2/R2-->ADC input part gain)
// Gain: 0~7,0 means the channel is prohibited, 1~7, corresponding to -12dB~6dB, 3dB/Step
void WM8978::setLINEINgain(uint8_t gain)
{
  uint16_t regval;
  gain &= 0X07;
  regval = WM8978::Read_Reg(47);     // Read R47
  regval &= ~(7 << 4);               // Clear the original setting
  Write_Reg(47, regval | gain << 4); // Set R47
  regval = WM8978::Read_Reg(48);     // Read R48
  regval &= ~(7 << 4);               // Clear the original setting
  Write_Reg(48, regval | gain << 4); // Set R48
}
// WM8978 AUXR, AUXL (PWM audio part) gain setting (AUXR/L-->ADC input part gain)
// Gain: 0~7, 0 means the channel is prohibited, 1~7, corresponding to -12dB~6dB,3dB/Step
void WM8978::setAUXgain(uint8_t gain)
{
  uint16_t regval;
  gain &= 0X07;
  regval = WM8978::Read_Reg(47);     // Read R47
  regval &= ~(7 << 0);               // Clear the original setting
  Write_Reg(47, regval | gain << 0); // Set R47
  regval = WM8978::Read_Reg(48);     // Read R48
  regval &= ~(7 << 0);               // Clear the original setting
  Write_Reg(48, regval | gain << 0); // Set R48
}
// Set I2S working mode
// fmt: 0, LSB (right alignment); 1, MSB (left alignment); 2, Philips standard I2S; 3, PCM/DSP;
// len: 0, 16 bits; 1, 20 bits; 2, 24 bits;3, 32 bits;
void WM8978::cfgI2S(uint8_t fmt, uint8_t len)
{
  fmt &= 0X03;
  len &= 0X03;                            // Limited range
  Write_Reg(4, (fmt << 3) | (len << 5));  // R4, WM8978 working mode setting
}

// Set the left and right channel volume of the earphone
// voll: left channel volume (0~63)
// volr: right channel volume (0~63)
void WM8978::setHPvol(uint8_t voll, uint8_t volr)
{
  voll &= 0X3F;
  volr &= 0X3F; // Limited range
  if (voll == 0)voll |= 1 << 6;   // When the volume is 0, directly mute
  if (volr == 0)volr |= 1 << 6;   // When the volume is 0, directly mute
  Write_Reg(52, voll);            // R52, the volume setting of the left channel of the earphone
  Write_Reg(53, volr | (1 << 8)); // R53, the volume setting of the right channel of the earphone, updated synchronously (HPVU=1)
}
// Set speaker volume
// voll: left channel volume (0~63)
void WM8978::setSPKvol(uint8_t volx)
{
  volx &= 0X3F;                   // Limited range
  if (volx == 0)volx |= 1 << 6;   // When the volume is 0, directly mute
  Write_Reg(54, volx);            // R54, speaker left channel volume setting
  Write_Reg(55, volx | (1 << 8)); // R55, speaker right channel volume setting, update synchronously (SPKVU=1)
}

  // Set 3D surround sound
  // depth: 0~15 (3D strength, 0 is the weakest, 15 is the strongest)
  void WM8978::set3D(uint8_t depth)
  {
  depth&=0XF;           // Limited range
  Write_Reg(41,depth);  // R41, 3D surround setting
  }

// Set EQ/3D action direction
// dir: 0, works in ADC
//      1, works on DAC (default)
void WM8978::set3Ddir(uint8_t dir)
{
  uint16_t regval;
  regval = WM8978::Read_Reg(0X12);
  if (dir)regval |= 1 << 8;
  else regval &= ~(1 << 8);
  Write_Reg(18, regval); // R18, the 9th bit of EQ1 controls EQ/3D direction
}

// Set EQ1
// cfreq: cut-off frequency, 0~3, respectively corresponding to: 80/105/135/175Hz
// gain: gain, 0~24, corresponding to -12~+12dB
void WM8978::setEQ1(uint8_t cfreq, uint8_t gain)
{
  uint16_t regval;
  cfreq &= 0X3;          // Limited range
  if (gain > 24)gain = 24;
  gain = 24 - gain;
  regval = WM8978::Read_Reg(18);
  regval &= 0X100;
  regval |= cfreq << 5;  // Set the cutoff frequency
  regval |= gain;        // Set gain
  Write_Reg(18, regval); // R18, EQ1 setting
}
// Set EQ2
// cfreq: center frequency, 0~3, respectively corresponding to: 230/300/385/500Hz
// gain: gain, 0~24, corresponding to -12~+12dB
void WM8978::setEQ2(uint8_t cfreq, uint8_t gain)
{
  uint16_t regval = 0;
  cfreq &= 0X3;          // Limited range
  if (gain > 24)gain = 24;
  gain = 24 - gain;
  regval |= cfreq << 5;  // Set the cutoff frequency
  regval |= gain;        // Set gain
  Write_Reg(19, regval); // R19, EQ2 setting
}
// Set EQ3
// cfreq: center frequency, 0~3, respectively corresponding to :650/850/1100/1400Hz
// gain: gain, 0~24, corresponding to -12~+12dB
void WM8978::setEQ3(uint8_t cfreq, uint8_t gain)
{
  uint16_t regval = 0;
  cfreq &= 0X3;          // Limited range
  if (gain > 24)gain = 24;
  gain = 24 - gain;
  regval |= cfreq << 5;  // Set the cutoff frequency
  regval |= gain;        // Set gain
  Write_Reg(20, regval); // R20, EQ3 setting
}
// Set EQ4
// cfreq: center frequency, 0~3, respectively corresponding to :1800/2400/3200/4100Hz
// gain: gain, 0~24, corresponding to -12~+12dB
void WM8978::setEQ4(uint8_t cfreq, uint8_t gain)
{
  uint16_t regval = 0;
  cfreq &= 0X3;          // Limited range
  if (gain > 24)gain = 24;
  gain = 24 - gain;
  regval |= cfreq << 5;  // Set the cutoff frequency
  regval |= gain;        // Set gain
  Write_Reg(21, regval); // R21, EQ4 settings
}
// Set EQ5
// cfreq: center frequency, 0~3, respectively corresponding to: 5300/6900/9000/11700Hz
// gain: gain, 0~24, corresponding to -12~+12dB
void WM8978::setEQ5(uint8_t cfreq, uint8_t gain)
{
  uint16_t regval = 0;
  cfreq &= 0X3;          // Limited range
  if (gain > 24)gain = 24;
  gain = 24 - gain;
  regval |= cfreq << 5;  // Set the cutoff frequency
  regval |= gain;        // Set gain
  Write_Reg(22, regval); // R22, EQ5 settings
}

void WM8978::setALC(uint8_t enable, uint8_t maxgain, uint8_t mingain)
{
  uint16_t regval;

  if (maxgain > 7) maxgain = 7;
  if (mingain > 7) mingain = 7;

  regval = WM8978::Read_Reg(32);
  if (enable)
    regval |= (3 << 7);
  regval |= (maxgain << 3) | (mingain << 0);
  Write_Reg(32, regval);
}

void WM8978::setNoise(uint8_t enable, uint8_t gain)
{
  uint16_t regval;

  if (gain > 7) gain = 7;

  regval = WM8978::Read_Reg(35);
  regval = (enable << 3);
  regval |= gain;        // Set gain
  Write_Reg(35, regval); // R18, EQ1 setting
}

void WM8978::setHPF(uint8_t enable)
{
  uint16_t regval;

  regval = WM8978::Read_Reg(14);
  regval &= ~(1 << 8);
  regval |= (enable << 8);
  Write_Reg(14, regval); // R14, high pass filter
}

bool WM8978::begin() {
  Wire.beginTransmission(WM8978_ADDR);
  const uint8_t error = Wire.endTransmission();
  if (error) {
    log_e("No WM8978 dac @ i2c address: 0x%X", WM8978_ADDR);
    return false;
  }
  const int err = Init();
  if (err) {
    log_e("WM8978 init err: 0x%X", err);
    return false;
  }
  cfgI2S(2, 0);       // Philips 16bit
  cfgADDA(1, 1);      // Enable ADC DAC
  cfgInput(0, 0, 0);  // Mic, linein, aux - Note: M5Stack node has only internal microphones connected
  setMICgain(0);
  setAUXgain(0);
  setLINEINgain(0);
  setSPKvol(0);       // 0-63
  setHPvol(0, 0);     // 0-63
  set3Ddir(0);
  setEQ1(0, 24);
  setEQ2(0, 24);
  setEQ3(0, 24);
  setEQ4(0, 24);
  setEQ5(0, 24);
  cfgOutput(1, 0);    // Output enabled, bypass disabled
  return true;
}

bool WM8978::begin(const uint8_t sda, const uint8_t scl, const uint32_t frequency) {
  if (!Wire.begin(sda, scl, frequency)) {
    log_e("Wire setup error sda=%i scl=%i frequency=%i", sda, scl, frequency);
    return false;
  }
  return begin();
}

WM8978.h

#ifndef __WM8978_H
#define __WM8978_H

#include <stdio.h>

#define WM8978_ADDR   0X1A  //WM8978 Default Address
#define EQ1_80Hz      0X00
#define EQ1_105Hz     0X01
#define EQ1_135Hz     0X02
#define EQ1_175Hz     0X03

#define EQ2_230Hz     0X00
#define EQ2_300Hz     0X01
#define EQ2_385Hz     0X02
#define EQ2_500Hz     0X03

#define EQ3_650Hz     0X00
#define EQ3_850Hz     0X01
#define EQ3_1100Hz    0X02
#define EQ3_14000Hz   0X03

#define EQ4_1800Hz    0X00
#define EQ4_2400Hz    0X01
#define EQ4_3200Hz    0X02
#define EQ4_4100Hz    0X03

#define EQ5_5300Hz    0X00
#define EQ5_6900Hz    0X01
#define EQ5_9000Hz    0X02
#define EQ5_11700Hz   0X03

class WM8978
{
  public:
    WM8978() {}
    ~WM8978() {}
    bool begin(); /* use this function if you want to setup i2c before */
    bool begin(const uint8_t sda, const uint8_t scl, const uint32_t frequency = 100000);
    void cfgADDA(uint8_t dacen, uint8_t adcen);
    void cfgInput(uint8_t micen, uint8_t lineinen, uint8_t auxen);
    void cfgOutput(uint8_t dacen, uint8_t bpsen);
    void cfgI2S(uint8_t fmt, uint8_t len);
    void setMICgain(uint8_t gain);
    void setLINEINgain(uint8_t gain);
    void setAUXgain(uint8_t gain);
    void setHPvol(uint8_t voll, uint8_t volr);
    void setSPKvol(uint8_t volx);
    void set3D(uint8_t depth);
    void set3Ddir(uint8_t dir);
    void setEQ1(uint8_t cfreq, uint8_t gain);
    void setEQ2(uint8_t cfreq, uint8_t gain);
    void setEQ3(uint8_t cfreq, uint8_t gain);
    void setEQ4(uint8_t cfreq, uint8_t gain);
    void setEQ5(uint8_t cfreq, uint8_t gain);
    void setNoise(uint8_t enable, uint8_t gain);
    void setALC(uint8_t enable, uint8_t maxgain, uint8_t mingain);
    void setHPF(uint8_t enable);

  private:
    uint8_t Init(void);
    uint8_t Write_Reg(uint8_t reg, uint16_t val);
    uint16_t Read_Reg(uint8_t reg);
};
#endif