Praise for AI assistance for YAML

,

I wanted my BTHome temp sensors to publish their data directly to MQTT, and rather than setting up an automation in Home Assistant, I figured an ESP32 could do it directly. But the syntax was way beyond me. So I asked claude.ai and it spit it out for me almost perfectly. So much easier than trudging through forum posts trying to figure out indents and spacing and all the intricacies of YAML configs.


esphome:
  name: esp32-bluetooth-proxy
  friendly_name: ESP32 Bluetooth Proxy

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "your-api-encryption-key-here"

# OTA updates
ota:
  - platform: esphome
    password: "your-ota-password-here"

# WiFi configuration
wifi:
  ssid: "Your_WiFi_SSID"
  password: "Your_WiFi_Password"
  
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "ESP32-Bluetooth-Proxy Fallback Hotspot"
    password: "fallback-password"

captive_portal:

# Web server (optional - for debugging)
web_server:
  port: 80

# MQTT configuration
mqtt:
  broker: 192.168.1.100  # Replace with your MQTT broker IP
  port: 1883
  username: "your-mqtt-username"
  password: "your-mqtt-password"
  client_id: esp32_bluetooth_proxy
  discovery: true
  discovery_retain: false
  topic_prefix: "homeassistant"
  id: mqtt_client_component  # ID for referencing in lambda functions
  birth_message:
    topic: "homeassistant/esp32_bluetooth_proxy/status"
    payload: "online"
  will_message:
    topic: "homeassistant/esp32_bluetooth_proxy/status"
    payload: "offline"

# Global variables for device name mapping
globals:
  - id: device_names
    type: std::map<std::string, std::string>
    restore_value: no
    initial_value: '{
      {"AA:BB:CC:DD:EE:FF", "bedroom_sensor"},
      {"11:22:33:44:55:66", "living_room_sensor"},
      {"77:88:99:AA:BB:CC", "kitchen_thermometer"},
      {"DD:EE:FF:00:11:22", "bathroom_humidity"},
      {"33:44:55:66:77:88", "outdoor_weather"}
    }'

