ESPHome Dynamic Uart debugging

I’m trying to debug what my airconditioner is sending via UART, so far I havent had any luck seeing any data at all and I assume its due to the UART settings being incorrect, I am making my way through variations but recompiling takes time each change and I want to be able to dynamically cycle through options.

I’ve managed to cobble together some partial code through copilot but the intracacies of the cpp api are stumping me

substitutions:
  name: living-room-aircon
  friendly_name: Living Room Aircon

esphome:
  name: ${name}
  friendly_name: ${friendly_name}
  
esp32:
  board: esp32dev
  framework: 
    type: esp-idf

# debug:
#  update_interval: 10s

# Enable logging
logger:

# Enable Home Assistant API
api:

# Allow Over-The-Air updates
ota:
- platform: esphome

uart:  
  id: my_uart
  baud_rate: 9600
  stop_bits: 2
  parity: NONE
  tx_pin:
    number: RX
  rx_pin: 
    number: TX    
  debug:
    direction: BOTH
    dummy_receiver: true
    sequence:
      - lambda: UARTDebug::log_string(direction, bytes);

web_server:
  port: 80

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

select:
  - id: change_baud_rate
    name: "Baud Rate"
    platform: template
    options:
      - "2400"
      - "9600"
      - "38400"
      - "57600"
      - "115200"
      - "256000"
      - "512000"
      - "921600"
    initial_option: "9600"
    optimistic: true
    restore_value: true
    entity_category: config
    icon: mdi:swap-horizontal
    set_action:
      - lambda: |-
          id(my_uart).flush();
          uint32_t new_baud_rate = stoi(x);
          ESP_LOGD("change_baud_rate", "Changing baud rate from %i to %i", id(my_uart).get_baud_rate(), new_baud_rate);
          if (id(my_uart).get_baud_rate() != new_baud_rate) {
            id(my_uart).set_baud_rate(new_baud_rate);
            id(my_uart).load_settings();
          }

  - id: change_stop_bits
    name: "Stop Bits"
    platform: template
    options:
      - "1"
      - "2"
    initial_option: "2"
    optimistic: true
    restore_value: true
    entity_category: config
    icon: mdi:swap-horizontal
    set_action:
      - lambda: |-
          uint8_t new_stop_bits = stoi(x);
          ESP_LOGD("change_stop_bits", "Changing stop bits from %i to %i", id(my_uart).get_stop_bits(), new_stop_bits);
          if (id(my_uart).get_stop_bits() != new_stop_bits) {
            id(my_uart).set_stop_bits(new_stop_bits);
            id(my_uart).load_settings();
          }

  - id: change_parity
    name: "Parity"
    platform: template
    options:
      - "NONE"
      - "ODD"
      - "EVEN"
    initial_option: "NONE"
    optimistic: true
    restore_value: true
    entity_category: config
    icon: mdi:swap-horizontal
    set_action:
      - lambda: |-
          UARTParityOptions new_parity;
          if (x == "NONE") {
            new_parity = UARTParityOptions.UART_CONFIG_PARITY_NONE;
          } else if (x == "ODD") {
            new_parity = UARTParityOptions.UART_CONFIG_PARITY_ODD;
          } else if (x == "EVEN") {
            new_parity = UARTParityOptions.UART_CONFIG_PARITY_EVEN;
          } else {
            new_parity = UART_PARITY_DISABLE;
          }
          ESP_LOGD("change_parity", "Changing parity from %i to %i", id(my_uart).get_parity(), new_parity);
          if (id(my_uart).get_parity() != new_parity) {
            id(my_uart).set_parity(new_parity);
            id(my_uart).load_settings();
          }  

  - id: change_tx_pin
    name: "TX Pin"
    platform: template
    options:
      - "GPIO1"
      - "GPIO3"

    initial_option: "GPIO1"
    optimistic: true
    restore_value: true
    entity_category: config
    icon: mdi:pin
    set_action:
      - lambda: |-
          // Expecting the option format "GPIO<number>"
          std::string pin_str(x.c_str());
          if (pin_str.find("GPIO") == 0) {
            int new_tx_pin = stoi(pin_str.substr(4));
            ESP_LOGD("change_tx_pin", "Changing TX pin to GPIO%i", new_tx_pin);
            auto *new_pin = new ESP32InternalGPIOPin();
            new_pin->set_pin(static_cast<gpio_num_t>(new_tx_pin));
            id(my_uart).set_tx_pin(new_pin);
            id(my_uart).load_settings();
          }

  - id: change_rx_pin
    name: "RX Pin"
    platform: template
    options:
      - "GPIO1"
      - "GPIO3"
    initial_option: "GPIO3"
    optimistic: true
    restore_value: true
    entity_category: config
    icon: mdi:pin
    set_action:
      - lambda: |-
          // Expecting the option format "GPIO<number>"
          std::string pin_str(x.c_str());
          if (pin_str.find("GPIO") == 0) {
            int new_rx_pin = stoi(pin_str.substr(4));
            ESP_LOGD("change_rx_pin", "Changing RX pin to GPIO%i", new_rx_pin);
            auto *new_pin = new ESP32InternalGPIOPin();
            new_pin->set_pin(static_cast<gpio_num_t>(new_rx_pin));
            id(my_uart).set_rx_pin(new_pin);
            id(my_uart).load_settings();
          }

  - id: change_data_bits
    name: "Data Bits"
    platform: template
    options:
      - "5"
      - "6"
      - "7"
      - "8"
    initial_option: "8"
    optimistic: true
    restore_value: true
    entity_category: config
    icon: mdi:format-letter-case
    set_action:
      - lambda: |-
          uint8_t new_data_bits = stoi(x);
          ESP_LOGD("change_data_bits", "Changing data bits from %i to %i", id(my_uart).get_data_bits(), new_data_bits);
          if (id(my_uart).get_data_bits() != new_data_bits) {
            id(my_uart).set_data_bits(new_data_bits);
            id(my_uart).load_settings();
          }

