stas-sl / esphome-sound-level-meter

77 stars 14 forks source link

AtomS3U microphone #26

Open viotemp1 opened 6 months ago

viotemp1 commented 6 months ago

Hello,

I'm trying to use this component with AtomS3U - SPM1423, but this has only MIC_CLK & MIC_DATA pins. Any ideea what to do with the ws_pin? It looks like it's mandatory.

Screenshot 2024-05-14 at 20 24 03

I tried also this external mic (based on the same chip) - pdm - the same, the WS pin is missing.

Regards, V

stas-sl commented 6 months ago

Hi!

Unfortunately, PDM mics currently are not supported, there was an attempt to support them, you can read previous discussion and you can also check out branch https://github.com/stas-sl/esphome-sound-level-meter/tree/pdm_support, but seems like there are still some issues, I just can’t try it myself as I don’t have those mics.

viotemp1 commented 6 months ago

Hello,

I tried that branch and I get sound_level: 118.52 all the time. MAybe you can find some hints in M5Stack PDM I'll check more into the source code. Thanks!

viotemp1 commented 6 months ago

I made a fork and changed a bit fork according to the M5Stack PDM example. It's working now, not sure about the values ranges and calibration, but I do not need that. IMG_5092

Thanks for help!

karrui commented 5 months ago

@viotemp1 can you share your config on how you got it working? I'm trying to get an Atom Echo to work as a sound level meter too, and it also uses a pdm mic.

viotemp1 commented 5 months ago

@viotemp1 can you share your config on how you got it working? I'm trying to get an Atom Echo to work as a sound level meter too, and it also uses a pdm mic.

This is my config for M5Stack Core2 AWS

# GPIOs
# 25              - side LEDs
# 26,36              - Groove Port B Black (DAC/ADC)
# 32, 33          - Groove Port A Red
# 21, 22          - I2CA - internal
# 18, 23, 38      - SPI

# 5, 15, 4        - TFT

## 13 14           - Groove Port C Blue (RX/TX)
# 27              - Digital RGB LED Weatherproof Strip SK6812
# (34-39) does not support output pin mode

# I2C
# Bus Internal
# 0x34  -   AXP192 Power management
# 0x35  -   ATECC608 HW Encryption
# 0x38  -   FT6336U Touch Screen
# 0x51  -   BM8563 RTC
# 0x68  -   MPU6886 Accelerometer/Gyroscope

substitutions:
  esp_name: "decibel-meter"
  esp_name1: "decibel_meter"
  ip: ...
  board: m5stack-core2
  mic_clk: GPIO0 # internal
  mic_data: GPIO34 # internal
  i2s_bclk: GPIO12 # internal for speaker
  groove_port_c_rx_pin: GPIO13 # ext MIC CLK
  groove_port_c_tx_pin: GPIO14 # ext MIC DATA
  aws_core2_side_leds_pin: GPIO25
  groove_port_a_sda_pin: GPIO32 # (IR Tx)
  groove_port_a_scl_pin: GPIO33 # (IR Rx)
  groove_port_b_dac_pin: GPIO26
  groove_port_b_adc_pin: GPIO36
  internal_i2c_sda_pin: GPIO21
  internal_i2c_scl_pin: GPIO22
  internal_spi_clk_pin: GPIO18
  internal_spi_mosi_pin: GPIO23
  internal_spi_miso_pin: GPIO38
  display_cs_pin: GPIO5
  display_dc_pin: GPIO15
  display_reset_pin: GPIO4
  count_volume_change: "2"

globals:
  - id: display_switch_var
    type: bool
    # initial_value: 'true'
    restore_value: yes
  - id: touch_x
    type: int
    initial_value: '0'
  - id: touch_y
    type: int
    initial_value: '0'
  - id: touch_state
    type: int
    initial_value: '0'

esphome:
  name: $esp_name
  friendly_name: $esp_name
  on_boot:
    - priority: -100.0
      then:
        # - light.turn_on: ${esp_name1}_led
        # - delay: 5s
        # - light.turn_off: ${esp_name1}_led
        # - switch.turn_on: ${esp_name1}_display_switch
        # -  lambda: |-
        #     ESP_LOGI("boot", "display_switch_var:  %s", id(display_switch_var) ? "ON" : "OFF");
        #     if (id(display_switch_var)) {
        #       id(${esp_name1}_display_switch).turn_on();
        #     }
          - logger.log: 
              level: INFO
              format: "boot finished"

