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