I get a few compile errors and am going in circles somewhat

/config/living-room-aircon.yaml: In lambda function:
/config/living-room-aircon.yaml:113:39: error: expected primary-expression before '.' token
  113 |             new_parity = UARTParityOptions.UART_CONFIG_PARITY_NONE;
      |                                       ^
/config/living-room-aircon.yaml:115:39: error: expected primary-expression before '.' token
  115 |             new_parity = UARTParityOptions.UART_CONFIG_PARITY_ODD;
      |                                       ^
/config/living-room-aircon.yaml:117:39: error: expected primary-expression before '.' token
  117 |             new_parity = UARTParityOptions.UART_CONFIG_PARITY_EVEN;
      |                                       ^
/config/living-room-aircon.yaml:119:22: error: cannot convert 'uart_parity_t' to 'esphome::uart::UARTParityOptions' in assignment
  119 |             new_parity = UART_PARITY_DISABLE;
      |                      ^~~~~~~~~~~~~~~~~~~
      |                      |
      |                      uart_parity_t
/config/living-room-aircon.yaml: In lambda function:
/config/living-room-aircon.yaml:146:29: error: expected type-specifier before 'ESP32InternalGPIOPin'
  146 |             auto *new_pin = new ESP32InternalGPIOPin();
      |                             ^~~~~~~~~~~~~~~~~~~~
/config/living-room-aircon.yaml: In lambda function:
/config/living-room-aircon.yaml:170:29: error: expected type-specifier before 'ESP32InternalGPIOPin'
  170 |             auto *new_pin = new ESP32InternalGPIOPin();
      |                             ^~~~~~~~~~~~~~~~~~~~

Can anyone point me in the right direction to get this working or alternative firmware that might be better suited to this?

I think I may have potentially figured it out.

substitutions:
  name: living-room-aircon
  friendly_name: Living Room Aircon

esphome:
  name: ${name}
  friendly_name: ${friendly_name}
  
esp32:
  board: esp32dev
  framework: 
    type: esp-idf

# debug:
#  update_interval: 10s

# Enable logging
logger:

# Enable Home Assistant API
api:

# Allow Over-The-Air updates
ota:
- platform: esphome

uart:  
  id: my_uart
  baud_rate: 9600
  stop_bits: 2
  parity: NONE
  tx_pin:
    number: RX
  rx_pin: 
    number: TX    
  debug:
    direction: BOTH
    dummy_receiver: true
    sequence:
      - lambda: UARTDebug::log_string(direction, bytes);

web_server:
  port: 80

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

