ESPHome  2022.5.1
xiaomi_miscale.cpp
Go to the documentation of this file.
1 #include "xiaomi_miscale.h"
2 #include "esphome/core/log.h"
3 
4 #ifdef USE_ESP32
5 
6 namespace esphome {
7 namespace xiaomi_miscale {
8 
9 static const char *const TAG = "xiaomi_miscale";
10 
12  ESP_LOGCONFIG(TAG, "Xiaomi Miscale");
13  LOG_SENSOR(" ", "Weight", this->weight_);
14  LOG_SENSOR(" ", "Impedance", this->impedance_);
15 }
16 
18  if (device.address_uint64() != this->address_) {
19  ESP_LOGVV(TAG, "parse_device(): unknown MAC address.");
20  return false;
21  }
22  ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_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, device.address_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 verison 1.");
42  } else {
43  if (res->impedance.has_value()) {
44  this->impedance_->publish_state(*res->impedance);
45  } else {
46  if (clear_impedance_)
47  this->impedance_->publish_state(NAN);
48  }
49  }
50  }
51  success = true;
52  }
53 
54  return success;
55 }
56 
58  ParseResult result;
59  if (service_data.uuid == esp32_ble_tracker::ESPBTUUID::from_uint16(0x181D) && service_data.data.size() == 10) {
60  result.version = 1;
61  } else if (service_data.uuid == esp32_ble_tracker::ESPBTUUID::from_uint16(0x181B) && service_data.data.size() == 13) {
62  result.version = 2;
63  } else {
64  ESP_LOGVV(TAG,
65  "parse_header(): Couldn't identify scale version or data size was not correct. UUID: %s, data_size: %d",
66  service_data.uuid.to_string().c_str(), service_data.data.size());
67  return {};
68  }
69 
70  return result;
71 }
72 
73 bool XiaomiMiscale::parse_message_(const std::vector<uint8_t> &message, ParseResult &result) {
74  if (result.version == 1) {
75  return parse_message_v1_(message, result);
76  } else {
77  return parse_message_v2_(message, result);
78  }
79 }
80 
81 bool XiaomiMiscale::parse_message_v1_(const std::vector<uint8_t> &message, ParseResult &result) {
82  // message size is checked in parse_header
83  // 1-2 Weight (MISCALE 181D)
84  // 3-4 Years (MISCALE 181D)
85  // 5 month (MISCALE 181D)
86  // 6 day (MISCALE 181D)
87  // 7 hour (MISCALE 181D)
88  // 8 minute (MISCALE 181D)
89  // 9 second (MISCALE 181D)
90 
91  const uint8_t *data = message.data();
92 
93  // weight, 2 bytes, 16-bit unsigned integer, 1 kg
94  const int16_t weight = uint16_t(data[1]) | (uint16_t(data[2]) << 8);
95  if (data[0] == 0x22 || data[0] == 0xa2) {
96  result.weight = weight * 0.01f / 2.0f; // unit 'kg'
97  } else if (data[0] == 0x12 || data[0] == 0xb2) {
98  result.weight = weight * 0.01f * 0.6f; // unit 'jin'
99  } else if (data[0] == 0x03 || data[0] == 0xb3) {
100  result.weight = weight * 0.01f * 0.453592f; // unit 'lbs'
101  }
102 
103  return true;
104 }
105 
106 bool XiaomiMiscale::parse_message_v2_(const std::vector<uint8_t> &message, ParseResult &result) {
107  // message size is checked in parse_header
108  // 2-3 Years (MISCALE 2 181B)
109  // 4 month (MISCALE 2 181B)
110  // 5 day (MISCALE 2 181B)
111  // 6 hour (MISCALE 2 181B)
112  // 7 minute (MISCALE 2 181B)
113  // 8 second (MISCALE 2 181B)
114  // 9-10 impedance (MISCALE 2 181B)
115  // 11-12 weight (MISCALE 2 181B)
116 
117  const uint8_t *data = message.data();
118 
119  bool has_impedance = ((data[1] & (1 << 1)) != 0);
120  bool is_stabilized = ((data[1] & (1 << 5)) != 0);
121  bool load_removed = ((data[1] & (1 << 7)) != 0);
122 
123  if (!is_stabilized || load_removed) {
124  return false;
125  }
126 
127  // weight, 2 bytes, 16-bit unsigned integer, 1 kg
128  const int16_t weight = uint16_t(data[11]) | (uint16_t(data[12]) << 8);
129  if (data[0] == 0x02) {
130  result.weight = weight * 0.01f / 2.0f; // unit 'kg'
131  } else if (data[0] == 0x03) {
132  result.weight = weight * 0.01f * 0.453592f; // unit 'lbs'
133  }
134 
135  if (has_impedance) {
136  // impedance, 2 bytes, 16-bit
137  const int16_t impedance = uint16_t(data[9]) | (uint16_t(data[10]) << 8);
138  result.impedance = impedance;
139 
140  if (impedance == 0 || impedance >= 3000) {
141  return false;
142  }
143  }
144 
145  return true;
146 }
147 
148 bool XiaomiMiscale::report_results_(const optional<ParseResult> &result, const std::string &address) {
149  if (!result.has_value()) {
150  ESP_LOGVV(TAG, "report_results(): no results available.");
151  return false;
152  }
153 
154  ESP_LOGD(TAG, "Got Xiaomi Miscale v%d (%s):", result->version, address.c_str());
155 
156  if (result->weight.has_value()) {
157  ESP_LOGD(TAG, " Weight: %.2fkg", *result->weight);
158  }
159  if (result->impedance.has_value()) {
160  ESP_LOGD(TAG, " Impedance: %.0fohm", *result->impedance);
161  }
162 
163  return true;
164 }
165 
166 } // namespace xiaomi_miscale
167 } // namespace esphome
168 
169 #endif
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override
optional< ParseResult > parse_header_(const esp32_ble_tracker::ServiceData &service_data)
bool has_value() const
Definition: optional.h:87
bool parse_message_v2_(const std::vector< uint8_t > &message, ParseResult &result)
bool parse_message_(const std::vector< uint8_t > &message, ParseResult &result)
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:70
const std::vector< ServiceData > & get_service_datas() const
bool parse_message_v1_(const std::vector< uint8_t > &message, ParseResult &result)
bool report_results_(const optional< ParseResult > &result, const std::string &address)
static ESPBTUUID from_uint16(uint16_t uuid)
Definition: a4988.cpp:4