esp32:
  board: $board
  framework:
    type: arduino

external_components:
  - source: github://viotemp1/esphome-sound-level-meter 
  - source:
      type: local
      path: my_components

# Enable logging
logger:
  level: !secret log_level # DEBUG
  # level: DEBUG
  logs:
    api.connection: ERROR
    component: ERROR
    graph: ERROR
    main: INFO
    remote.pronto: INFO
    uptime.sensor: INFO

# Enable Home Assistant API
api:
  encryption:
    key: ...

ota:
  password: ...

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  use_address: $ip
  domain: .local
  reboot_timeout: !secret reboot_timeout
  fast_connect: on
  manual_ip:
    static_ip: $ip
    gateway: 10.0.0.1
    subnet: 255.255.255.0
color:
  - id: color_blue
    red: 0%
    green: 0%
    blue: 100%
  - id: color_red
    red: 100%
    green: 0%
    blue: 0%
  - id: color_green
    red: 0%
    green: 100%
    blue: 0%
  - id: color_white
    red: 100%
    green: 100%
    blue: 100%
  - id: color_black
    red: 0%
    green: 0%
    blue: 0%
  - id: color_dark_green
    red: 0%
    green: 50%
    blue: 0%
  - id: color_dark_red
    red: 50%
    green: 0%
    blue: 0%
  - id: color_dark_blue
    red: 0%
    green: 0%
    blue: 50%

font:
  - file: "gfonts://Roboto@medium"
    id: font_roboto
    size: 22
  - file: "gfonts://Roboto@medium"
    id: font_roboto_med
    size: 56
  - file: "gfonts://Roboto@medium"
    id: font_roboto_big
    size: 80
  - file: "gfonts://Roboto@medium"
    id: font_roboto_10
    size: 10
  - file: "gfonts://Roboto@medium"
    id: font_roboto_20
    size: 20

light:
  - platform: neopixelbus
    type: GRB
    variant: SK6812
    pin: $aws_core2_side_leds_pin
    num_leds: 10
    name: "${esp_name1}_LED_Light"
    id: ${esp_name1}_led
    restore_mode: ALWAYS_OFF

# remote_receiver:
#   pin:
#     number: $groove_port_a_scl_pin # GPIO33 # (IR Rx)
#     inverted: true
#   dump: raw # all raw

remote_transmitter:
  pin: $groove_port_a_sda_pin # GPIO32 # (IR Tx)
  carrier_duty_percent: 50%

button:
  - platform: restart
    name: ${esp_name1}_Restart
    id: ${esp_name1}_restart_switch
  - platform: template
    name: "${esp_name1}_Sound Level Meter Toggle"
    on_press:
      - sound_level_meter.toggle: my_sound_level_meter
....

time:
  - platform: homeassistant
    id: esptime

spi:
  clk_pin: $internal_spi_clk_pin
  mosi_pin: $internal_spi_mosi_pin
  miso_pin: $internal_spi_miso_pin

i2c:
   - id: bus_internal
     sda: $internal_i2c_sda_pin
     scl: $internal_i2c_scl_pin
     frequency: 800kHz
     scan: True

switch:
  - platform: template
    name: "${esp_name1} Display Switch"
    id: ${esp_name1}_display_switch
    lambda: |-
      if (id(display_switch_var)) {
        return true;
      } else {
        return false;
      }
    turn_on_action:
        - lambda: |-
            id(display_switch_var) = true;
            id(axp192_id).set_brightness(0.75);
    turn_off_action:
        - lambda: |-
            id(display_switch_var) = false;
            id(axp192_id).set_brightness(0.0);

sensor:
  - platform: wifi_signal
    name: "${esp_name1}_WiFi_Signal"
    id: wifi_signal_sensor
    update_interval: 60s
  - platform: uptime
    name: "${esp_name1}_Uptime"
    id: ${esp_name1}_uptime_sensor
    accuracy_decimals: 0
    internal: true
    update_interval: 1s
  - platform: mpu6886
    i2c_id: bus_internal
    address: 0x68
    temperature:
      name: "${esp_name1}_Temperature"
  - platform: axp192
    id: axp192_id
    model: M5CORE2
    address: 0x34
    i2c_id: bus_internal
    update_interval: 30s
    brightness: 75%
    battery_level:
      name: "${esp_name1} Battery Level"
      id: "${esp_name1}_battery_level"
  - platform: template
    name: "${esp_name1} Vin Voltage"
    id: "${esp_name1}_get_vin_voltage"
    lambda: return id(axp192_id).GetVinVoltage();
    update_interval: 60s
    force_update: True
    unit_of_measurement: "V"
  - platform: template
    name: "${esp_name1} Battery Voltage"
    id: "${esp_name1}_get_bat_voltage"
    lambda: return id(axp192_id).GetBatVoltage();
    update_interval: 60s
    force_update: True
    unit_of_measurement: "V"
  - platform: template
    name: "${esp_name1} APS Voltage"
    id: "${esp_name1}_get_aps_voltage"
    lambda: return id(axp192_id).GetAPSVoltage();
    update_interval: 60s
    force_update: True
    unit_of_measurement: "V"
  - platform: homeassistant
    name: "${esp_name1} Living Threshold"
    entity_id: input_number.decibel_meter_living_threshold
    id: decibel_meter_living_threshold
    internal: true
  - platform: template
    name: "Sound Level Peak 1min"
    id: ${esp_name1}_sound_level_peak_1min
    # lambda: return id(${esp_name1}_sound_level_peak).state;
    update_interval: 1s
    unit_of_measurement: dBZ # dBA
    filters:
      - max:
          window_size: 60
          send_every: 1
          send_first_at: 1

text_sensor:
  - platform: wifi_info
    ip_address:
      name: ${esp_name1}_IP_Address
    ssid:
      name: ${esp_name1}_ESP_Connected_SSID
      id: wifi_info_ssid_sensor
    bssid:
      name: ${esp_name1}_ESP_Connected_BSSID
    mac_address:
      name: ${esp_name1}_ESP_Mac_Wifi_Address
  - platform: template
    name: "display_text"
    id: display_text
    internal: True
    # lambda: |-
    #   return {""};
    update_interval: 60s
    # on_value:
    #   then:
    #     - lambda: |-
    #         ESP_LOGI("display_text", "value changed %s", x.c_str());

binary_sensor:
  - platform: status
    name: ${esp_name}_Status
    id: esp32_status
  - platform: homeassistant
    name: "HA Decibel Meter Living Enabled"
    entity_id: input_boolean.decibel_meter_living
    id: ha_decibel_meter_living_enabled
    internal: True
    on_press:
      then:
        - sound_level_meter.turn_on
    on_release:
      then:
        - sound_level_meter.turn_off
  - platform: template
    name: "${esp_name}_high_sound_level"
    id: ${esp_name1}_high_sound_level
    lambda: |-
      if (id(${esp_name1}_sound_level_peak).state > id(decibel_meter_living_threshold).state) {
        if (!id(ir_volume_down_up_script).is_running()) {
            id(ir_volume_down_up_script).execute();
        }
        return true;
      } else {
        return false;
      }
  - platform: template
    name: "${esp_name}_high_sound_level_running"
    lambda: |-
      if (id(ir_volume_down_up_script).is_running()) {
        return true;
      } else {
        return false;
      }

i2s:
  ws_pin: $groove_port_c_rx_pin #: GPIO13 # ext MIC CLK
  din_pin: $groove_port_c_tx_pin #: GPIO14 # ext MIC DATA
  sample_rate: 16000 # 44100 48000
  bits_per_sample: 16
  dma_buf_count: 2
  dma_buf_len: 128
  use_apll: true # true false
  bits_shift: 0 # 8 0 4
  # pdm: true

sound_level_meter:
  id: my_sound_level_meter
  # update_interval specifies over which interval to aggregate audio data
  # you can specify default update_interval on top level, but you can also
  # override it further by specifying it on sensor level
  update_interval: 1s           # default: 60s
  # ignore audio data at startup for this long
  warmup_interval: 3s        # default: 500ms

  # buffer_size is in samples (not bytes), so for float data type
  # number of bytes will be buffer_size * 4
  # buffer_size: 1024             # default: 1024

  # see your mic datasheet to find sensitivity and reference SPL.
  # those are used to convert dB FS to db SPL
  mic_sensitivity: 0dB        # default: empty
  mic_sensitivity_ref: 0dB     # default: empty
  offset: 29dB
  multiplier: 10dB

  groups:
    - sensors:
        - type: eq
          name: Leq_1s
          id: "sound_level"
          internal: true
          update_interval: 0.2s
        - type: peak
          name: "Sound Level Peak"
          id: ${esp_name1}_sound_level_peak
          unit_of_measurement: dBZ # dBZ
          # internal: true
          on_value:
            then:
              lambda: |-
                id(decibel_meter_living_threshold).publish_state(id(decibel_meter_living_threshold).state);
                id(${esp_name1}_sound_level_peak_1min).publish_state(x);

          #     - logger.log: 
          #         level: INFO
          #         format: "sound_level_peak: %.2f"
          #         args: ['x']

