Skip to content

Commit b4d2e41

Browse files
committed
Add YLKG07YL support
1 parent 6f758e9 commit b4d2e41

File tree

7 files changed

+539
-3
lines changed

7 files changed

+539
-3
lines changed

.clang-format

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
Language: Cpp
2+
AccessModifierOffset: -1
3+
AlignAfterOpenBracket: Align
4+
AlignConsecutiveAssignments: false
5+
AlignConsecutiveDeclarations: false
6+
AlignEscapedNewlines: DontAlign
7+
AlignOperands: true
8+
AlignTrailingComments: true
9+
AllowAllParametersOfDeclarationOnNextLine: true
10+
AllowShortBlocksOnASingleLine: false
11+
AllowShortCaseLabelsOnASingleLine: false
12+
AllowShortFunctionsOnASingleLine: All
13+
AllowShortIfStatementsOnASingleLine: false
14+
AllowShortLoopsOnASingleLine: false
15+
AlwaysBreakAfterReturnType: None
16+
AlwaysBreakBeforeMultilineStrings: false
17+
AlwaysBreakTemplateDeclarations: MultiLine
18+
BinPackArguments: true
19+
BinPackParameters: true
20+
BraceWrapping:
21+
AfterClass: false
22+
AfterControlStatement: false
23+
AfterEnum: false
24+
AfterFunction: false
25+
AfterNamespace: false
26+
AfterObjCDeclaration: false
27+
AfterStruct: false
28+
AfterUnion: false
29+
AfterExternBlock: false
30+
BeforeCatch: false
31+
BeforeElse: false
32+
IndentBraces: false
33+
SplitEmptyFunction: true
34+
SplitEmptyRecord: true
35+
SplitEmptyNamespace: true
36+
BreakBeforeBinaryOperators: None
37+
BreakBeforeBraces: Attach
38+
BreakBeforeInheritanceComma: false
39+
BreakInheritanceList: BeforeColon
40+
BreakBeforeTernaryOperators: true
41+
BreakConstructorInitializersBeforeComma: false
42+
BreakConstructorInitializers: BeforeColon
43+
BreakAfterJavaFieldAnnotations: false
44+
BreakStringLiterals: true
45+
ColumnLimit: 120
46+
CommentPragmas: "^ IWYU pragma:"
47+
CompactNamespaces: false
48+
ConstructorInitializerAllOnOneLineOrOnePerLine: true
49+
ConstructorInitializerIndentWidth: 4
50+
ContinuationIndentWidth: 4
51+
Cpp11BracedListStyle: true
52+
DerivePointerAlignment: true
53+
DisableFormat: false
54+
ExperimentalAutoDetectBinPacking: false
55+
FixNamespaceComments: true
56+
ForEachMacros:
57+
- foreach
58+
- Q_FOREACH
59+
- BOOST_FOREACH
60+
IncludeBlocks: Preserve
61+
IncludeCategories:
62+
- Regex: '^<ext/.*\.h>'
63+
Priority: 2
64+
- Regex: '^<.*\.h>'
65+
Priority: 1
66+
- Regex: "^<.*"
67+
Priority: 2
68+
- Regex: ".*"
69+
Priority: 3
70+
IncludeIsMainRegex: "([-_](test|unittest))?$"
71+
IndentCaseLabels: true
72+
IndentPPDirectives: None
73+
IndentWidth: 2
74+
IndentWrappedFunctionNames: false
75+
KeepEmptyLinesAtTheStartOfBlocks: false
76+
MacroBlockBegin: ""
77+
MacroBlockEnd: ""
78+
MaxEmptyLinesToKeep: 1
79+
NamespaceIndentation: None
80+
PenaltyBreakAssignment: 2
81+
PenaltyBreakBeforeFirstCallParameter: 1
82+
PenaltyBreakComment: 300
83+
PenaltyBreakFirstLessLess: 120
84+
PenaltyBreakString: 1000
85+
PenaltyBreakTemplateDeclaration: 10
86+
PenaltyExcessCharacter: 1000000
87+
PenaltyReturnTypeOnItsOwnLine: 2000
88+
PointerAlignment: Right
89+
RawStringFormats:
90+
- Language: Cpp
91+
Delimiters:
92+
- cc
93+
- CC
94+
- cpp
95+
- Cpp
96+
- CPP
97+
- "c++"
98+
- "C++"
99+
CanonicalDelimiter: ""
100+
BasedOnStyle: google
101+
- Language: TextProto
102+
Delimiters:
103+
- pb
104+
- PB
105+
- proto
106+
- PROTO
107+
EnclosingFunctions:
108+
- EqualsProto
109+
- EquivToProto
110+
- PARSE_PARTIAL_TEXT_PROTO
111+
- PARSE_TEST_PROTO
112+
- PARSE_TEXT_PROTO
113+
- ParseTextOrDie
114+
- ParseTextProtoOrDie
115+
CanonicalDelimiter: ""
116+
BasedOnStyle: google
117+
ReflowComments: true
118+
SortIncludes: false
119+
SortUsingDeclarations: false
120+
SpaceAfterCStyleCast: true
121+
SpaceAfterTemplateKeyword: false
122+
SpaceBeforeAssignmentOperators: true
123+
SpaceBeforeCpp11BracedList: false
124+
SpaceBeforeCtorInitializerColon: true
125+
SpaceBeforeInheritanceColon: true
126+
SpaceBeforeParens: ControlStatements
127+
SpaceBeforeRangeBasedForLoopColon: true
128+
SpaceInEmptyParentheses: false
129+
SpacesBeforeTrailingComments: 2
130+
SpacesInAngles: false
131+
SpacesInContainerLiterals: false
132+
SpacesInCStyleCastParentheses: false
133+
SpacesInParentheses: false
134+
SpacesInSquareBrackets: false
135+
Standard: Auto
136+
TabWidth: 2
137+
UseTab: Never

