From 94ed17815c64d4469a1b801aa7b2d652788d810d Mon Sep 17 00:00:00 2001 From: mtmatteo79 <74114415+mtmatteo79@users.noreply.github.com> Date: Thu, 1 Oct 2026 01:55:39 +0200 Subject: [PATCH] [bthome_mithermometer] Decode 0.1C temperature and 1% humidity objects (#19956) --- .../bthome_mithermometer/bthome_ble.cpp | 15 +++++ .../sensor/bthome_objects_test.cpp | 66 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 tests/components/bthome_mithermometer/sensor/bthome_objects_test.cpp diff --git a/esphome/components/bthome_mithermometer/bthome_ble.cpp b/esphome/components/bthome_mithermometer/bthome_ble.cpp index 1ebabea0a3..51f5d866a4 100644 --- a/esphome/components/bthome_mithermometer/bthome_ble.cpp +++ b/esphome/components/bthome_mithermometer/bthome_ble.cpp @@ -422,6 +422,21 @@ bool BTHomeMiThermometer::handle_service_data_(const ble_device_base::ServiceDat } break; } + case 0x45: { // temperature (0.1C), e.g. Shelly BLU H&T + if (this->temperature_ != nullptr) { + const int16_t raw = encode_uint16(value[1], value[0]); + this->temperature_->publish_state(raw * 0.1f); + reported = true; + } + break; + } + case 0x2E: { // humidity (uint8, 1%), e.g. Shelly BLU H&T + if (this->humidity_ != nullptr) { + this->humidity_->publish_state(value[0]); + reported = true; + } + break; + } default: break; } diff --git a/tests/components/bthome_mithermometer/sensor/bthome_objects_test.cpp b/tests/components/bthome_mithermometer/sensor/bthome_objects_test.cpp new file mode 100644 index 0000000000..e3903212be --- /dev/null +++ b/tests/components/bthome_mithermometer/sensor/bthome_objects_test.cpp @@ -0,0 +1,66 @@ +#include + +#include +#include +#include + +#include "esphome/components/bthome_mithermometer/bthome_ble.h" + +namespace esphome::bthome_mithermometer::testing { + +namespace { + +constexpr uint64_t SENSOR_ADDRESS = 0xA4C1384E1678ULL; + +// Unencrypted BTHome v2 service data (UUID 0xFCD2) sent from SENSOR_ADDRESS. +ble_device_base::ESPBTDevice advert(std::initializer_list service_data) { + std::vector adv = {static_cast(service_data.size() + 3), 0x16, 0xD2, 0xFC}; + adv.insert(adv.end(), service_data.begin(), service_data.end()); + uint8_t mac[6]; + for (size_t i = 0; i < 6; i++) + mac[i] = static_cast(SENSOR_ADDRESS >> (i * 8)); + ble_device_base::ESPBTDevice device; + device.from_scan_result(mac, -60, 0, adv.data(), static_cast(adv.size())); + return device; +} + +struct Harness { + Harness() { + this->thermometer.set_address(SENSOR_ADDRESS); + this->thermometer.set_temperature(&this->temperature); + this->thermometer.set_humidity(&this->humidity); + this->thermometer.set_battery_level(&this->battery_level); + } + + BTHomeMiThermometer thermometer; + sensor::Sensor temperature, humidity, battery_level; +}; + +} // namespace + +// Shelly BLU H&T: packet id, battery 90 %, humidity 55 % (0x2E), temperature 22.5 C (0x45) +TEST(BTHomeMiThermometerObjects, DecodesShellyStyleTemperatureAndHumidity) { + Harness h; + ASSERT_TRUE(h.thermometer.parse_device(advert({0x40, 0x00, 0x01, 0x01, 0x5A, 0x2E, 0x37, 0x45, 0xE1, 0x00}))); + EXPECT_NEAR(h.temperature.state, 22.5f, 0.001f); + EXPECT_FLOAT_EQ(h.humidity.state, 55.0f); + EXPECT_FLOAT_EQ(h.battery_level.state, 90.0f); +} + +TEST(BTHomeMiThermometerObjects, DecodesNegativeTenthDegreeTemperature) { + Harness h; + // -5.3 C = -53 = 0xFFCB + ASSERT_TRUE(h.thermometer.parse_device(advert({0x40, 0x45, 0xCB, 0xFF}))); + EXPECT_NEAR(h.temperature.state, -5.3f, 0.001f); + EXPECT_FALSE(h.humidity.has_state()); +} + +// PVVX firmware: temperature 22.50 C (0x02) and humidity 45.67 % (0x03) keep working +TEST(BTHomeMiThermometerObjects, StillDecodesHundredthDegreeObjects) { + Harness h; + ASSERT_TRUE(h.thermometer.parse_device(advert({0x40, 0x02, 0xCA, 0x08, 0x03, 0xD7, 0x11}))); + EXPECT_NEAR(h.temperature.state, 22.5f, 0.001f); + EXPECT_NEAR(h.humidity.state, 45.67f, 0.001f); +} + +} // namespace esphome::bthome_mithermometer::testing