# Bluetooth proxy and BTHome configuration
esp32_ble_tracker:
  scan_parameters:
    interval: 320ms
    window: 30ms
    active: false  # Passive scanning for better performance with many devices
  on_ble_advertise:
    then:
      - lambda: |-
          // Log BLE advertisement for debugging
          ESP_LOGD("ble_adv", "BLE Advertisement from %s (RSSI: %d)", x.address_str().c_str(), x.get_rssi());
          
          // Check if this is a BTHome advertisement
          for (auto service_data : x.get_service_datas()) {
            if (service_data.uuid == esp32_ble::ESPBTUUID::from_uint16(0x181C) || 
                service_data.uuid == esp32_ble::ESPBTUUID::from_uint16(0xFCD2)) {
              ESP_LOGD("bthome", "BTHome advertisement detected from %s", x.address_str().c_str());
              
              // Parse BTHome data and publish to MQTT
              std::string mac_address = x.address_str();
              std::string device_name = mac_address;
              
              // Replace MAC with friendly name if found in device_names map
              std::replace(device_name.begin(), device_name.end(), ':', '_');
              if (id(device_names).count(mac_address) > 0) {
                device_name = id(device_names)[mac_address];
              }
              
              // Parse BTHome format and publish individual sensors
              if (service_data.data.size() >= 3) {
                uint8_t bthome_info = service_data.data[0];
                bool encrypted = (bthome_info & 0x01) != 0;
                uint8_t version = (bthome_info >> 5) & 0x07;
                
                if (!encrypted && version == 2) {  // BTHome v2, unencrypted
                  // Parse sensor data starting from byte 1 and build JSON
                  std::string json_data = "{";
                  json_data += "\"device\":\"" + device_name + "\",";
                  json_data += "\"mac\":\"" + mac_address + "\",";
                  json_data += "\"rssi\":" + to_string(x.get_rssi()) + ",";
                  json_data += "\"timestamp\":" + to_string(millis()) + ",";
                  
                  bool first_sensor = true;
                  
                  for (int i = 1; i < service_data.data.size(); ) {
                    if (i >= service_data.data.size()) break;
                    
                    uint8_t object_id = service_data.data[i++];
                    std::string sensor_key = "";
                    std::string sensor_value = "";
                    std::string sensor_unit = "";
                    
                    switch (object_id) {
                      case 0x01: // Battery %
                        if (i < service_data.data.size()) {
                          sensor_key = "battery";
                          sensor_value = to_string(service_data.data[i++]);
                          sensor_unit = "%";
                        }
                        break;
                      case 0x02: // Temperature °C
                        if (i + 1 < service_data.data.size()) {
                          sensor_key = "temperature";
                          int16_t temp = (service_data.data[i+1] << 8) | service_data.data[i];
                          sensor_value = to_string(temp / 100.0f);
                          sensor_unit = "°C";
                          i += 2;
                        }
                        break;
                      case 0x03: // Humidity %
                        if (i + 1 < service_data.data.size()) {
                          sensor_key = "humidity";
                          uint16_t hum = (service_data.data[i+1] << 8) | service_data.data[i];
                          sensor_value = to_string(hum / 100.0f);
                          sensor_unit = "%";
                          i += 2;
                        }
                        break;
                      case 0x04: // Pressure hPa
                        if (i + 2 < service_data.data.size()) {
                          sensor_key = "pressure";
                          uint32_t press = (service_data.data[i+2] << 16) | (service_data.data[i+1] << 8) | service_data.data[i];
                          sensor_value = to_string(press / 100.0f);
                          sensor_unit = "hPa";
                          i += 3;
                        }
                        break;
                      case 0x05: // Illuminance lux
                        if (i + 2 < service_data.data.size()) {
                          sensor_key = "illuminance";
                          uint32_t lux = (service_data.data[i+2] << 16) | (service_data.data[i+1] << 8) | service_data.data[i];
                          sensor_value = to_string(lux / 100.0f);
                          sensor_unit = "lx";
                          i += 3;
                        }
                        break;
                      case 0x06: // Mass kg
                        if (i + 1 < service_data.data.size()) {
                          sensor_key = "mass";
                          uint16_t mass = (service_data.data[i+1] << 8) | service_data.data[i];
                          sensor_value = to_string(mass / 100.0f);
                          sensor_unit = "kg";
                          i += 2;
                        }
                        break;
                      case 0x07: // Mass lb
                        if (i + 1 < service_data.data.size()) {
                          sensor_key = "mass_lb";
                          uint16_t mass = (service_data.data[i+1] << 8) | service_data.data[i];
                          sensor_value = to_string(mass / 100.0f);
                          sensor_unit = "lb";
                          i += 2;
                        }
                        break;
                      case 0x08: // Moisture %
                        if (i + 1 < service_data.data.size()) {
                          sensor_key = "moisture";
                          uint16_t moist = (service_data.data[i+1] << 8) | service_data.data[i];
                          sensor_value = to_string(moist / 100.0f);
                          sensor_unit = "%";
                          i += 2;
                        }
                        break;
                      case 0x0A: // CO2 ppm
                        if (i + 1 < service_data.data.size()) {
                          sensor_key = "co2";
                          uint16_t co2 = (service_data.data[i+1] << 8) | service_data.data[i];
                          sensor_value = to_string(co2);
                          sensor_unit = "ppm";
                          i += 2;
                        }
                        break;
                      case 0x0D: // PM2.5 µg/m³
                        if (i + 1 < service_data.data.size()) {
                          sensor_key = "pm25";
                          uint16_t pm = (service_data.data[i+1] << 8) | service_data.data[i];
                          sensor_value = to_string(pm);
                          sensor_unit = "µg/m³";
                          i += 2;
                        }
                        break;
                      case 0x0E: // PM10 µg/m³
                        if (i + 1 < service_data.data.size()) {
                          sensor_key = "pm10";
                          uint16_t pm = (service_data.data[i+1] << 8) | service_data.data[i];
                          sensor_value = to_string(pm);
                          sensor_unit = "µg/m³";
                          i += 2;
                        }
                        break;
                      case 0x0F: // Binary sensor (generic)
                        if (i < service_data.data.size()) {
                          sensor_key = "binary_sensor";
                          sensor_value = service_data.data[i++] ? "true" : "false";
                          sensor_unit = "";
                        }
                        break;
                      case 0x10: // Power W
                        if (i + 2 < service_data.data.size()) {
                          sensor_key = "power";
                          uint32_t power = (service_data.data[i+2] << 16) | (service_data.data[i+1] << 8) | service_data.data[i];
                          sensor_value = to_string(power / 100.0f);
                          sensor_unit = "W";
                          i += 3;
                        }
                        break;
                      case 0x11: // Voltage V
                        if (i + 1 < service_data.data.size()) {
                          sensor_key = "voltage";
                          uint16_t volt = (service_data.data[i+1] << 8) | service_data.data[i];
                          sensor_value = to_string(volt / 1000.0f);
                          sensor_unit = "V";
                          i += 2;
                        }
                        break;
                      case 0x15: // Window/Door sensor
                        if (i < service_data.data.size()) {
                          sensor_key = "door";
                          sensor_value = service_data.data[i++] ? "true" : "false";
                          sensor_unit = "";
                        }
                        break;
                      case 0x16: // Motion sensor
                        if (i < service_data.data.size()) {
                          sensor_key = "motion";
                          sensor_value = service_data.data[i++] ? "true" : "false";
                          sensor_unit = "";
                        }
                        break;
                      default:
                        // Skip unknown sensor types
                        ESP_LOGW("bthome", "Unknown BTHome object ID: 0x%02X", object_id);
                        i++; // Skip at least one byte
                        break;
                    }
                    
                    // Add sensor data to JSON if we parsed something
                    if (!sensor_key.empty() && !sensor_value.empty()) {
                      if (!first_sensor) {
                        json_data += ",";
                      }
                      json_data += "\"" + sensor_key + "\":{";
                      json_data += "\"value\":" + sensor_value;
                      if (!sensor_unit.empty()) {
                        json_data += ",\"unit\":\"" + sensor_unit + "\"";
                      }
                      json_data += "}";
                      first_sensor = false;
                    }
                  }
                  
                  json_data += "}";
                  
                  // Publish JSON data to single topic
                  std::string json_topic = "bthome/" + device_name + "/sensors";
                  id(mqtt_client_component).publish(json_topic, json_data);
                  ESP_LOGD("bthome", "Published JSON: %s", json_data.c_str());
                }
              }
              break; // Only process first BTHome service data
            }
          }