components/xiaomi_ble/xiaomi_ble.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@ bool parse_xiaomi_value(uint8_t value_type, const uint8_t *data, uint8_t value_l
1616
// remote control key code, 3 bytes
1717
if ((value_type == 0x01) && (value_length == 3)) {
1818
result.keycode = data[0];
19-
result.is_long_press = data[2] == 2;
19+
result.dimmer = data[1];
20+
result.press_type = data[2];
21+
ESP_LOGD(TAG, "Key code: %d", data[0]); // button
22+
ESP_LOGD(TAG, "Dimmer: %d", data[1]); // value
23+
ESP_LOGD(TAG, "Press type: %d", data[2]); // 0: single press, 1: double press, 2: long press, 3: ???,
24+
// 4: dimmer <= 127 = rotate right / else: rotate left
2025
}
2126
// motion detection, 1 byte, 8-bit unsigned integer
2227
else if ((value_type == 0x03) && (value_length == 1)) {
@@ -214,6 +219,9 @@ optional<XiaomiParseResult> parse_xiaomi_header(const esp32_ble_tracker::Service
214219
} else if ((raw[2] == 0x53) && (raw[3] == 0x01)) { // Yeelight Remote Control YLYK01YL
215220
result.type = XiaomiParseResult::TYPE_YLYK01YL;
216221
result.name = "YLYK01YL";
222+
} else if ((raw[2] == 0xB6) && (raw[3] == 0x03)) { // Yeelight Wireless Smart Dimmer YLKG07YL/YLKG08YL
223+
result.type = XiaomiParseResult::TYPE_YLKG07YL;
224+
result.name = "YLKG07YL";
217225
} else {
218226
ESP_LOGVV(TAG, "parse_xiaomi_header(): unknown device, no magic bytes.");
219227
return {};
@@ -222,6 +230,7 @@ optional<XiaomiParseResult> parse_xiaomi_header(const esp32_ble_tracker::Service
222230
return result;
223231
}
224232

233+
// Decrypt MiBeacon V4/V5 payload
225234
bool decrypt_xiaomi_payload(std::vector<uint8_t> &raw, const uint8_t *bindkey, const uint64_t &address) {
226235
if (!((raw.size() == 19) || ((raw.size() >= 22) && (raw.size() <= 24)))) {
227236
ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): data packet has wrong size (%d)!", raw.size());

components/xiaomi_ble/xiaomi_ble.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,13 @@ struct XiaomiParseResult {
2525
TYPE_MJYD02YLA,
2626
TYPE_MHOC401,
2727
TYPE_CGPR1,
28-
TYPE_YLYK01YL
28+
TYPE_YLYK01YL,
29+
TYPE_YLKG07YL,
2930
} type;
3031
std::string name;
3132
optional<int> keycode;
33+
optional<int> dimmer;
34+
optional<int> press_type;
3235
optional<float> temperature;
3336
optional<float> humidity;
3437
optional<float> moisture;
@@ -41,7 +44,6 @@ struct XiaomiParseResult {
4144
optional<bool> is_active;
4245
optional<bool> has_motion;
4346
optional<bool> is_light;
44-
optional<bool> is_long_press;
4547
bool has_data; // 0x40
4648
bool has_capability; // 0x20
4749
bool has_encryption; // 0x08
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import esphome.codegen as cg
2+
import esphome.config_validation as cv
3+
from esphome.components import sensor, esp32_ble_tracker
4+
from esphome import automation
5+
from esphome.const import (
6+
CONF_MAC_ADDRESS,
7+
CONF_BINDKEY,
8+
UNIT_EMPTY,
9+
ICON_EMPTY,
10+
DEVICE_CLASS_EMPTY,
11+
CONF_ID,
12+
CONF_TRIGGER_ID,
13+
)
14+
15+
AUTO_LOAD = ["xiaomi_ble", "sensor"]
16+
CODEOWNERS = ["@syssi"]
17+
DEPENDENCIES = ["esp32_ble_tracker"]
18+
MULTI_CONF = True
19+
20+
CONF_LAST_BUTTON_PRESSED = "last_button_pressed"
21+
CONF_ON_BUTTON_ON = "on_button_on"
22+
23+
ON_PRESS_ACTIONS = [
24+
CONF_ON_BUTTON_ON,
25+
]
26+
27+
xiaomi_ylkg07yl_ns = cg.esphome_ns.namespace("xiaomi_ylkg07yl")
28+
XiaomiYLKG07YL = xiaomi_ylkg07yl_ns.class_(
29+
"XiaomiYLKG07YL", esp32_ble_tracker.ESPBTDeviceListener, cg.Component
30+
)
31+
32+
OnButtonOnTrigger = xiaomi_ylkg07yl_ns.class_(
33+
"OnButtonOnTrigger", automation.Trigger.template()
34+
)
35+
36+
37+
def validate_short_bind_key(value):
38+
value = cv.string_strict(value)
39+
parts = [value[i : i + 2] for i in range(0, len(value), 2)]
40+
if len(parts) != 12:
41+
raise cv.Invalid("Bind key must consist of 12 hexadecimal numbers")
42+
parts_int = []
43+
if any(len(part) != 2 for part in parts):
44+
raise cv.Invalid("Bind key must be format XX")
45+
for part in parts:
46+
try:
47+
parts_int.append(int(part, 16))
48+
except ValueError:
49+
# pylint: disable=raise-missing-from
50+
raise cv.Invalid("Bind key must be hex values from 00 to FF")
51+
52+
return "".join(f"{part:02X}" for part in parts_int)
53+
54+
55+
CONFIG_SCHEMA = (
56+
cv.Schema(
57+
{
58+
cv.GenerateID(): cv.declare_id(XiaomiYLKG07YL),
59+
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
60+
cv.Required(CONF_BINDKEY): validate_short_bind_key,
61+
cv.Optional(CONF_LAST_BUTTON_PRESSED): sensor.sensor_schema(
62+
UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY
63+
),
64+
cv.Optional(CONF_ON_BUTTON_ON): automation.validate_automation(
65+
{
66+
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OnButtonOnTrigger),
67+
}
68+
),
69+
}
70+
)
71+
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
72+
.extend(cv.COMPONENT_SCHEMA)
73+
)
74+
75+
76+
async def to_code(config):
77+
var = cg.new_Pvariable(config[CONF_ID])
78+
await cg.register_component(var, config)
79+
await esp32_ble_tracker.register_ble_device(var, config)
80+
81+
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
82+
cg.add(var.set_bindkey(config[CONF_BINDKEY]))
83+
84+
if CONF_LAST_BUTTON_PRESSED in config:
85+
sens = await sensor.new_sensor(config[CONF_LAST_BUTTON_PRESSED])
86+
cg.add(var.set_keycode(sens))
87+
88+
for action in ON_PRESS_ACTIONS:
89+
for conf in config.get(action, []):
90+
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
91+
await automation.build_automation(trigger, [], conf)

0 commit comments

Comments
 (0)