12static constexpr uint16_t PRODUCT_ID = 0x098b;
15static constexpr uint8_t FC_ENCRYPTED = 0x08;
16static constexpr uint8_t FC_MAC_INCLUDED = 0x10;
17static constexpr uint8_t FC_CAPABILITY = 0x20;
18static constexpr uint8_t FC_OBJECT = 0x40;
21static constexpr size_t MIC_SIZE = 4;
22static constexpr size_t ENCRYPTED_TRAILER = 3 + MIC_SIZE;
24static constexpr size_t MAX_PAYLOAD_SIZE = 16;
28static constexpr uint16_t OBJ_LIGHT = 0x0018;
29static constexpr uint16_t OBJ_LIGHT_ALT = 0x1018;
30static constexpr uint16_t OBJ_DOOR = 0x0019;
31static constexpr uint16_t OBJ_DOOR_ALT = 0x1019;
32static constexpr uint16_t OBJ_BATTERY = 0x100A;
33static constexpr uint16_t OBJ_BATTERY_ALT = 0x4803;
36 uint8_t mac[MAC_ADDRESS_SIZE];
38 char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
43 LOG_BINARY_SENSOR(
" ",
"Opening",
this);
44 LOG_BINARY_SENSOR(
" ",
"Light", this->
light_);
56 if (!service_data.uuid.contains(0x95, 0xFE))
63 ESP_LOGD(TAG,
"%s: open=%s light=%s battery=%s", this->
get_name().c_str(),
64 reading.
open.has_value() ? (*reading.
open ? LOG_STR_LITERAL(
"yes") : LOG_STR_LITERAL(
"no"))
65 : LOG_STR_LITERAL(
"-"),
66 reading.
light.has_value() ? (*reading.
light ? LOG_STR_LITERAL(
"yes") : LOG_STR_LITERAL(
"no"))
67 : LOG_STR_LITERAL(
"-"),
68 reading.
battery_level.has_value() ? LOG_STR_LITERAL(
"updated") : LOG_STR_LITERAL(
"-"));
70 if (reading.
open.has_value())
72 if (reading.
light.has_value() && this->light_ !=
nullptr)
74 if (reading.
battery_level.has_value() && this->battery_level_ !=
nullptr)
82 uint8_t nonce[MAC_ADDRESS_SIZE + 6];
83 for (
size_t i = 0; i < MAC_ADDRESS_SIZE; i++)
84 nonce[i] =
static_cast<uint8_t
>(this->
address_ >> (i * 8));
85 memcpy(nonce + MAC_ADDRESS_SIZE, frame + 2, 3);
86 memcpy(nonce + MAC_ADDRESS_SIZE + 3, frame +
size - ENCRYPTED_TRAILER, 3);
87 static constexpr uint8_t AUTH_DATA[1] = {0x11};
89 frame + offset,
size - offset - ENCRYPTED_TRAILER, plaintext,
90 frame +
size - MIC_SIZE, MIC_SIZE);
94 const size_t size = data.size();
97 const uint8_t *
raw = data.data();
99 const uint8_t fc =
raw[0];
100 if (!(fc & FC_OBJECT))
103 ESP_LOGVV(TAG,
"Not an MCCGQ02HL frame (product id %02X%02X).",
raw[3],
raw[2]);
107 const uint8_t frame_count =
raw[4];
108 if (this->
last_frame_count_.has_value() && *this->last_frame_count_ == frame_count) {
109 ESP_LOGVV(TAG,
"Duplicate frame %u.", frame_count);
114 if (!(fc & FC_ENCRYPTED)) {
115 ESP_LOGVV(TAG,
"Ignoring unencrypted frame %u.", frame_count);
119 const size_t offset = 5 + ((fc & FC_MAC_INCLUDED) ? 6 : 0) + ((fc & FC_CAPABILITY) ? 1 : 0);
121 ESP_LOGW(TAG,
"Unsupported encrypted frame layout (fc=0x%02X, %u bytes).", fc, (
unsigned)
size);
124 uint8_t plaintext[MAX_PAYLOAD_SIZE];
126 ESP_LOGW(TAG,
"Decryption failed (%u-byte frame) -- check the bindkey.", (
unsigned)
size);
141 const uint8_t
len = payload[2];
144 const uint8_t *value = payload + 3;
146 if ((
id == OBJ_DOOR ||
id == OBJ_DOOR_ALT) &&
len == 1) {
149 reading.
open = value[0] != 1;
152 }
else if ((
id == OBJ_LIGHT ||
id == OBJ_LIGHT_ALT) &&
len == 1) {
153 reading.
light = value[0] != 0;
155 }
else if ((
id == OBJ_BATTERY ||
id == OBJ_BATTERY_ALT) &&
len == 1) {
159 ESP_LOGVV(TAG,
"Ignoring object 0x%04X (%u bytes).",
id,
len);
const StringRef & get_name() const
void publish_state(bool new_state)
Publish a new state to the front-end.
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
void publish_state(float state)
Publish a new state to the front-end.
bool parse_device(const ble_device_base::ESPBTDevice &device) override
bool decrypt_(const uint8_t *frame, size_t size, size_t offset, uint8_t *plaintext) const
optional< uint8_t > last_frame_count_
binary_sensor::BinarySensor * light_
void dump_config() override
bool parse_objects_(const uint8_t *payload, size_t length, Reading &reading)
sensor::Sensor * battery_level_
bool parse_service_data_(const std::vector< uint8_t > &data, Reading &reading)
void set_bindkey(const char *bindkey)
void uint64_to_mac_msb_first(uint64_t address, uint8_t out[6])
Unpack a uint64 BLE address into printable (MSB-first) byte order — the order bd_addr_t / esp_bd_addr...
bool aes_ccm_auth_decrypt(const uint8_t key[16], const uint8_t *nonce, size_t nonce_len, const uint8_t *aad, size_t aad_len, const uint8_t *ciphertext, size_t ct_len, uint8_t *plaintext, const uint8_t *tag, size_t tag_len)
ESPHOME_LOG_TAG(TAG, "xiaomi_mccgq02hl")
size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count)
Parse bytes from a hex-encoded string into a byte array.
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
char * format_mac_addr_upper(const uint8_t *mac, char *output)
Format MAC address as XX:XX:XX:XX:XX:XX (uppercase, colon separators)
optional< float > battery_level