Integrando Una Báscula Bluetooth en Home Assistant: Mi Experiencia Y Resultados

Integrando una báscula Bluetooth Swisshome con Home Assistant usando ESP32 y ESPHome

¡Hola a todos!

Quería compartir cómo integré mi báscula Bluetooth Swisshome con Home Assistant utilizando un ESP32 y ESPHome.
Lo mejor de todo es que esta báscula económica no necesita ninguna aplicación adicional: basta con interceptar las emisiones Bluetooth que lanza, y enviarlas a Home Assistant.


¿Qué hace esta báscula?

Esta báscula transmite paquetes de datos por Bluetooth, que incluyen:

  • El peso corporal
  • La impedancia corporal

Cada uno de estos datos se identifica por un tipo de paquete y necesita una operación XOR para decodificarse correctamente.


Pasos clave del proyecto

Decodificación de los datos

La báscula emite dos tipos de paquetes:

  • 0xAD → para el peso corporal
  • 0xA6 → para la impedancia

Ambos necesitan aplicar una operación XOR con una clave derivada del Company ID (0xA0AC) para extraer los valores.


Configurando el ESP32 con ESPHome

Se usa ESPHome para configurar el ESP32, conectarlo a la red y escanear dispositivos Bluetooth cercanos.

A continuación te comparto un ejemplo del código que utilicé:

sensor:
  - platform: template
    name: "Peso Báscula"
    id: weight_sensor
    unit_of_measurement: "kg"
    accuracy_decimals: 2
    icon: "mdi:weight-kilogram"
    
  - platform: template
    name: "Impedancia (Z)"
    id: body_impedancia_sensor
    unit_of_measurement: "Ω"
    accuracy_decimals: 0
    icon: "mdi:scale-bathroom"

esp32_ble_tracker:
  scan_parameters:
    interval: 1100ms
    window: 1100ms
    active: true
  on_ble_advertise:
    - mac_address:
        - XX:XX:XX:XX:XX:XX  # MAC address de tu báscula
      then:
        - lambda: |-
            const uint16_t company_id = 0xA0AC;
            int xor_key = company_id >> 8;

            for (auto data : x.get_manufacturer_datas()) {
              if (data.data.size() >= 12) {
                std::vector<uint8_t> buf(6);
                for (int i = 0; i < 6; i++) {
                  buf[i] = data.data[i + 6] ^ xor_key;
                }

                int chk = 0;
                for (int i = 0; i < 5; i++) {
                  chk += buf[i];
                }

                if ((chk & 0x1F) != (buf[5] & 0x1F)) {
                  ESP_LOGD("ble_adv", "Checksum error");
                  return;
                }

                uint8_t packet_type = buf[4];
                if (packet_type == 0xAD) {
                  int32_t value = (buf[3] | (buf[2] << 8) | (buf[1] << 16) | (buf[0] << 24));
                  uint8_t state = (value >> 31) & 0x1;
                  int grams = value & 0x3FFFF;
                  float weight_kg = grams / 1000.0;

                  ESP_LOGD("ble_adv", "Peso: %.2f kg, state: %d", weight_kg, state);
                  if (state != 0) {
                    ESP_LOGD("ble_adv", "Medición completada.");
                    id(weight_sensor).publish_state(weight_kg);
                  }
                } else if (packet_type == 0xA6) {
                  int fat_raw = ((buf[1]) << 8) | buf[0];
                  ESP_LOGD("ble_adv", "Impedancia: %d", fat_raw);
                  float impedancia = fat_raw / 10.0;
                  ESP_LOGD("ble_adv", "Impedancia (Z): %.2f Ω", impedancia);
                  id(body_impedancia_sensor).publish_state(impedancia);
                } else {
                  ESP_LOGD("ble_adv", "Tipo de paquete no admitido: 0x%02X", packet_type);
                }
              }
            }

💬 Anímate a dejar tu duda, sugerencia o comentario relacionado con este artículo. ¡Te leo!