bluetooth_proxy:
  active: true

# Status LED (optional)
status_led:
  pin: GPIO2

# Example sensors that publish to MQTT
sensor:
  # WiFi signal strength
  - platform: wifi_signal
    name: "WiFi Signal Strength"
    id: wifi_signal_db
    update_interval: 60s
    entity_category: "diagnostic"
    on_value:
      then:
        - mqtt.publish:
            topic: "esp32/wifi_signal"
            payload: !lambda |-
              return to_string(id(wifi_signal_db).state);

  # Uptime sensor
  - platform: uptime
    name: "Uptime"
    id: uptime_sensor
    update_interval: 60s
    on_value:
      then:
        - mqtt.publish:
            topic: "esp32/uptime"
            payload: !lambda |-
              return to_string(id(uptime_sensor).state);

# Text sensors
text_sensor:
  # Device info
  - platform: wifi_info
    ip_address:
      name: "IP Address"
      id: ip_address
      on_value:
        then:
          - mqtt.publish:
              topic: "esp32/ip_address"
              payload: !lambda |-
                return id(ip_address).state;
    
    ssid:
      name: "Connected SSID"
      id: connected_ssid
      on_value:
        then:
          - mqtt.publish:
              topic: "esp32/ssid"
              payload: !lambda |-
                return id(connected_ssid).state;

# Binary sensors
binary_sensor:
  # Connection status
  - platform: status
    name: "Status"
    id: device_status
    on_state:
      then:
        - mqtt.publish:
            topic: "esp32/status"
            payload: !lambda |-
              return id(device_status).state ? "online" : "offline";

# Button for restart (optional)
button:
  - platform: restart
    name: "Restart"
    id: restart_button

# Example: GPIO sensors/controls (customize as needed)
# Uncomment and modify based on your hardware setup

# switch:
#   - platform: gpio
#     pin: GPIO4
#     name: "GPIO4 Switch"
#     id: gpio4_switch
#     on_turn_on:
#       then:
#         - mqtt.publish:
#             topic: "esp32/gpio4"
#             payload: "ON"
#     on_turn_off:
#       then:
#         - mqtt.publish:
#             topic: "esp32/gpio4"
#             payload: "OFF"

# binary_sensor:
#   - platform: gpio
#     pin: 
#       number: GPIO5
#       mode: INPUT_PULLUP
#     name: "GPIO5 Button"
#     id: gpio5_button
#     on_press:
#       then:
#         - mqtt.publish:
#             topic: "esp32/gpio5_button"
#             payload: "PRESSED"
#     on_release:
#       then:
#         - mqtt.publish:
#             topic: "esp32/gpio5_button"
#             payload: "RELEASED"
1 Like

Glad it is working for you. For what you wanted this might have been easier:

2 Likes

You may run into problems running API and MQTT on the ESP. At least its unnecessary to have API if using MQTT. Web server is memory intensive and so is aurduino. BLE proxy is also quite intensive on resources. The BLE scan interval and window look quite short.

    on_value:
      then:
        - mqtt.publish:
            topic: "esp32/uptime"
            payload: !lambda |-
              return to_string(id(uptime_sensor).state);

None of this code is necessary as the sensor will report values to MQTT. It’ll be the same with many of your other sensors. They will get reported to MQTT.

Does the device manage to stay up for long periods or restart quite a lot. Most of mine will stay up for several million seconds before I update stuff.

1 Like

And this demonstration shows that while AI can frequently give you something that works, it probably isn’t going to give you the better or best solution to your problem. Oftentimes a good enough for who its for solution is all that is necessary.

3 Likes

For my very limited experience, AI is reliable for correcting indentation (like OP mentioned). For the rest, I didn’t get any reliable results. My daughter is happy heavy-user though, she relies on ChatGPT in everything… :roll_eyes:

1 Like


There has to be some middle ground

4 Likes

This topic is a shining example of why we do not allow users to help others with LLMs.

You are of course free to use LLMs yourself. Just do not use them to post answers to other peoples questions here or you will be banned.

4 Likes