diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..e0f5ddc --- /dev/null +++ b/.clang-format @@ -0,0 +1,137 @@ +Language: Cpp +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: DontAlign +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 120 +CommentPragmas: "^ IWYU pragma:" +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: true +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^' + Priority: 2 + - Regex: '^<.*\.h>' + Priority: 1 + - Regex: "^<.*" + Priority: 2 + - Regex: ".*" + Priority: 3 +IncludeIsMainRegex: "([-_](test|unittest))?$" +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 2 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: "" +MacroBlockEnd: "" +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 2000 +PointerAlignment: Right +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - "c++" + - "C++" + CanonicalDelimiter: "" + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + CanonicalDelimiter: "" + BasedOnStyle: google +ReflowComments: true +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: true +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +TabWidth: 2 +UseTab: Never diff --git a/components/xiaomi_ble/xiaomi_ble.cpp b/components/xiaomi_ble/xiaomi_ble.cpp index 417c3ce..4131748 100644 --- a/components/xiaomi_ble/xiaomi_ble.cpp +++ b/components/xiaomi_ble/xiaomi_ble.cpp @@ -214,6 +214,9 @@ optional parse_xiaomi_header(const esp32_ble_tracker::Service } else if ((raw[2] == 0x53) && (raw[3] == 0x01)) { // Yeelight Remote Control YLYK01YL result.type = XiaomiParseResult::TYPE_YLYK01YL; result.name = "YLYK01YL"; + } else if ((raw[2] == 0xB6) && (raw[3] == 0x03)) { // Yeelight Wireless Smart Dimmer YLKG07YL/YLKG08YL + result.type = XiaomiParseResult::TYPE_YLKG07YL; + result.name = "YLKG07YL"; } else { ESP_LOGVV(TAG, "parse_xiaomi_header(): unknown device, no magic bytes."); return {}; @@ -222,6 +225,7 @@ optional parse_xiaomi_header(const esp32_ble_tracker::Service return result; } +// Decrypt MiBeacon V4/V5 payload bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, const uint64_t &address) { if (!((raw.size() == 19) || ((raw.size() >= 22) && (raw.size() <= 24)))) { ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): data packet has wrong size (%d)!", raw.size()); diff --git a/components/xiaomi_ble/xiaomi_ble.h b/components/xiaomi_ble/xiaomi_ble.h index 77edf73..cec1f7a 100644 --- a/components/xiaomi_ble/xiaomi_ble.h +++ b/components/xiaomi_ble/xiaomi_ble.h @@ -25,7 +25,8 @@ struct XiaomiParseResult { TYPE_MJYD02YLA, TYPE_MHOC401, TYPE_CGPR1, - TYPE_YLYK01YL + TYPE_YLYK01YL, + TYPE_YLKG07YL, } type; std::string name; optional keycode; diff --git a/components/xiaomi_ylkg07yl/__init__.py b/components/xiaomi_ylkg07yl/__init__.py new file mode 100644 index 0000000..00aa935 --- /dev/null +++ b/components/xiaomi_ylkg07yl/__init__.py @@ -0,0 +1,141 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, esp32_ble_tracker +from esphome import automation +from esphome.const import ( + CONF_MAC_ADDRESS, + CONF_BINDKEY, + UNIT_EMPTY, + ICON_EMPTY, + DEVICE_CLASS_EMPTY, + CONF_ID, + CONF_TRIGGER_ID, +) + +AUTO_LOAD = ["xiaomi_ble", "sensor"] +CODEOWNERS = ["@syssi"] +DEPENDENCIES = ["esp32_ble_tracker"] +MULTI_CONF = True + +CONF_LAST_BUTTON_PRESSED = "last_button_pressed" +CONF_ON_BUTTON_ON = "on_button_on" +CONF_ON_BUTTON_OFF = "on_button_off" +CONF_ON_BUTTON_SUN = "on_button_sun" +CONF_ON_BUTTON_M = "on_button_m" +CONF_ON_BUTTON_PLUS = "on_button_plus" +CONF_ON_BUTTON_MINUS = "on_button_minus" + +ON_PRESS_ACTIONS = [ + CONF_ON_BUTTON_ON, + CONF_ON_BUTTON_OFF, + CONF_ON_BUTTON_SUN, + CONF_ON_BUTTON_M, + CONF_ON_BUTTON_PLUS, + CONF_ON_BUTTON_MINUS, +] + +xiaomi_ylkg07yl_ns = cg.esphome_ns.namespace("xiaomi_ylkg07yl") +XiaomiYLKG07YL = xiaomi_ylkg07yl_ns.class_( + "XiaomiYLKG07YL", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +) + +OnButtonOnTrigger = xiaomi_ylkg07yl_ns.class_( + "OnButtonOnTrigger", automation.Trigger.template() +) +OnButtonOffTrigger = xiaomi_ylkg07yl_ns.class_( + "OnButtonOffTrigger", automation.Trigger.template() +) +OnButtonSunTrigger = xiaomi_ylkg07yl_ns.class_( + "OnButtonSunTrigger", automation.Trigger.template() +) +OnButtonMTrigger = xiaomi_ylkg07yl_ns.class_( + "OnButtonMTrigger", automation.Trigger.template() +) +OnButtonPlusTrigger = xiaomi_ylkg07yl_ns.class_( + "OnButtonPlusTrigger", automation.Trigger.template() +) +OnButtonMinusTrigger = xiaomi_ylkg07yl_ns.class_( + "OnButtonMinusTrigger", automation.Trigger.template() +) + + +def validate_short_bind_key(value): + value = cv.string_strict(value) + parts = [value[i : i + 2] for i in range(0, len(value), 2)] + if len(parts) != 12: + raise Invalid("Bind key must consist of 12 hexadecimal numbers") + parts_int = [] + if any(len(part) != 2 for part in parts): + raise Invalid("Bind key must be format XX") + for part in parts: + try: + parts_int.append(int(part, 16)) + except ValueError: + # pylint: disable=raise-missing-from + raise Invalid("Bind key must be hex values from 00 to FF") + + return "".join(f"{part:02X}" for part in parts_int) + + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(XiaomiYLKG07YL), + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Required(CONF_BINDKEY): validate_short_bind_key, + cv.Optional(CONF_LAST_BUTTON_PRESSED): sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_ON_BUTTON_ON): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OnButtonOnTrigger), + } + ), + cv.Optional(CONF_ON_BUTTON_OFF): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OnButtonOffTrigger), + } + ), + cv.Optional(CONF_ON_BUTTON_SUN): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OnButtonSunTrigger), + } + ), + cv.Optional(CONF_ON_BUTTON_M): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OnButtonMTrigger), + } + ), + cv.Optional(CONF_ON_BUTTON_PLUS): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OnButtonPlusTrigger), + } + ), + cv.Optional(CONF_ON_BUTTON_MINUS): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OnButtonMinusTrigger), + } + ), + } + ) + .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await esp32_ble_tracker.register_ble_device(var, config) + + cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) + cg.add(var.set_bindkey(config[CONF_BINDKEY])) + + if CONF_LAST_BUTTON_PRESSED in config: + sens = await sensor.new_sensor(config[CONF_LAST_BUTTON_PRESSED]) + cg.add(var.set_keycode(sens)) + + for action in ON_PRESS_ACTIONS: + for conf in config.get(action, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) diff --git a/components/xiaomi_ylkg07yl/xiaomi_ylkg07yl.cpp b/components/xiaomi_ylkg07yl/xiaomi_ylkg07yl.cpp new file mode 100644 index 0000000..1095c4b --- /dev/null +++ b/components/xiaomi_ylkg07yl/xiaomi_ylkg07yl.cpp @@ -0,0 +1,201 @@ +#include "xiaomi_ylkg07yl.h" +#include "esphome/core/log.h" + +#include "mbedtls/ccm.h" +#include + +#ifdef USE_ESP32 + +namespace esphome { +namespace xiaomi_ylkg07yl { + +static const char *const TAG = "xiaomi_ylkg07yl"; + +void XiaomiYLKG07YL::dump_config() { + ESP_LOGCONFIG(TAG, "Xiaomi YLKG07YL"); + ESP_LOGCONFIG(TAG, " Bindkey: %s", hexencode(this->bindkey_, 12).c_str()); + LOG_SENSOR(" ", "Keycode", this->keycode_); +} + +bool XiaomiYLKG07YL::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { + if (device.address_uint64() != this->address_) { + ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); + return false; + } + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + + bool success = false; + for (auto &service_data : device.get_service_datas()) { + auto res = xiaomi_ble::parse_xiaomi_header(service_data); + if (!res.has_value()) { + continue; + } + if (res->is_duplicate) { + continue; + } + if (res->has_encryption && + (!(this->decrypt_legacy_xiaomi_payload(const_cast &>(service_data.data), this->bindkey_, + this->address_)))) { + continue; + } + if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { + continue; + } + if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + continue; + } + if (res->keycode.has_value()) { + if (this->keycode_ != nullptr) + this->keycode_->publish_state(*res->keycode); + + this->receive_callback_.call(*res->keycode); + } + success = true; + } + + return success; +} + +void XiaomiYLKG07YL::set_bindkey(const std::string &bindkey) { + memset(bindkey_, 0, 12); + if (bindkey.size() != 24) { + return; + } + char temp[3] = {0}; + for (int i = 0; i < 12; i++) { + strncpy(temp, &(bindkey.c_str()[i * 2]), 2); + bindkey_[i] = std::strtoul(temp, nullptr, 16); + } +} + +void XiaomiYLKG07YL::add_on_receive_callback(std::function &&callback) { + this->receive_callback_.add(std::move(callback)); +} + +// Decrypt MiBeacon V2/V3 payload +bool XiaomiYLKG07YL::decrypt_legacy_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, + const uint64_t &address) { + if (raw.size() != 21) { + ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): data packet has wrong size (%d)!", raw.size()); + ESP_LOGVV(TAG, " Packet : %s", hexencode(raw.data(), raw.size()).c_str()); + return false; + } + + uint8_t mac_reverse[6] = {0}; + mac_reverse[5] = (uint8_t)(address >> 40); + mac_reverse[4] = (uint8_t)(address >> 32); + mac_reverse[3] = (uint8_t)(address >> 24); + mac_reverse[2] = (uint8_t)(address >> 16); + mac_reverse[1] = (uint8_t)(address >> 8); + mac_reverse[0] = (uint8_t)(address >> 0); + + xiaomi_ble::XiaomiAESVector vector{.key = {0}, + .plaintext = {0}, + .ciphertext = {0}, + .authdata = {0x11}, + .iv = {0}, + .tag = {0}, + .keysize = 16, + .authsize = 1, + .datasize = 0, + .tagsize = 4, + .ivsize = 13}; + + vector.datasize = raw.size() - 15; + int cipher_pos = 11; + + const uint8_t *v = raw.data(); + + // key format is: bindkey[:6] "8d3d3c97" bindkey[6:] + uint8_t key[16] = {0, 0, 0, 0, 0, 0, 0x8d, 0x3d, 0x3c, 0x97, 0, 0, 0, 0, 0, 0}; + memcpy(key + 0x0, bindkey + 0, 6); + memcpy(key + 0xA, bindkey + 6, 6); + + memcpy(vector.key, key, vector.keysize); + memcpy(vector.ciphertext, v + cipher_pos, vector.datasize); + + // 58.30.B6.03.01.BC.7B.C4.41.24.F8.5A.B8.4A.60.93.B8.01.00.00.21 (21) + // ^^^^^^^^^^^^^^^^^^^^^^^^^^ + // + // 58.30. Frame control + // B6.03. Device type + // 01. Frame count + // BC.7B.C4.41.24.F8. MAC address + // 5A.B8.4A.60.93.B8. Cipher + // 01.00.00. Ext. count + // 21 + + // nonce = b"".join( + // [ + // self.framectrl_data, + // self.device_type, + // payload_counter, + // self.xiaomi_mac_reversed[:-1] + // ] + // ) + + // 58.30.B6.03.01.BC.7B.C4.41.24.F8.5A.B8.4A.60.93.B8.01.00.00.21 (21) + // ^^^^^^^^^^^^^^ + memcpy(vector.iv, v, 5); // frame control (2 bytes) + device type (2 bytes) + + // frame count (1 byte) + + // 58.30.B6.03.01.BC.7B.C4.41.24.F8.5A.B8.4A.60.93.B8.01.00.00.21 (21) + // ^^^^^^^^ + memcpy(vector.iv + 5, v + raw.size() - 4, 3); // ext. count + + // 58.30.B6.03.01.BC.7B.C4.41.24.F8.5A.B8.4A.60.93.B8.01.00.00.21 (21) + // ^^^^^^^^^^^^^^ + memcpy(vector.iv + 8, mac_reverse, 5); // 5 bytes of the reversed MAC address + + mbedtls_ccm_context ctx; + mbedtls_ccm_init(&ctx); + + int ret = mbedtls_ccm_setkey(&ctx, MBEDTLS_CIPHER_ID_AES, vector.key, vector.keysize * 8); + if (ret) { + ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): mbedtls_ccm_setkey() failed."); + mbedtls_ccm_free(&ctx); + return false; + } + + ret = mbedtls_ccm_star_auth_decrypt(&ctx, vector.datasize, vector.iv, vector.ivsize, vector.authdata, vector.authsize, + vector.ciphertext, vector.plaintext, nullptr, 0); + if (ret) { + uint8_t mac_address[6] = {0}; + memcpy(mac_address, mac_reverse + 5, 1); + memcpy(mac_address + 1, mac_reverse + 4, 1); + memcpy(mac_address + 2, mac_reverse + 3, 1); + memcpy(mac_address + 3, mac_reverse + 2, 1); + memcpy(mac_address + 4, mac_reverse + 1, 1); + memcpy(mac_address + 5, mac_reverse, 1); + ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): mbedtls_ccm_star_auth_decrypt failed."); + ESP_LOGVV(TAG, " MAC address : %s", hexencode(mac_address, 6).c_str()); + ESP_LOGVV(TAG, " Packet : %s", hexencode(raw.data(), raw.size()).c_str()); + ESP_LOGVV(TAG, " Key : %s", hexencode(vector.key, vector.keysize).c_str()); + ESP_LOGVV(TAG, " Iv : %s", hexencode(vector.iv, vector.ivsize).c_str()); + ESP_LOGVV(TAG, " Cipher : %s", hexencode(vector.ciphertext, vector.datasize).c_str()); + mbedtls_ccm_free(&ctx); + return false; + } + + // replace encrypted payload with plaintext + uint8_t *p = vector.plaintext; + for (std::vector::iterator it = raw.begin() + cipher_pos; it != raw.begin() + cipher_pos + vector.datasize; + ++it) { + *it = *(p++); + } + + // clear encrypted flag + raw[0] &= ~0x08; + + ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): authenticated decryption passed."); + ESP_LOGVV(TAG, " Plaintext : %s, Packet : %d", hexencode(raw.data() + cipher_pos, vector.datasize).c_str(), + static_cast(raw[4])); + + mbedtls_ccm_free(&ctx); + return true; +} + +} // namespace xiaomi_ylkg07yl +} // namespace esphome + +#endif diff --git a/components/xiaomi_ylkg07yl/xiaomi_ylkg07yl.h b/components/xiaomi_ylkg07yl/xiaomi_ylkg07yl.h new file mode 100644 index 0000000..23a6916 --- /dev/null +++ b/components/xiaomi_ylkg07yl/xiaomi_ylkg07yl.h @@ -0,0 +1,111 @@ +#pragma once + +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/xiaomi_ble/xiaomi_ble.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace xiaomi_ylkg07yl { + +static const uint8_t BUTTON_ON = 0; +static const uint8_t BUTTON_OFF = 1; +static const uint8_t BUTTON_SUN = 2; +static const uint8_t BUTTON_M = 4; +static const uint8_t BUTTON_PLUS = 3; +static const uint8_t BUTTON_MINUS = 5; + +class XiaomiYLKG07YL : public Component, public esp32_ble_tracker::ESPBTDeviceListener { + public: + void set_address(uint64_t address) { address_ = address; } + void set_bindkey(const std::string &bindkey); + + bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; + + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + void set_keycode(sensor::Sensor *keycode) { keycode_ = keycode; } + void add_on_receive_callback(std::function &&callback); + + protected: + uint64_t address_; + uint8_t bindkey_[12]; + sensor::Sensor *keycode_{nullptr}; + CallbackManager receive_callback_{}; + + bool decrypt_legacy_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, const uint64_t &address); +}; + +class OnButtonOnTrigger : public Trigger<> { + public: + OnButtonOnTrigger(XiaomiYLKG07YL *a_remote) { + a_remote->add_on_receive_callback([this](int keycode) { + if (keycode == BUTTON_ON) { + this->trigger(); + } + }); + } +}; + +class OnButtonOffTrigger : public Trigger<> { + public: + OnButtonOffTrigger(XiaomiYLKG07YL *a_remote) { + a_remote->add_on_receive_callback([this](int keycode) { + if (keycode == BUTTON_OFF) { + this->trigger(); + } + }); + } +}; + +class OnButtonSunTrigger : public Trigger<> { + public: + OnButtonSunTrigger(XiaomiYLKG07YL *a_remote) { + a_remote->add_on_receive_callback([this](int keycode) { + if (keycode == BUTTON_SUN) { + this->trigger(); + } + }); + } +}; + +class OnButtonMTrigger : public Trigger<> { + public: + OnButtonMTrigger(XiaomiYLKG07YL *a_remote) { + a_remote->add_on_receive_callback([this](int keycode) { + if (keycode == BUTTON_M) { + this->trigger(); + } + }); + } +}; + +class OnButtonPlusTrigger : public Trigger<> { + public: + OnButtonPlusTrigger(XiaomiYLKG07YL *a_remote) { + a_remote->add_on_receive_callback([this](int keycode) { + if (keycode == BUTTON_PLUS) { + this->trigger(); + } + }); + } +}; + +class OnButtonMinusTrigger : public Trigger<> { + public: + OnButtonMinusTrigger(XiaomiYLKG07YL *a_remote) { + a_remote->add_on_receive_callback([this](int keycode) { + if (keycode == BUTTON_MINUS) { + this->trigger(); + } + }); + } +}; + +} // namespace xiaomi_ylkg07yl +} // namespace esphome + +#endif diff --git a/yeedimmer_ylkg07yl.yaml b/yeedimmer_ylkg07yl.yaml new file mode 100644 index 0000000..a1d5a7c --- /dev/null +++ b/yeedimmer_ylkg07yl.yaml @@ -0,0 +1,42 @@ +substitutions: + name: yeedimmer + +esphome: + name: ${name} + +esp32: + board: esp32doit-devkit-v1 + framework: + type: esp-idf + +external_components: + - source: components +# - source: github://syssi/esphome-yeelight-ceiling-light@main +# refresh: 0s + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + +ota: +api: +logger: + level: VERY_VERBOSE + logs: + uart_esp32: WARN + api.service: WARN + ota: WARN + sensor: WARN + esp32_ble_tracker: WARN + wifi: WARN + +esp32_ble_tracker: + +xiaomi_ylkg07yl: + mac_address: "F8:24:41:C4:7B:BC" + bindkey: "ef3afd53f5c7853a080a3d52" + last_button_pressed: + name: "last button pressed" + on_button_on: + then: + - logger.log: "Button on pressed"