select:
  - id: change_baud_rate
    name: "Baud Rate"
    platform: template
    options:
      - "2400"
      - "9600"
      - "38400"
      - "57600"
      - "115200"
      - "256000"
      - "512000"
      - "921600"
    initial_option: "9600"
    optimistic: true
    restore_value: true
    entity_category: config
    icon: mdi:swap-horizontal
    set_action:
      - lambda: |-
          id(my_uart).flush();
          uint32_t new_baud_rate = stoi(x);
          ESP_LOGD("change_baud_rate", "Changing baud rate from %i to %i", id(my_uart).get_baud_rate(), new_baud_rate);
          if (id(my_uart).get_baud_rate() != new_baud_rate) {
            id(my_uart).set_baud_rate(new_baud_rate);
            id(my_uart).load_settings();
          }

  - id: change_stop_bits
    name: "Stop Bits"
    platform: template
    options:
      - "1"
      - "2"
    initial_option: "2"
    optimistic: true
    restore_value: true
    entity_category: config
    icon: mdi:swap-horizontal
    set_action:
      - lambda: |-
          uint8_t new_stop_bits = stoi(x);
          ESP_LOGD("change_stop_bits", "Changing stop bits from %i to %i", id(my_uart).get_stop_bits(), new_stop_bits);
          if (id(my_uart).get_stop_bits() != new_stop_bits) {
            id(my_uart).set_stop_bits(new_stop_bits);
            id(my_uart).load_settings();
          }

  - id: change_parity
    name: "Parity"
    platform: template
    options:
      - "NONE"
      - "ODD"
      - "EVEN"
    initial_option: "NONE"
    optimistic: true
    restore_value: true
    entity_category: config
    icon: mdi:swap-horizontal
    set_action:
      - lambda: |-
          UARTParityOptions new_parity;
          if (x == "NONE") {
            new_parity = UART_PARITY_NONE;
          } else if (x == "ODD") {
            new_parity = UART_PARITY_ODD;
          } else if (x == "EVEN") {
            new_parity = UART_PARITY_EVEN;
          } else {
            new_parity = UART_PARITY_NONE;
          }
          ESP_LOGD("change_parity", "Changing parity from %i to %i", id(my_uart).get_parity(), new_parity);
          if (id(my_uart).get_parity() != new_parity) {
            id(my_uart).set_parity(new_parity);
            id(my_uart).load_settings();
          }  

  - id: change_tx_pin
    name: "TX Pin"
    platform: template
    options:
      - "GPIO1"
      - "GPIO3"
    initial_option: "GPIO1"
    optimistic: true
    restore_value: true
    entity_category: config
    icon: mdi:pin
    set_action:
      - lambda: |-
          // Expecting the option format "GPIO<number>"
          std::string pin_str(x.c_str());
          if (pin_str.find("GPIO") == 0) {
            int new_tx_pin = stoi(pin_str.substr(4));
            ESP_LOGD("change_tx_pin", "Changing TX pin to GPIO%i", new_tx_pin);
            id(my_uart).set_tx_pin(new InternalGPIOPin(new_tx_pin));
            id(my_uart).load_settings();
          }

  - id: change_rx_pin
    name: "RX Pin"
    platform: template
    options:
      - "GPIO1"
      - "GPIO3"
    initial_option: "GPIO3"
    optimistic: true
    restore_value: true
    entity_category: config
    icon: mdi:pin
    set_action:
      - lambda: |-
          // Expecting the option format "GPIO<number>"
          std::string pin_str(x.c_str());
          if (pin_str.find("GPIO") == 0) {
            int new_rx_pin = stoi(pin_str.substr(4));
            ESP_LOGD("change_rx_pin", "Changing RX pin to GPIO%i", new_rx_pin);
            id(my_uart).set_rx_pin(new InternalGPIOPin(new_rx_pin));
            id(my_uart).load_settings();
          }          


Whether it acutally works how I need it to is another thing!

Whenever I am doing something like this, unknown communication protocol. I start with a DMM to get an idea of the voltages. I then use an oscilloscope to get a picture of what it looks like. I then use a logic analyzer to get a better picture in the digital domain. If it is UART, the I know the baud rate, number of bits, number of stop bits, etc and some idea of the data.

With that I then use a UART connected to a PC running a terminal program that can log. This gets me a much better understanding of the data.

What’s the point of playing with uart pins? Since you didn’t disable logger on serial, default rx and tx are occupied for that. Set uart to other pins like 16rx and 17tx and then play with baudrates.
Also be aware that logic level of your AC might be different from 3.3v.

Does this matter if im powering it via 5v externally and not using the usb?

I have a logic analyser on order just have to wait 3 weeks from ali express now :smiley:

No, logger is polluting UART0 anyway (if you don’t disable serial logging). Just go with 16/17 and you don’t need to worry about pins.