graph:
  # Show bare-minimum auto-ranged graph
  - id: sound_level_graph
    duration: 1min
    width: 300
    height: 180
    traces:
      - sensor: decibel_meter_living_threshold
        line_type: SOLID
        continuous: true
        line_thickness: 2
        color: color_red
      - sensor: ${esp_name1}_sound_level_peak
        line_type: SOLID
        continuous: true
        line_thickness: 2
        color: color_blue

script:
  - id: ir_volume_down_up_script
    mode: single
    then:
      # - logger.log: 
      #     format: "High sound level detected. Volume will be adjusted"
      #     level: INFO
...
  - id: turn_on_display
    mode: single
    then:
      - switch.turn_on: ${esp_name1}_display_switch
      - delay: 60s
      - switch.turn_off: ${esp_name1}_display_switch

display:
  - platform: ili9xxx # ili9341
    model: M5STACK # TFT 2.4R (ILI9342) / M5STACK
    cs_pin: $display_cs_pin
    dc_pin: $display_dc_pin
    reset_pin: $display_reset_pin
    # rotation: 90°
    update_interval: 1s
    id: core2_display
    lambda: |-
      int d_width = it.get_width();
      int d_height = it.get_height();
      int d_h_width = int(d_width/2);
      int d_h_height = int(d_height/2);
      // bool battery_level_state = id(${esp_name1}_battery_level).state > 80 ? true : false;
      // ESP_LOGI("display dimensions", "width: %d / d_height: %d", d_width, d_height); // 320x240
      it.fill(COLOR_OFF);

      if (id(${esp_name1}_display_switch).state)
      {
        //auto touch = id(my_touchscreen)->get_touch();
        //if (touch) { // or touch.has_value()
        //  it.filled_circle(touch.value().x, touch.value().y, 10, color_red);
        //}

        // No display_text
        if (id(display_text).state == "") {
          it.strftime(10, 0, id(font_roboto), TextAlign::TOP_LEFT, "%H:%M:%S", id(esptime).now());
          it.printf(d_width-10, 0, id(font_roboto), TextAlign::TOP_RIGHT, "%.02f / %.02f", id(${esp_name1}_sound_level_peak).state, id(${esp_name1}_sound_level_peak_1min).state);
          it.graph(10, 30, id(sound_level_graph));

          if (id(esp32_status).state) {
            it.filled_rectangle(0, d_height -25, d_h_width, 25, color_dark_green);
          } 
          else {
            it.filled_rectangle(0, d_height -25, d_h_width, 25, color_dark_red);
          }
          if (id(${esp_name1}_battery_level).state > 80 ) {
            it.filled_rectangle(d_h_width, d_height -25, d_width, 25, color_dark_green);
          }
          else if (id(${esp_name1}_battery_level).state > 50 ) {
            it.filled_rectangle(d_h_width, d_height -25, d_width, 25, color_dark_blue);
          }
          else {
            it.filled_rectangle(d_h_width, d_height -25, d_width, 25, color_dark_red);
          }
          it.printf(d_h_width, d_height, id(font_roboto), TextAlign::BOTTOM_CENTER, "%s / %s / %.0f", id(esp32_status).state ? "ON" : "OFF", id(wifi_info_ssid_sensor).state.c_str(), id(wifi_signal_sensor).state);
          it.printf(d_width, d_height, id(font_roboto), TextAlign::BOTTOM_RIGHT, "%.0f", id(${esp_name1}_battery_level).state);
        }
        // display_text present
        else if (id(display_text).state != ""){
          it.printf(d_h_width, d_h_height, id(font_roboto_med), TextAlign::CENTER_HORIZONTAL , "%s", id(display_text).state.c_str());
        }
      }