ESPHome 2026.10.0-dev
Loading...
Searching...
No Matches
xiaomi_miscale.cpp
Go to the documentation of this file.
1#include "xiaomi_miscale.h"
3#include "esphome/core/log.h"
4
6
7static const char *const TAG = "xiaomi_miscale";
8
10 ESP_LOGCONFIG(TAG, "Xiaomi Miscale");
11 LOG_SENSOR(" ", "Weight", this->weight_);
12 LOG_SENSOR(" ", "Impedance", this->impedance_);
13}
14
16 if (device.address_uint64() != this->address_) {
17 ESP_LOGVV(TAG, "parse_device(): unknown MAC address.");
18 return false;
19 }
20 char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
21 const char *addr_str = device.address_str_to(addr_buf);
22 ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str);
23
24 bool success = false;
25 for (auto &service_data : device.get_service_datas()) {
26 auto res = parse_header_(service_data);
27 if (!res.has_value())
28 continue;
29
30 if (!parse_message_(service_data.data, *res))
31 continue;
32
33 if (!report_results_(res, addr_str))
34 continue;
35
36 if (res->weight.has_value() && this->weight_ != nullptr)
37 this->weight_->publish_state(*res->weight);
38
39 if (this->impedance_ != nullptr) {
40 if (res->version == 1) {
41 ESP_LOGW(TAG, "Impedance is only supported on version 2. Your scale was identified as version 1.");
42 } else {
43 if (res->impedance.has_value()) {
44 this->impedance_->publish_state(*res->impedance);
45 } else {
47 this->impedance_->publish_state(NAN);
48 }
49 }
50 }
51 success = true;
52 }
53
54 return success;
55}
56
57optional<ParseResult> XiaomiMiscale::parse_header_(const ble_device_base::ServiceData &service_data) {
58 ParseResult result;
59 if (service_data.uuid == ble_device_base::ESPBTUUID::from_uint16(0x181D) && service_data.data.size() == 10) {
60 result.version = 1;
61 } else if (service_data.uuid == ble_device_base::ESPBTUUID::from_uint16(0x181B) && service_data.data.size() == 13) {
62 result.version = 2;
63 } else {
64 char uuid_buf[ble_device_base::UUID_STR_LEN];
65 ESP_LOGVV(TAG,
66 "parse_header(): Couldn't identify scale version or data size was not correct. UUID: %s, data_size: %d",
67 service_data.uuid.to_str(uuid_buf), service_data.data.size());
68 return {};
69 }
70
71 return result;
72}
73
74bool XiaomiMiscale::parse_message_(const std::vector<uint8_t> &message, ParseResult &result) {
75 if (result.version == 1) {
76 return parse_message_v1_(message, result);
77 } else {
78 return parse_message_v2_(message, result);
79 }
80}
81
82bool XiaomiMiscale::parse_message_v1_(const std::vector<uint8_t> &message, ParseResult &result) {
83 // message size is checked in parse_header
84 // 1-2 Weight (MISCALE 181D)
85 // 3-4 Years (MISCALE 181D)
86 // 5 month (MISCALE 181D)
87 // 6 day (MISCALE 181D)
88 // 7 hour (MISCALE 181D)
89 // 8 minute (MISCALE 181D)
90 // 9 second (MISCALE 181D)
91
92 const uint8_t *data = message.data();
93
94 // weight, 2 bytes, 16-bit unsigned integer, 1 kg
95 const int16_t weight = uint16_t(data[1]) | (uint16_t(data[2]) << 8);
96 if (data[0] == 0x22 || data[0] == 0xa2) {
97 result.weight = weight * 0.01f / 2.0f; // unit 'kg'
98 } else if (data[0] == 0x12 || data[0] == 0xb2) {
99 result.weight = weight * 0.01f * 0.6f; // unit 'jin'
100 } else if (data[0] == 0x03 || data[0] == 0xb3) {
101 result.weight = weight * 0.01f * 0.453592f; // unit 'lbs'
102 }
103
104 return true;
105}
106
107bool XiaomiMiscale::parse_message_v2_(const std::vector<uint8_t> &message, ParseResult &result) {
108 // message size is checked in parse_header
109 // 2-3 Years (MISCALE 2 181B)
110 // 4 month (MISCALE 2 181B)
111 // 5 day (MISCALE 2 181B)
112 // 6 hour (MISCALE 2 181B)
113 // 7 minute (MISCALE 2 181B)
114 // 8 second (MISCALE 2 181B)
115 // 9-10 impedance (MISCALE 2 181B)
116 // 11-12 weight (MISCALE 2 181B)
117
118 const uint8_t *data = message.data();
119
120 bool has_impedance = ((data[1] & (1 << 1)) != 0);
121 bool is_stabilized = ((data[1] & (1 << 5)) != 0);
122 bool load_removed = ((data[1] & (1 << 7)) != 0);
123
124 if (!is_stabilized || load_removed) {
125 return false;
126 }
127
128 // weight, 2 bytes, 16-bit unsigned integer, 1 kg
129 const int16_t weight = uint16_t(data[11]) | (uint16_t(data[12]) << 8);
130 if (data[0] == 0x02) {
131 result.weight = weight * 0.01f / 2.0f; // unit 'kg'
132 } else if (data[0] == 0x03) {
133 result.weight = weight * 0.01f * 0.453592f; // unit 'lbs'
134 }
135
136 if (has_impedance) {
137 // impedance, 2 bytes, 16-bit
138 const int16_t impedance = uint16_t(data[9]) | (uint16_t(data[10]) << 8);
139 result.impedance = impedance;
140
141 if (impedance == 0 || impedance >= 3000) {
142 return false;
143 }
144 }
145
146 return true;
147}
148
149bool XiaomiMiscale::report_results_(const optional<ParseResult> &result, const char *address) {
150 if (!result.has_value()) {
151 ESP_LOGVV(TAG, "report_results(): no results available.");
152 return false;
153 }
154
155 ESP_LOGD(TAG, "Got Xiaomi Miscale v%d (%s):", result->version, address);
156
157 if (result->weight.has_value()) {
158 ESP_LOGD(TAG, " Weight: %.2fkg", *result->weight);
159 }
160 if (result->impedance.has_value()) {
161 ESP_LOGD(TAG, " Impedance: %.0fohm", *result->impedance);
162 }
163
164 return true;
165}
166
167} // namespace esphome::xiaomi_miscale
uint8_t address
Definition bl0906.h:4
ESPDEPRECATED("Use address_str_to() instead. Removed in 2027.2.0.", "2026.8.0") std const char * address_str_to(char *buf) const
Return MAC as "XX:XX:XX:XX:XX:XX" string.
uint64_t address_uint64() const
Return MAC as packed uint64 (byte 0 in LSB — matches esp32's address_uint64).
const std::vector< ServiceData > & get_service_datas() const
Definition ble_device.h:225
static ESPBTUUID from_uint16(uint16_t uuid)
const char * to_str(char *buf) const
Write "0xABCD" / "0xABCDEF01" / the dashed 128-bit form, or "None" for an unset UUID,...
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:68
bool parse_message_v1_(const std::vector< uint8_t > &message, ParseResult &result)
bool parse_message_(const std::vector< uint8_t > &message, ParseResult &result)
optional< ParseResult > parse_header_(const ble_device_base::ServiceData &service_data)
bool parse_device(const ble_device_base::ESPBTDevice &device) override
bool report_results_(const optional< ParseResult > &result, const char *address)
bool parse_message_v2_(const std::vector< uint8_t > &message, ParseResult &result)
const LogString * message
Definition component.cpp:35