Last active
March 6, 2026 15:22
-
-
Save patrick3399/df2307d453ba17b9f0ad96a60e8f1962 to your computer and use it in GitHub Desktop.
ESPHome MiBeacon v5 AES-CCM Remote Decoder
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| substitutions: | |
| devicename: "mi decode" | |
| upper_devicename: "Mi Decode" | |
| comment: "" | |
| bindkey: "fa0fd744af52860c2ac77a61a29ada89" | |
| key_press_delay: "2s" | |
| esp32_ble_tracker: | |
| on_ble_advertise: | |
| - mac_address: AA:BB:CC:DD:EE:DD | |
| then: | |
| - lambda: |- | |
| // MiBeacon v5 AES-CCM Remote Decoder | |
| // Service UUID: 0xFE95 | |
| // Product ID: 0x3F3A (8Key) giot.remote.v58kwm | |
| // Product ID: 0x3F36 (4Key) giot.remote.v54kwm | |
| static const char* BINDKEY = "${bindkey}"; | |
| static uint8_t last_counter = 0; | |
| for (auto &svc : x.get_service_datas()) { | |
| if (!svc.uuid.contains(0x95, 0xFE)) continue; | |
| auto& data = svc.data; | |
| // Check: 22 bytes, encrypted (bit 3), product ID 0x3F3A | |
| if (data.size() != 22 || !(data[0] & 0x08) || | |
| (data[2] | (data[3] << 8)) != 0x3F3A) continue; | |
| // Deduplicate by frame counter | |
| if (data[4] == last_counter) continue; | |
| last_counter = data[4]; | |
| // Build nonce: MAC[5-10] + PID[2-3] + Counter[4] + Payload[15-17] | |
| uint8_t nonce[12]; | |
| memcpy(nonce, &data[5], 6); | |
| memcpy(nonce + 6, &data[2], 3); | |
| memcpy(nonce + 9, &data[15], 3); | |
| // Convert bindkey hex to bytes | |
| uint8_t key[16]; | |
| for (int i = 0; i < 16; i++) { | |
| sscanf(BINDKEY + 2*i, "%2hhx", &key[i]); | |
| } | |
| // Decrypt with AES-CCM | |
| mbedtls_ccm_context ctx; | |
| mbedtls_ccm_init(&ctx); | |
| mbedtls_ccm_setkey(&ctx, MBEDTLS_CIPHER_ID_AES, key, 128); | |
| uint8_t plaintext[4]; | |
| const uint8_t aad[] = {0x11}; | |
| int ret = mbedtls_ccm_auth_decrypt(&ctx, 4, nonce, 12, aad, 1, | |
| &data[11], plaintext, | |
| &data[18], 4); | |
| mbedtls_ccm_free(&ctx); | |
| if (ret == 0 && plaintext[0] == 0x0C && plaintext[1] == 0x4A) { | |
| int btn = plaintext[3]; | |
| ESP_LOGI("v58kwm", "Button %d pressed", btn); | |
| // Trigger corresponding sensor | |
| switch(btn) { | |
| case 1: id(remote_key_1).publish_state(true); break; | |
| case 2: id(remote_key_2).publish_state(true); break; | |
| case 3: id(remote_key_3).publish_state(true); break; | |
| case 4: id(remote_key_4).publish_state(true); break; | |
| case 5: id(remote_key_5).publish_state(true); break; | |
| case 6: id(remote_key_6).publish_state(true); break; | |
| case 7: id(remote_key_7).publish_state(true); break; | |
| case 8: id(remote_key_8).publish_state(true); break; | |
| } | |
| } | |
| } | |
| esp32: | |
| board: esp32-s3-devkitc-1 | |
| framework: | |
| type: esp-idf | |
| sdkconfig_options: | |
| CONFIG_MBEDTLS_CCM_C: y | |
| esphome: | |
| name: $devicename | |
| friendly_name: $upper_devicename | |
| comment: $comment | |
| platformio_options: | |
| build_src_flags: | |
| - "-include mbedtls/ccm.h" | |
| - "-include mbedtls/cipher.h" | |
| on_boot: | |
| - priority: -100 | |
| then: | |
| - binary_sensor.template.publish: | |
| id: remote_key_1 | |
| state: false | |
| - binary_sensor.template.publish: | |
| id: remote_key_2 | |
| state: false | |
| - binary_sensor.template.publish: | |
| id: remote_key_3 | |
| state: false | |
| - binary_sensor.template.publish: | |
| id: remote_key_4 | |
| state: false | |
| - binary_sensor.template.publish: | |
| id: remote_key_5 | |
| state: false | |
| - binary_sensor.template.publish: | |
| id: remote_key_6 | |
| state: false | |
| - binary_sensor.template.publish: | |
| id: remote_key_7 | |
| state: false | |
| - binary_sensor.template.publish: | |
| id: remote_key_8 | |
| state: false | |
| binary_sensor: | |
| - platform: template | |
| name: "Button 1" | |
| id: remote_key_1 | |
| on_press: | |
| then: | |
| - delay: ${key_press_delay} | |
| - binary_sensor.template.publish: | |
| id: remote_key_1 | |
| state: false | |
| - platform: template | |
| name: "Button 2" | |
| id: remote_key_2 | |
| on_press: | |
| then: | |
| - delay: ${key_press_delay} | |
| - binary_sensor.template.publish: | |
| id: remote_key_2 | |
| state: false | |
| - platform: template | |
| name: "Button 3" | |
| id: remote_key_3 | |
| on_press: | |
| then: | |
| - delay: ${key_press_delay} | |
| - binary_sensor.template.publish: | |
| id: remote_key_3 | |
| state: false | |
| - platform: template | |
| name: "Button 4" | |
| id: remote_key_4 | |
| on_press: | |
| then: | |
| - delay: ${key_press_delay} | |
| - binary_sensor.template.publish: | |
| id: remote_key_4 | |
| state: false | |
| - platform: template | |
| name: "Button 5" | |
| id: remote_key_5 | |
| on_press: | |
| then: | |
| - delay: ${key_press_delay} | |
| - binary_sensor.template.publish: | |
| id: remote_key_5 | |
| state: false | |
| - platform: template | |
| name: "Button 6" | |
| id: remote_key_6 | |
| on_press: | |
| then: | |
| - delay: ${key_press_delay} | |
| - binary_sensor.template.publish: | |
| id: remote_key_6 | |
| state: false | |
| - platform: template | |
| name: "Button 7" | |
| id: remote_key_7 | |
| on_press: | |
| then: | |
| - delay: ${key_press_delay} | |
| - binary_sensor.template.publish: | |
| id: remote_key_7 | |
| state: false | |
| - platform: template | |
| name: "Button 8" | |
| id: remote_key_8 | |
| on_press: | |
| then: | |
| - delay: ${key_press_delay} | |
| - binary_sensor.template.publish: | |
| id: remote_key_8 | |
| state: false |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment